ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Unix网络编程—— shutdown与close

2021-05-17 10:55:55  阅读:174  来源: 互联网

标签:addr int Unix client shutdown sockfd close 接字


TCP的连接终止序列:

TCP建立一个连接需要三次握手,但是终止一个连接需要四次挥手:
1. 当某个应用进程主动调用close时,它向对端发送一个FIN分节,表示这端需要关闭连接
2. 当对端接收到FIN分节时,read函数返回0,它的TCP发送一个ACK,表示接收到了主动close端的FIN分节。主动关闭端的TCP在接受到ACK后处于FIN_WAIT状态,表示需要等待对端的FIN分节到达。
3. 被动关闭端发送FIN分节给主动关闭端,主动关闭端收到FIN后,发送ACK给对端,处于TIME_WAIT状态,表示等待被动关闭端的ACK确认
4. 被动关闭端接收到ACK后四次挥手完成,两端套接字关闭完成
TCP四次挥手

close函数

#includeint close(int fd);// 成功返回0,失败返回-1,并且置位errno

close在TCP中的默认行为是把该套接字标记为已关闭,然后立即返回到调用进程。该套接字描述符不能再由调用进程使用,也就是不能再作为read,write的第一个参数。然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后再发送TCP连接终止序列。

close的错误返回三种情况:
- EBADF:表示参数fd为非法描述符
- EINTR:close调用被信号中断
- EIO:I/O时出现错误

一个常见的TCP并发服务器模型:

#include#include#include#include#include#includeint main(){    int sockfd = socket(AF_INET,SOCK_STREAM,0);    struct sockaddr_in serv_addr;    struct sockaddr_in client_addr;    int client_len;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(LISTEN_PORT);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    int ret = bind(sockfd,(struct sockaddr* )&serv_addr,sizeof(serv_addr));
    ret = listen(sockfd,LISTENQ);    while(1){
        client_len = sizeof(client_addr);        int client_fd = accept(sockfd,(struct sockaddr *)&client_addr,&client_len);
        pid_t pid = fort();        if(pid < 0){            continue;
        }        else if(pid == 0){
            do_serv();
            close();    
        }        else close(client_fd);
    }    return 0;
}

上述代码中close被调用2次,因为子进程和父进程共享了client_fd,其引用计数为2,如果子进程只调用一次,则会导致client_fd的套接字永远不能关闭,导致进程描述符耗尽而无法为其他请求提供服务。并且当close调用时,TCP的读写2端都被关闭。

close在网络编程中的局限:
- 当一个描述符被共享使用时,调用close函数只是将其引用计数减一,仅仅在引用计数为0的时候,该套接字才被关闭。
- close终止读和写2个方向的数据传输。TCP为全双工协议,有时候当我们完成数据发送后,可能需要等待对端发送数据,此时可以调用shutdown来实现此功能。

shutdown函数

int shutdown(int sockfd,int howto); 
//返回:成功返回0,失败返回-1

howto的可选项:
SHUT_RD:关闭连接的读——套接字中不再读取数据,而且套接字接受缓冲区中的数据也会被丢弃。进程不能再对这个套接字调用任何读取函数。
SHUT_WR:关闭连接的写——对于TCP套接字,这成为半关闭。当前留在套接字发送缓冲区中的数据将被发送掉,然后TCP的正常连接终止序列。无论这个套接字描述符的引用计数是否为0,shutdown都会激发TCP终止序列,以后进程不能再对这个套接字调用任何写函数。
SHUT_RDWR:关闭读、写——相当于分别调取了shutdown两次并传递参数SHUT_RD和SHUT_WR。shutdown使用此选项与close的区别是,shutdown立马关闭套接字的读写通道,但是close只会在引用计数为0的情况才关闭读写通道。

close和shutdown的对比示例

主程序

#include#include#include#include#include#include#includeextern void client_echo(FILE *fp,int sockfd);int main(){ 
    int sockfd = socket(AF_INET,SOCK_STREAM,0);    struct sockaddr_in serv_addr;    struct sockaddr_in client_addr;    int client_len;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(LISTEN_PORT);    char *strIp = inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr);    int ret = connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    client_echo(stdin,sockfd);//从stdin读取数据发送到sockfd并回写
    return 0;
}

使用close的实现

void client_echo(FILE *fp,int sockfd){    if(fp == NULL) return;
    int fd = fineno(fp);
    fd_set read_set;
    FD_ZERO(&read_set);
    int stdin_eof = 0;
    int nread = 0;
    char recvbuf[1024];    while(1){
        FD_SET(fd,&read_set);
        FD_SET(sockfd,&read_set);       
        int maxfd = max(fd,sockfd);
        int ret = select(maxfd + 1,&read_set,NULL,NULL,NULL);        if(ret < 0){            if(errno == EINTR) continue; // 由于中断引发的失败,重试            else {                       // 其他错误,退出
                perror("select");                return;
            }
        }        if(FD_ISSET(sockfd,&read_set)){            if( (nread = read(sockfd,recvbuf,sizelf(recvbuf))) == 0){                printf("read EOF from serv\n");
                close(sockfd);                exit(1);
            }
            fputs(recvbuf,stdout);
        }        if(FD_ISSET(fd,&read_set)){            if(fgets(recvbuf,sizeof(recvbuf),fd) == 0){                printf("read EOF from terminate\n");
                close(sockfd);                exit(1);
            }
            write(sockfd,recvbuf,strlen(recvbuf));
        }
    }
}

在上面的程序中,当客户端从终端读取到CTRL+D的时候将使fgets函数返回0,此时引发客户端主动关闭close,退出客户端的连接处理函数后,将立即退出程序(main函数结束)。此时服务端如果有数据正在发送,则将会丢失。处理这种问题的方式是,当客户端不再write时,只关闭TCP的write,但是任然保留其read通道。可以通过shutdown来实现。

使用shutdown的实现

void client_echo(FILE *fp,int sockfd){    if(fp == NULL) return;
    int fd = fineno(fp);
    fd_set read_set;
    FD_ZERO(&read_set);
    int stdin_eof = 0;
    int nread = 0;
    char recvbuf[1024];
    int maxfd;    while(1){       
        FD_SET(fd,&read_set);
        FD_SET(sockfd,&read_set);               
        if(stdin_eof != 0){
            FD_CLR(fd,&read_set);
            maxfd = sockfd;
        }        else maxfd = max(fd,sockfd);
        int ret = select(maxfd + 1,&read_set,NULL,NULL,NULL);        if(ret < 0){            if(errno == EINTR) continue; // 由于中断引发的失败,重试            else {                       // 其他错误,退出
                perror("select");                return;
            }
        }        if(FD_ISSET(sockfd,&read_set)){            if( (nread = read(sockfd,recvbuf,sizelf(recvbuf))) == 0){                if(stdin_eof == 1){                    return;
                }                else{                    printf("read EOF from serv\n");
                    close(sockfd);                    exit(1);
                }
            }
            fputs(recvbuf,stdout);
        }        if(FD_ISSET(fd,&read_set)){            if(fgets(recvbuf,sizeof(recvbuf),fd) == 0){
                stdin_eof = 1;
                shutdown(sockfd,SHUT_WR);   // send FIN
                FD_CLR(fd,&read_set);
            }            else write(sockfd,recvbuf,strlen(recvbuf));
        }
    }
}

在shutdown_client中,当从终端读取到EOF时,将调用shutdown关闭套接字的写通道,但是此套接字任然可以从服务端读取数据,保证了数据不会丢失。

标签:addr,int,Unix,client,shutdown,sockfd,close,接字
来源: https://blog.51cto.com/u_6650004/2780448

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有