第七章 优雅地断开套接字连接
直接调用close或closesocket函数单方面断开连接,不叫优雅。
7.1 基于TCP的半关闭
单方面断开连接带来的问题
主机A强行close后,由主机B传输的、主机A必须接收的数据也销毁了,所以不优雅。
只关闭一部分数据交换中使用的流的方法就叫优雅的方法。
断开一部分是指:可以传输数据但是无法接收,或可以接收数据但无法传输,就是只关闭流的一半。
套接字和流
两台主机之间有两个流,一个从A到B,一个从B到A,只断开其中的一个流
针对优雅断开的shutdown函数
用于半关闭的函数:
#include<sys/socket.h>
int shutdown(int sock,int howto);
sock 需要断开的套接字文件描述符
howto 传递断开方式信息
SHUT_RD 断开输入流,套接字无法接收数据,即使输入缓冲收到数据也会抹去
SHUT_WR 断开输出流,套接字无法传输数据。但如果输出缓冲还留有未传输的数据,则将传递至目标主机
SHUT_RDWR 同时断开I/O流
成功返回0,失败返回-1
基于半关闭的文件传输程序
理解传递EOF的重要性和半关闭的重要性:
传递EOF的重要性在于,要告诉客户端,我这边给你发的文件发完啦,别再一直调用输入函数啦;
半关闭的重要性在于,即使通过关闭输出流向文件发送了EOF,也仍然还保留有输入流,这样还是能收到客户端发来的Thank you字符串。
file_server.c:
[root@VM_0_10_centos tcpHalf]# cat file_server.c
#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(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_adr,clnt_adr;
socklen_t clnt_adr_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_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]));
bind(serv_sd,(struct sockaddr*)&serv_adr,sizeof(serv_adr));
listen(serv_sd,5);
clnt_adr_sz = sizeof(clnt_adr);
clnt_sd = accept(serv_sd,(struct sockaddr*)&clnt_adr,&clnt_adr_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 handling(char * message){
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
file_client.c :
[root@VM_0_10_centos tcpHalf]# cat file_client.c
#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(char * message);
int main(int argc,char* argv[]){
int sd;
FILE * fp;
char buf[BUF_SIZE];
int read_cnt;
struct sockaddr_in serv_adr;
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]));
connect(sd,(struct sockaddr*)&serv_adr,sizeof(serv_adr));
while((read_cnt = read(sd,buf,BUF_SIZE)) != 0)
fwrite((void*)buf,1,read_cnt,fp);
puts("Received file data");
write(sd,"Thank you",10);
fclose(fp);
close(sd);
return 0;
}
void error_handling(char* message){
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
运行结果:
[root@VM_0_10_centos tcpHalf]# ./fserver 9190 &
[1] 5639
[root@VM_0_10_centos tcpHalf]# ./fclient 127.0.0.1 9190
Received file data
Message from client:Thank you 服务器端半关闭输出流后,表明不往外写东西了,但仍可以接收到客户端发来的信息
[1]+ Done ./fserver 9190
[root@VM_0_10_centos tcpHalf]#
7.2 习题
(1)解释TCP中“流”的概念。UDP中能否形成流?请说明原因
TCP的流指:两台主机通过套接字建立连接后进入可交换数据的状态,也称为“流形成的状态”。而对于UDP来说,不存在流,因为两个SOCKET不能相互连接
(2)Linux中的close函数或Windows中的closesocket函数属于单方面断开连接的方法,有可能带来一些问题。什么是单方面断开连接?什么情况下会出现问题?
单方面的断开连接意味着套接字无法再发送数据。一般在对方有剩余数据未发送完成时,断开己方连接,会造成问题。
(3)什么是半关闭?针对输出流执行半关闭的主机处于何种状态?半关闭会导致对方主机接收什么信息?
半关闭是指只关闭输入和输出流中的其中一个。而且,如果对输出流进行半关闭,EOF被传送到对方主机,己方套接字无法再传送别的数据,但可以接收对方主机传送的数据。