本篇文章简单讨论了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
运行结果
点滴记录 点滴成长 未雨绸缪 不乱于心