在TCP服务端和客户端建立连接之后服务端和客户端会分别有两个独立的输入流和输出流,而且相互对应。服务端的输出流对应于客户端的输入流,服务端的输入流对应于客户端的输出流。这是在建立连接之后的状态。
当我们调用close()函数时,系统会同时把双方的输入流和输出流全部关闭,但是有时候我们仍需要在一方断开连接之后只进行接受数据或者传输数据其中一项操作。这时就需要我们只断开输入或者输出,保留另一个流的正常运转,也就引入了TCP的半关闭状态。
基本操作:
之前我们传输完数据之后便直接调用了close()函数,我们可以使用系统提供的shutdown()函数方便的完成TCP的半关闭。
shutdown(int socket , int type):半关闭套接字中的输入或者输出流
- socket(套接字描述符):需要断开的套接字描述符
- type(断开类型):套接字的断开方式
SHUT_RD——断开输入流,并清空输入缓冲中的数据
SHUT_WR——断开输出流,并将输出缓冲中的数据输出
SHUT_RDWR——同时断开输入输出流,分两次调用shutdown()函数
成功时返回0,失败时返回-1
服务端:
#include <stdio.h> #include <stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #define BUFF_SIZE 30 void error_handling(char *message); /** 基于TCP的半连接服务端 **/ int main(int argc ,char *argv[]){ //客户端和服务端的socket描述符 int serv_sock; int clent_sock; //客户端和服务端的地址 struct sockaddr_in serv_addr; struct sockaddr_in clent_addr; //客户端地址大小 socklen_t client_addr_size; //用于记录读取的文件字节数 int read_count; //缓冲大小 char buff[BUFF_SIZE]; //用于打开文件的文件指针 FILE *fp; //检查输入的参数个数是否合法 if(argc!=2){ printf("Usage : %s <port> ",argv[0]); exit(1); } //使用文件描述符打开文件 fp = fopen("word_file.txt","rb"); //创建服务端saocket serv_sock = socket(AF_INET,SOCK_STREAM,0); //将server_addr中的内容清空 memset(&serv_addr,0,sizeof(serv_addr)); //初始化socket地址 serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); //进行地址绑定 if(bind(serv_sock,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) == -1){ error_handling("bind() error"); } //使服务端进入监听状态 if(listen(serv_sock,5) == -1){ error_handling("listen() error"); } //获的客户端地址的大小 client_addr_size = sizeof(clent_addr); clent_sock = accept(serv_sock,(struct sockaddr *) &clent_addr,&client_addr_size); while(1){ //读取的文件字节数 read_count = fread((void *) buff,1,BUFF_SIZE,fp); //向客户端写入数据 if(read_count<BUFF_SIZE){ write(clent_sock,buff,read_count); break; } write(clent_sock,buff,BUFF_SIZE); } /** 在数据输出完成之后,对输出流进行流半关闭 这种状态下服务读不能向客户端写入数据,但是可以接受来自客户端的数据 **/ shutdown(clent_sock,SHUT_WR); //接受来自客户端的消息 while(0 == read(clent_sock,buff,BUFF_SIZE)) { continue; } //打印消息 printf("Message from client : %s ",buff); //关闭文件 fclose(fp); write(clent_sock,"11111",5); //彻底关闭TCP连接 //close(clent_sock); //close(serv_sock); //避免因进程退出,导致 server 向 client 发 FIN while(1){}; return 0; } void error_handling(char * message){ fputs(message,stderr); fputc(' ',stderr); exit(1); }
客户端:
#include <stdio.h> #include <stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #define BUFF_SIZE 30 void error_handling(char *message); int main(int argc ,char *argv[]){ int sock; FILE *fp; char buff[BUFF_SIZE]; int read_count; struct sockaddr_in server_addr; if(argc!=3){ printf("Usage : %s <port> ",argv[0]); exit(1); } fp = fopen("recevice.dat","wb"); sock = socket(AF_INET,SOCK_STREAM,0); memset(&server_addr,0,sizeof(server_addr)); //初始化socket地址 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(argv[1]); server_addr.sin_port = htons(atoi(argv[2])); connect(sock,(struct sockaddr *) &server_addr,sizeof(server_addr)); while(read_count = read(sock,buff,BUFF_SIZE) != 0){ //向文件中写入读取到的数据 printf("recv data from server : %s ",buff); fwrite((void *) buff,1,read_count,fp); } //打印接收到的数据 puts("Recevied file data"); //接收完成之后向服务端发送一个消息 // 睡3秒,确保server端 shutdown()已执行 sleep(3); write(sock,"Thank you ",10); write(sock,"server",6); //关闭文件和socket连接 fclose(fp); // 避免因进程退出,导致 client 向 server 发 FIN while(1){}; //close(sock); return 0; } void error_handling(char * message){ fputs(message,stderr); fputc(' ',stderr); exit(1); }
至此就完成了一个基于半关闭的TCP服务端/客户端程序,这样使得服务端在传输完数据之后可以只保留输入流,关闭输出流。
抓包:https://files.cnblogs.com/files/yorkyang/tcp%E5%8D%8A%E5%85%B3%E9%97%AD.zip
转自:http://blog.csdn.net/wqc_csdn/article/details/51543504