第七章:优雅地断开套接字连接
7、1 基于TCP的半关闭:
-
单方面断开连接的问题:
-
Linux的close函数意味着完全断开连接,完全断开连接不仅无法传输数据,而且不能接收数据。通信一方调用close函数断开连接显得不太优雅。
-
正在双向通信的A,B主机,在主机A发送完数据后,调用close函数断开连接后,主机A就无法调用接收数据的函数来接收主机B传输的数据,所以由主机B传输的、主机A必须接收的数据也销毁了。
-
“只关闭一部分数据交换中使用的流。(Half-close)” 断开一部分连接指,可以传输数据但是无法接收,或可以接收数据但无法传输。即只关闭流的一半。
-
-
套接字和流:(Stream)
-
两台主机通过套接字建立连接进入可交换数据的状态,称为“流形成的状态”,即将建立套接字连接后可交换数据的状态看作一种流。
-
套接字的流中,数据也只能向一个方向移动。所以为了进行双向通信,需要两个流。
-
两台主机建立了套接字连接,每个主机会拥有单独的输入流和输出流。其中一个主机的输入流与另一个主机的输出流相连,输出流与另一个主机的输入流相连。
-
-
针对优雅地断开secoket的shutdown函数:
-
shutdown函数用来关闭其中1个流:
-
#include <sys/socket.h> int shutdown(int sock, int howto); // 成功返回0,失败返回-1
- sock : 需要断开的套接字文件描述符
- howto:传递断开方式的信息
-
howto参数决定断开连接的方式,其值可能为:
- SHUT_RD:断开输入流
- SHUT_WR:断开输入流
- SHUT_RDWR:同时断开I/O流
-
howto参数传入SHUT_RD,则断开输入流,套接字无法接收数据,即使输入缓冲区收到数据也会抹去,而且无法调用输入相关函数。
-
howto 参数传入 SHUT_WR,则断开输出流,无法传输数据,如果输出缓冲中还留有未传输的数据,则将传递至目标主机。
-
howto参数传入SHUT_RDER相当于分两次调用shutdown,一次以SHUT_RD为参数一次以SHUT_WR为参数。
-
-
基于半关闭的文件传输程序:
-
文件传输服务器与客户端的数据流如图:
-
服务器端代码示例:
-
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 30 void error_handling(const char* message); int main(int argc, char* argv[]){ FILE * fp; int serv_sd,clnt_sd; struct sockaddr_in serv_adr,clnt_adr; socklen_t clnt_adr_sz; char buf[BUF_SIZE]; int read_cnt; if(argc != 2){ printf("Usage: %s <port>\n",argv[0]); exit(1); } // 二进制文件读, 打开文件以向客户端传输服务器端源文件file_server.c fp = fopen("file_server.c","rb"); serv_sd = socket(PF_INET,SOCK_STREAM,0); memset(&serv_adr,0,sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if(bind(serv_sd,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1) error_handling("bind() error"); if(listen(serv_sd,5) == -1) error_handling("listen() error"); clnt_adr_sz = sizeof(clnt_adr); clnt_sd = accept(serv_sd,(struct sockaddr*)&clnt_adr,&clnt_adr_sz); if(clnt_sd == -1) error_handling("accept() error"); else printf("Connected......"); // 向客户端传输文件数据的循环语句。客户端是accept函数调用中连接的 while(1){ read_cnt = fread((void*)buf, 1, BUF_SIZE,fp); if(read_cnt < BUF_SIZE){ write(clnt_sd, buf,read_cnt); printf("last read_cnt : %d\n", read_cnt); break; } write(clnt_sd,buf,BUF_SIZE); printf("read_cnt : %d\n", read_cnt); } // 发送完文件后针对输出流进行半关闭 shutdown(clnt_sd,SHUT_WR); // 只关闭了输出流,还可以通过输入流接收数据 read(clnt_sd,buf,BUF_SIZE); printf("Message from client: %s \n",buf); fclose(fp); close(clnt_sd); close(serv_sd); return 0; } void error_handling(const char* message){ fputs(message,stderr); fputc('\n',stderr); exit(1); }
-
-
客户端代码示例:
-
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 30 void error_handling(const char* message); int main(int argc, char* argv[]){ FILE* fp; int sd; struct sockaddr_in serv_adr; char buf[BUF_SIZE]; int read_cnt; if(argc != 3){ printf("Usage : %s <IP> <port>\n",argv[0]); exit(1); } // 创建新文件以保存服务器端传输的文件数据 fp = fopen("receive.dat","wb"); sd = socket(PF_INET, SOCK_STREAM,0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = inet_addr(argv[1]); serv_adr.sin_port = htons(atoi(argv[2])); if(connect(sd,(struct sockaddr*)&serv_adr, sizeof(serv_adr))) error_handling("Connect() error"); else{ puts("Connected ......."); } // 接收数据保存到文件fp中,知道接收到EOF为止 while((read_cnt = read(sd,buf,BUF_SIZE)) != 0) fwrite((void*)buf,1,read_cnt,fp); puts("Recevied file data"); //向服务器端发送感谢信息。如果服务器端没有关闭输入流,则可以接收到这个消息 write(sd,"Thank you",10); fclose(fp); close(sd); return 0; } void error_handling(const char* message){ fputs(message,stderr); fputc('\n',stderr); exit(1); }
-
-