• 【TCP/IP网络编程】:07优雅地断开套接字连接


    本篇文章简单讨论了TCP套接字半关闭的相关知识。

    通常来说,TCP建立连接的过程相对稳定,因为此时并未开始进行数据交换;而断开连接的过程由于已发生了数据交换,可能会发生一些预想不到的情况。

    单方面断开连接带来的问题

    前文所述的内容中,我们直接调用了close函数进行了完全断开连接,这就意味着本端既无法再发送数据,也不能再接收数据了。而如果本端仅仅希望不再发送数据,还能够接收数据的话,直接调用close完全断开连接则显得不够优雅。因此,我们需要一种“只关闭一部分数据交换中使用的流”(Half-close)的方法。

     单方面断开连接

    套接字和流(Stream)

    两台主机通过套接字建立连接后进入可交换数据的状态,又称为“流形成的状态”。每台主机都拥有单独的输入流和输出流,并和对端的输出流和输入流相匹配而形成两个I/O流。

     套接字中形成的两个I/O流

    针对优雅断开的shutdown函数

    shutdown可用来断开双向I/O流中的一个。

    #include <sys/socket.h>
    
    int shutdown(int sock, int howto);
        -> 成功时返回0,失败时返回-1

    其中,第二个参数决定断开流的方式:

    • SHUT_RD:断开输入流
    • SHUT_WR:断开输出流
    • SHUT_RDWR:同时断开两个I/O流

    所谓断开流,其实是断开套接字与其I/O缓冲区之间的通道。因此,SHUT_RD断开输入流,套接字便无法接收数据,即使输入缓冲收到数据也会被抹去,且无法调用输入相关函数;SHUT_WR断开输出流,套接字便无法传输数据,但如果输出缓冲还留有数据,仍然可以传递至目标主机。

    为何需要半关闭

    半关闭主要作用有两方面。其一,向目标主机发出一个数据传输结束的信号(EOF),使对端感知到本端数据已经发送完成而可以进行其他动作了(这个作用close函数也可以完成);其二,如果本端在数据发送完成后还需要接收对端的反馈信息,则需要调用shutdown函数仅进行输出流的关闭(半关闭,这一点close函数无法实现)。

    基于半关闭的文件传输程序

    下面以基于半关闭的文件传输程序做结,来展示shutdown半关闭的作用。

     文件传输数据流图 

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <unistd.h>
     5 #include <arpa/inet.h>
     6 #include <sys/socket.h>
     7 
     8 #define BUF_SIZE 30
     9 void error_handling(char *message);
    10 
    11 int main(int argc, char *argv[])
    12 {
    13     int serv_sd, clnt_sd;
    14     FILE * fp;
    15     char buf[BUF_SIZE];
    16     int read_cnt;
    17     
    18     struct sockaddr_in serv_adr, clnt_adr;
    19     socklen_t clnt_adr_sz;
    20     
    21     if(argc!=2) {
    22         printf("Usage: %s <port>
    ", argv[0]);
    23         exit(1);
    24     }
    25     
    26     fp=fopen("file_server.c", "rb"); 
    27     serv_sd=socket(PF_INET, SOCK_STREAM, 0);   
    28     
    29     memset(&serv_adr, 0, sizeof(serv_adr));
    30     serv_adr.sin_family=AF_INET;
    31     serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    32     serv_adr.sin_port=htons(atoi(argv[1]));
    33     
    34     bind(serv_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    35     listen(serv_sd, 5);
    36     
    37     clnt_adr_sz=sizeof(clnt_adr);    
    38     clnt_sd=accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
    39     
    40     while(1)
    41     {
    42         read_cnt=fread((void*)buf, 1, BUF_SIZE, fp);
    43         if(read_cnt<BUF_SIZE)
    44         {
    45             write(clnt_sd, buf, read_cnt);
    46             break;
    47         }
    48         write(clnt_sd, buf, BUF_SIZE);
    49     }
    50     
    51     shutdown(clnt_sd, SHUT_WR);    
    52     read(clnt_sd, buf, BUF_SIZE);
    53     printf("Message from client: %s 
    ", buf);
    54     
    55     fclose(fp);
    56     close(clnt_sd); close(serv_sd);
    57     return 0;
    58 }
    59 
    60 void error_handling(char *message)
    61 {
    62     fputs(message, stderr);
    63     fputc('
    ', stderr);
    64     exit(1);
    65 }
    66 
    67 flie_server
    68 
    69 file_server
    file_server
     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <unistd.h>
     5 #include <arpa/inet.h>
     6 #include <sys/socket.h>
     7 
     8 #define BUF_SIZE 30
     9 void error_handling(char *message);
    10 
    11 int main(int argc, char *argv[])
    12 {
    13     int sd;
    14     FILE *fp;
    15     
    16     char buf[BUF_SIZE];
    17     int read_cnt;
    18     struct sockaddr_in serv_adr;
    19     if(argc!=3) {
    20         printf("Usage: %s <IP> <port>
    ", argv[0]);
    21         exit(1);
    22     }
    23     
    24     fp=fopen("receive.dat", "wb");
    25     sd=socket(PF_INET, SOCK_STREAM, 0);   
    26 
    27     memset(&serv_adr, 0, sizeof(serv_adr));
    28     serv_adr.sin_family=AF_INET;
    29     serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    30     serv_adr.sin_port=htons(atoi(argv[2]));
    31 
    32     connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    33     
    34     while((read_cnt=read(sd, buf, BUF_SIZE ))!=0)
    35         fwrite((void*)buf, 1, read_cnt, fp);
    36     
    37     puts("Received file data");
    38     write(sd, "Thank you", 10);
    39     fclose(fp);
    40     close(sd);
    41     return 0;
    42 }
    43 
    44 void error_handling(char *message)
    45 {
    46     fputs(message, stderr);
    47     fputc('
    ', stderr);
    48     exit(1);
    49 }
    file_client

    运行结果

  • 相关阅读:
    (转)A*算法详解及习题
    BZOJ1444[Jsoi2009]有趣的游戏——AC自动机+概率DP+矩阵乘法
    交通——set+dsu on tree
    [Codeforces1132G]Greedy Subsequences——线段树+单调栈
    BZOJ4482[Jsoi2015]套娃——贪心+set
    BZOJ4477[Jsoi2015]字符串树——可持久化trie树
    BZOJ4475[Jsoi2015]子集选取——递推(结论题)
    [UOJ86]mx的组合数——NTT+数位DP+原根与指标+卢卡斯定理
    BZOJ2428[HAOI2006]均分数据——模拟退火
    BZOJ4712洪水——动态DP+树链剖分+线段树
  • 原文地址:https://www.cnblogs.com/Glory-D/p/12112875.html
Copyright © 2020-2023  润新知