socket编程之read函数


疑惑篇

先看下我们的代码:

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一次,这是为什么呢,我们来根据原理找找原因。
客户发送
多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();
}

再测试,没问题了:
在这里插入图片描述