优雅的断开套接字连接(半关闭)

  在断开连接时,除了使用close或closesocket函数完全断开连接,还可以只关闭一部分数据交换中使用的流。断开一部分连接是指,可以传输数据但无法接收,或可以接收数据但无法传输。

套接字和流

  建立套接字后可交换数据的状态看作一种流,在套接字的流中,数据只能向一个方向移动。为了进行双向通信,需要如图所示的两个流。

 一旦两台主机间建立了套接字连接,每个主机就会拥有单独的输入流和输出流。其中一个主机的输入流与另一个主机的输出流相连,而输出流与另一个主机的输入流相连。

针对优雅断开的shutdown函数

  shutdown函数用来关闭其中1个流。 

 

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

半关闭的应用场景

  一种可供参考的应用场景如下:服务器向客户端传输文件,当文件传输结束后需要客户端发送某些信息时,可以调用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;
	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)
	{
	/*
	size_t   fread(   void   *buffer,   size_t   size,   size_t   count,   FILE   *stream   ) 
	 buffer   是读取的数据存放的内存的指针
	 size       是每次读取的字节数  
	 count     是读取次数  
	 stream   是要读取的文件的指针
	 返回成功读取的对象个数,若出现错误或到达文件末尾,则可能小于count。若size或count为零,则fread返回零且不进行其他动作。
	*/
		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);
	}
	
	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) //当收到EOF时客户端知道文件传输已完成,跳出循环
		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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值