疑惑篇
先看下我们的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | int main(void) { ... for(;;) { clilen = sizeof(cliaddr); connfd = accept(listenfd,(struct sockaddr *) &cliaddr,&clilen); if((childpid=fork())==0){ close(listenfd); str_echo(connfd); exit(0); } close(connfd); } } void str_echo(int sockfd) { ssize_t n; char buf[MAXLINE]; again: while((n=read(sockfd,buf,MAXLINE))) { buf[n] = '\0'; printf("%s\n",buf); } if(n<0&&errno==EINTR) goto again; else if(n<0) printf("str_echo:read error"); } |
这个程序是unix服务器程序中,主程序fork的一个子程序。当我们的客户机发送一个字符串时,str_echo函数会把它在shell上打印出来。问题来了:当我们的客户程序退出时,shell会把之前打印的字符多print一次,这是为什么呢,我们来根据原理找找原因。
函数read
我们来看看read是如何工作的,首先看函数原型:
1 2 | #include <unistd.h> sszize_t read(int fd,void *buf,size_t nbytes); |
read 从文件描述符fd中读取nbyte个字节,然后把这些字节存到buf中去。
read是有返回值的,如果n>0,那么就可以读到BUF里去,n=0,说明客户端发送了FIN信号,就断开连接,n<0,说明发生了中断或者错误。
解决篇
想了很久,觉得有点奇怪。当客户端退出时,read会返回0,这是不进入while循环,按道理是正常退出。然而似乎仍然打印了一次。这是为什么?
后来我把
1 | while((n=read(sockfd,buf,MAXLINE))) |
改为
1 | while((n=read(sockfd,buf,MAXLINE))>0) |
奇迹发生了,不会再多读一次,但是有以下输出:
说明我们之前一直返回的n是负数,我们直到,条件为负数时也为真,所以多print了一次。
那么为什么会返回负数呢?
思考
看看这篇文章:socket读写返回值的处理。我们先把errno print一下。得到104。也就是Connection reset by peer。也就是说,我们在客户端退出时没有正常关闭socket。
客户端时用qt写的,我们做一点修改即可。
1 2 3 4 | void JGF::closeEvent(QCloseEvent* ev)//关掉QMainwindow时关闭连接 { tcpSocket->close(); } |
再测试,没问题了: