本篇文章简单讨论了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
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 }
运行结果