- 论坛徽章:
- 0
|
呵呵. wangshim_ 兄,很高兴和你讨论这个问题.
先说一些题外话.
我对网络编程不怎么熟悉.接触socket是在几个月前.
我参加的项目中有一个小模块是个消息接收处理模块.所以我边学习边写了一个多线程tcp server.
当时处理server如何知道clinet退出这个问题, 我是参考了网上的资料(具体是哪里的文章我忘记了)并且做了测试后,
用的判断方法是:server时刻关注连接的fd,如果select()到该fd可读但是read()的返回是0的话,那么就认为client主动断开了连接.
具体原理我没有时间,同时也觉得没有必要去了解.在具体开发中,我认为会用select()或许比精通TCP协议更有用,你觉得呢?
现在由于这个帖子,我对其中的细节比较有兴趣了,所以看了一些资料,补充学习了一下.
下面看一下一个连接断开的过程.
为了理解后面的步骤.需要先贴一些代码上来.这里可以先跳过这些代码.
(代码是<Unix Network Programming Volume 1,Third Edition The Sockets Networking API>上直接拷过来的.)
- 这是server:
- 1 #include "unp.h"
- 2 void
- 3 str_echo(int sockfd)
- 4 {
- 5 ssize_t n;
- 6 char buf[MAXLINE];
- 7 again:
- 8 while ( (n = read(sockfd, buf, MAXLINE)) > 0)
- 9 Writen(sockfd, buf, n);
- 10 if (n < 0 && errno == EINTR)
- 11 goto again;
- 12 else if (n < 0)
- 13 err_sys("str_echo: read error");
- 14 }
- 2 int
- 3 main(int argc, char **argv)
- 4 {
- 5 int listenfd, connfd;
- 6 pid_t childpid;
- 7 socklen_t clilen;
- 8 struct sockaddr_in cliaddr, servaddr;
- 9 listenfd = Socket (AF_INET, SOCK_STREAM, 0);
- 10 bzero(&servaddr, sizeof(servaddr));
- 11 servaddr.sin_family = AF_INET;
- 12 servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
- 13 servaddr.sin_port = htons (SERV_PORT);
- 14 Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
- 15 Listen(listenfd, LISTENQ);
- 16 for ( ; ; ) {
- 17 clilen = sizeof(cliaddr);
- 18 connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
- 19 if ( (childpid = Fork()) == 0) { /* child process */
- 20 Close(listenfd); /* close listening socket */
- 21 str_echo(connfd); /* process the request */
- 22 exit (0);
- 23 }
- 24 Close(connfd); /* parent closes connected socket */
- 25 }
- 26 }
复制代码
- 这是client:
- 1 #include "unp.h"
- 2 void
- 3 str_cli(FILE *fp, int sockfd)
- 4 {
- 5 char sendline[MAXLINE], recvline[MAXLINE];
- 6 while (Fgets(sendline, MAXLINE, fp) != NULL) {
- 7 Writen(sockfd, sendline, strlen (sendline));
- 8 if (Readline(sockfd, recvline, MAXLINE) == 0)
- 9 err_quit("str_cli: server terminated prematurely");
- 10 Fputs(recvline, stdout);
- 11 }
- 12 }
- 2 int
- 3 main(int argc, char **argv)
- 4 {
- 5 int sockfd;
- 6 struct sockaddr_in servaddr;
- 7 if (argc != 2)
- 8 err_quit("usage: tcpcli <IPaddress>");
- 9 sockfd = Socket(AF_INET, SOCK_STREAM, 0);
- 10 bzero(&servaddr, sizeof(servaddr));
- 11 servaddr.sin_family = AF_INET;
- 12 servaddr.sin_port = htons(SERV_PORT);
- 13 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
- 14 Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
- 15 str_cli(stdin, sockfd); /* do it all */
- 16 exit(0);
- 17 }
- Create socket
复制代码
<Unix Network Programming Volume 1,Third Edition The Sockets Networking API.chm>
中关于一个连接正常断开过程的描述:
- client连上了server.client输入^D.
- 1.When we type our EOF character, fgets returns a null pointer
- and the function str_cli returns.
- 2.When str_cli returns to the client main function,
- the latter terminates by calling exit.
- //步骤1和2告诉我们client退出
- 3.Part of process termination is the closing of all open descriptors,
- so the client socket is closed by the kernel.
- This sends a FIN to the server, to which the server TCP responds with an ACK.
- //上面这句比较关键,我想这就是server select()的时候可读的原因
- This is the first half of the TCP connection termination sequence.
- At this point, the server socket is in the CLOSE_WAIT state
- and the client socket is in the FIN_WAIT_2 state.
- 4.When the server TCP receives the FIN,
- the server child is blocked in a call to readline,
- //server开始读这个文件描述符
- and readline then returns 0.
- //但是缓冲区没有数据可读,所以返回0
- This causes the str_echo function to return to the server child main.
- 5.The server child terminates by calling exit.
- 6.All open descriptors in the server child are closed.
- The closing of the connected socket by the child causes the final two segments
- of the TCP connection termination to take place: a FIN from the server to the client,
- and an ACK from the client. At this point, the connection is completely terminated.
- The client socket enters the TIME_WAIT state.
- 7.Finally, the SIGCHLD signal is sent to the parent when the server child terminates.
- This occurs in this example, but we do not catch the signal in our code,
- and the default action of the signal is to be ignored.
- Thus, the child enters the zombie state. We can verify this with the ps command.
复制代码
以上还不能证明"select()到文件描述符可读但是read()返回0,就说明对方断开连接"是正确的.
因为还有一种情况,如果client发送的就是0字节的内容呢?于是我做了一个简单的测试,
发现write()0字节的时候server根本没有反应,不会select()到fd可读,或者就是说0字节是更本write()不过去的(本来就没有嘛).
还有一种情况,如果client是被kill -9了呢,没来得及close()文件描述符怎么办?
我想这种情况系统会自动close的吧.不知道是否正确.
所以,我的理解是:client断开,server是能够理解知道的,并且是根据"select()到文件描述符可读但是read()返回0"来判断.
对了,select()的功能大致就是告诉我们一个描述符是否已经准备好读,写或异常.
匆匆看了一些资料,没时间细想,还忘各位指正. |
|