基于TCP的半关闭
在前面的章节中,我们都是通过close或者closesocket来断开套接字连接的,但是调用这两个函数导致我们套接字完全断开,套接字将无法接受数据,并且也只能传输完最后余留在缓冲区的数据内容。此时"只关闭一部分数据交换中使用的流"的方法应运而生。
针对优雅断开的shutdown函数
#include<sys/socket.h> int shutdown(int sock,int howto);//成功时返回0,失败时返回-1 sock
//需要半断开的文件描述符、 howto //进行半断开的方式
此函数的第二个参数可能是下面之一:
1.SHUT_RD//断开输入流
2.SHUT_WR//断开输出流
3.SHUT_RDWR//同时断开IO流
为何需要半关闭
试想一个场景,在客户端和服务端建立连接后,服务端向客户端传递文件,当服务端传递完文件后,客户端需要发送一个"Thank
you"给服务端。这里就有一个问题,客户端该何时知道它应该发送“Thank
you”给服务端。如果服务端通过close关闭套接字发EOF给客户端的话,服务端将再也无法接受“Thank
you”。因此如果服务端只用关闭它的输出流,并且传递EOF给客户端的话就能够解决这个问题。shutdown函数的半关闭可以同时做到上述的两个需求。
基于半关闭的文件传输程序
下面介绍服务器的代码:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h>
#include<arpa/inet.h> #include<sys/socket.h> #defind BUF_SIZE 30; void
error_handling(char*message); int main(int argc,char *argv[]){ int
serv_sd,clnt_sd; FILE *fp; char buf[BUF_SIZE]; int read_cnt; struct sockaddr_in
serv_addr,clnt_addr; socklen_t clnt_addr_sz; if(argc!=2){ printf("Usage: %s
<port>\n",argv[0]); exit(1); } fp=fopen("file_server.c","rb");
serv_sd=socket(PF_INET,SOCK_STREAM,0); memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_familiy=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1])); bind(serv_sd,(struct
sockaddr*)&serv_addr,sizeof(serv_addr)); listen(serv_sd,5);
clnt_addr_sz=sizeof(clnt_addr); clnt_sd=accept(serv_ad,(struct
sockaddr*)&clnt_addr,&clnt_addr_sz); while(1){
read_cnt=fread((void*)buf,1,BUF_SIZE,fp); if(read_cnt<BUF_SIZE){
write(clnt_sd,buf,read_cnt); break; } write(clnt_sd,buf,BUF_SIZE); }
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(char *message){ fputs(message,stderr); fputc('\n',stderr);
exit(1); }
下面介绍客户端代码:
#include<"与服务器头文件声明一致,故省略"> #defind BUF_SIZE 30 void error_handling(char
*message); int main(int argc,char*argv[]){ int sd; FILE *fp; char
buf[BUF_SIZE]; int read_cnt; struct sockaddr_in serv_addr; 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_addr,0,sizeof(serv_addr)); serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2])); connect(sd,(struct
sockaddr*)&serv_addr,sizeof(serv_addr));
while((read_cnt=read(sd,buf,BUF_SIZE))!=0) fwrite((void*)buf,1,read_cnt,fp);
puts("Reveived file data"); write(sd,"Thank you",10); fclose(fp); close(sd);
return 0; } void error_handling(char*message){ //与服务器的内容一致 }
基于Windows的实现
Windows平台同样通过调用shutdown函数完成半关闭,只是想起传递的参数名略有不同。
#include<winsock2.h> int shutdown(SOCKET sock,int
howto);//成功返回0.失败返回SOCKET_ERROR; sock //要断开的套接字句柄 howto //断开方式的信息
上述函数的第二个参数的可能值及其含义如下:
1.SD_RECEIVE:断开输入流
2.SD_SEND:断开输出流
3.SD_BOTH:断开IO流
虽然这些常量名不同于Linux中的名称,但是其内部的值完全相同。