首页 > 技术文章 > 【TCP/IP网络编程】:07优雅地断开套接字连接

Glory-D 2019-12-28 20:03 原文

本篇文章简单讨论了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>\n", 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 \n", 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('\n', 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>\n", 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('\n', stderr);
48     exit(1);
49 }
file_client

运行结果

推荐阅读