网编(6):优雅地断开套接字连接

我们是调用close 或closesocket 函数只是单方面断开连接。

————————————————————————————————————————————————

基于TCP 的半关闭

TCP中的断开连接过程比建立连接过程更重要,因为连接过程中一般不会出现大的变数,但断开过程有可能发生预想不到的情况,因此应准确掌控。只有掌握了下面要讲解的半关闭(Half-close), 才能明确断开过程。

单方面断开连接带来的问题
Linux 的close函数和Windows的closesocket函数意味着完全断开连接。完全断开不仅指无法传输数据,而且也不能接收数据。因此, 在某些情况下,通信一方调用close或closesocket函数断开连接就显得不太优雅,如图所示。

上图描述的是2 台主机正在进行双向通信。主机A发送完最后的数据后,调用close函数断开了连接,之后主机A无法再接收主机B传输的数据。实际上,是完全无法调用与接收数据相关的函数。最终,由主机B传输的、主机A必须接收的数据也销毁了。

为了解决这类问题,“只关闭一部分数据交换中使用的流" ( Half-close ) 的方法应运而生。断开一部分连接是指 “可以传输数据但无法接收,或可以接收数据但无法传输”。顾名思义就是只关闭流的一半。

————————————————————————————————————————————————

套接字和流( Stream )

两台主机通过套接字建立连接后进入可交换数据的状态,又称“流形成的状态"。也就是把建立套接字后可交换数据的状态看作一种流。

此处的流可以比作水流。水朝着一个方向流动,同样,在套接字的流中,数据也只能向一个方向移动。因此,为了进行双向通信,需要如图所示的2个流。

一旦两台主机间建立了套接字连接,每个主机就会拥有单独的输入流和输出流。当然,其中一个主机的输入流与另一主机的输出流相连,而输出流则与另一主机的输入流相连。另外,“优雅地断开连接方式”只断开其中1 个流,而非同时断开两个流。Linux的close和Windows的closesocket函数将同时断开这两个流,因此与“优雅” 二字还有一段距离。

————————————————————————————————————————————————

针对优雅断开的 shutdown 函数

#include <sys/socket.h>
int shutdown(int sock, int howto);
//成功时返回0, 失败时返回-1。

#sock 需要断开的套接字文件描述符。
#howto 传递断开方式信息。

调用上述函数时, 第二个参数决定断开连接的方式,其可能值如下所示。

  • SHUT_RD: 断开输入流,套接字无法接收数据。即使输入缓冲收到数据也会抹去,而且无法调用输入相关函数。
  • SHUT_WR: 中断输出流,也就无法传输数据。但如果输出缓冲还留有未传输的数据,则将传递至目标主机。
  • SHUT RDWR: 同时中断I/O流。这相当于分2次调用shutdown ,其中一次以SHUT_RD为参数,另一次以SHUT_WR为参数。

————————————————————————————————————————————————

为何需要半关闭

close与shutdown的区别主要表现在:
close函数会关闭套接字ID,如果有其他的进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写,并且有时候这是非常重要的 ,特别是对于多进程并发服务器来说。

而shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零,那些试图读得进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号,同时可利用shutdown的第二个参数选择断连的方式。

调用close函数的同时关闭1/0流,这样也会向对方发送EOF 。但此时无法再接收对方传输的数据。换言之,若调用close函数关闭流,就无法接收客户端最后发送的字符串"Thank you " 。这时需要调用shutdown函数,只关闭服务器的输出流( 半关闭)。这样既可以发送EOF, 同时又保留了输入流,可以接收对方数据。

实例代码:

服务器:

#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 = NULL;
	char buf[BUF_SIZE] = {0};
	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("message.txt", "rb"); //从这个文件读取信息
	if (fp == NULL) {
		printf("error\n");
		exit(-1);
	}
	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 error_handling(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(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);
	}
	
	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) {
		printf("cli:%s\n", (char *)buf);
	}
	
	puts("Received file data");
	write(sd, "Thank you", 10);//在服务器半关闭后,再回复一个信息
	close(sd);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

运行结果:

服务器:
$ ./ser 9190 
Message from client: Thank you 

客户端:
$ ./cli 192.168.1.141 9190
cli:aaaaaa
Received file data

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值