TCP 通信服務器端實現第六步:調用close或者shutdown關閉TCP的連接【linux】(zzy)

說明

TCP斷開連接時,可以由客戶和服務器任何一方發起。

調用close或者sutdown函數斷開連接時,對於應用程序來說,只需要調用這兩個函數來關閉連接即可,四次握手的過程是由TCP自動完成的。

使用close函數斷開連接

服務器端調用close斷開連接

close(accept返回的文件描述符);

代碼演示

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#define SPROT 5006
#define SIP "192.168.31.162"

//封裝發送的應用層數據:學生信息
typedef struct _data
{
	unsigned int stu_num;		//學號	
	char stu_name[50];	//姓名
}Data;

void  print_err(char * str,int line,int err_no)//出錯處理函數
{
	printf("%d,%s: %s\n",line,str,strerror(err_no));
	exit(-1);
}


int cfd = -1; /*存放與客戶通信的通信文件描述符*/

//信號處理函數
void signal_fun(int signo)
{
	if(signo == SIGINT)
	{
//斷開連接
		close(cfd);
		exit(0);
	}
}

/*次線程用於接收客戶端的數據*/
void * pth_fun(void * pth_arg)
{
	int ret = 0;
	Data stu_data = {0}; //用於存放接收對方發送過來的學生數據

	while(1)
	{
		bzero(&stu_data,sizeof(stu_data));
		ret = recv(cfd,&stu_data,sizeof(stu_data),0);//接收對方發送過來的數據
		if(-1 == ret) //進行出錯處理
			print_err("recv fail",__LINE__,errno);
		printf("student number = %d\n",ntohl(stu_data.stu_num));
		printf("student name = %s\n",stu_data.stu_name);
	}
}
int main(void)
{
	int ret = -1;
	int sockfd = -1; //存放套接字文件描述符
	/*創建使用TCP協議通信的套接字文件*/
	sockfd =  socket(PF_INET,SOCK_STREAM,0); //指定TCP協議
	if(-1 == sockfd) //進行出錯處理
		print_err("socket fail",__LINE__,errno);

	signal(SIGINT,signal_fun);//

	/*調用bind函數綁定套接字文件/ip/端口*/
	struct sockaddr_in saddr;
	saddr.sin_family = AF_INET;//指定ip格式
	saddr.sin_port= htons(SPROT);//指定端口
	saddr.sin_addr.s_addr = inet_addr(SIP);//設置ip
	ret = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//綁定
	if(-1 ==ret)
		print_err("bind fail",__LINE__,errno);

	/*將主動的套接字文件描述符轉換爲被動的文件描述符,用於被動監聽客戶連接。*/
	ret = listen(sockfd,3);
	if(-1 ==ret)
		print_err("listen fail",__LINE__,errno);
	
	/*調用accep函數,被動監聽客戶的連接*/
	struct sockaddr_in clnaddr = {0};/*用來保存連接客戶端的IP和端口*/
	int clnaddr_size  = sizeof(clnaddr);/*存放結構體變量的大小*/
	cfd = accept(sockfd,(struct sockaddr *)&clnaddr,&clnaddr_size);
	if(-1 ==ret)
		print_err("accept fail",__LINE__,errno);
	/*打印客戶端的端口和ip,一定要記得進行端序轉換*/
	printf("client_port = %d\n,client_ip = %s\n",ntohs(clnaddr.sin_port),inet_ntoa(clnaddr.sin_addr));
	
	/*創建次線程用於接收對方發送的數據*/
	pthread_t tid;
	pthread_create(&tid,NULL,pth_fun,NULL);
	/*循環發送數據*/
	Data stu_data = {0};
	int tmp_num;
	while(1)
	{
		/*獲取學生的學號,但是需要將學號從主機端序轉換爲網絡端序*/
		printf("input student number:\n");
		scanf("%d",&tmp_num);
		stu_data.stu_num = htonl(tmp_num);

		/*char類型不需要端序轉換*/	
		printf("input student name:\n");
		scanf("%s",stu_data.stu_name);

		ret = send(cfd,(void *)&stu_data,sizeof(stu_data),0);
		if(-1 ==ret)
			print_err("send fail",__LINE__,errno);
		
	}
	return 0;

}

客戶端調用close斷開連接

· close(socket返回的描述符)
代碼演示我們在寫客戶端程序的時候進行演示。

close斷開連接的缺點

· 缺點1:會一次性將讀寫都關掉了如果我只想關寫,但是讀打開着,或者只想關讀、但是寫打開着,close做不到。

· 缺點2:如果多個文件描述符指向了同一個連接時,如果只close關閉了其中某個文件描述符時,只要其它的fd還打開着,那麼連接不會被斷開,直到所有的描述符都被close後才斷開連接。

出現多個描述指向同一個連接的原因可能兩個:

  • 通過dup方式複製出其它描述符

  • 子進程繼承了這個描述符,所以子進程的描述符也指向了連接

使用shutdown函數斷開連接

和close函數功能差不多,但是shutdown函數有效的解決了close函數的缺點,所以以後斷開連接時,建議使用更正規shutdown函數。

函數原型

#include <sys/socket.h>
int shutdown(int sockfd, int how);

功能

可以按照要求關閉連接,而且不管有多少個描述符指向同一個連接,只要調用shutdown函數去操作了其中某個描述符,連接就會被立即斷開。

返回值

成功

返回0

失敗

返回-1,ernno被設置。

參數

sokcfd

TCP服務器斷開連接時,使用的是accept所返回的文件描述符。
客戶端斷開連接的時候使用的是socket函數返回的文件描述符。
客戶端調用shutdown斷開連接的情況,我們在實現TCP連接客戶端的博客中詳細說明。

how

如何斷開連接

  • SHUT_RD:只斷開讀連接
  • SHUT_WR:只斷開寫連接
  • SHUT_RDWR:讀、寫連接都斷開

代碼演示

· 服務器shutdown
只需要把使用close函數的地方改爲shutdown函數即可:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#define SPROT 5006
#define SIP "192.168.31.162"

//封裝發送的應用層數據:學生信息
typedef struct _data
{
	unsigned int stu_num;		//學號	
	char stu_name[50];	//姓名
}Data;

void  print_err(char * str,int line,int err_no)//出錯處理函數
{
	printf("%d,%s: %s\n",line,str,strerror(err_no));
	exit(-1);
}


int cfd = -1; /*存放與客戶通信的通信文件描述符*/

//信號處理函數
void signal_fun(int signo)
{
	if(signo == SIGINT)
	{
		//斷開連接
		//close(cfd);
		shutdown(cfd,SHUT_RDWR);
		exit(0);
	}
}

/*次線程用於接收客戶端的數據*/
void * pth_fun(void * pth_arg)
{
	int ret = 0;
	Data stu_data = {0}; //用於存放接收對方發送過來的學生數據

	while(1)
	{
		bzero(&stu_data,sizeof(stu_data));
		ret = recv(cfd,&stu_data,sizeof(stu_data),0);//接收對方發送過來的數據
		if(-1 == ret) //進行出錯處理
			print_err("recv fail",__LINE__,errno);
		printf("student number = %d\n",ntohl(stu_data.stu_num));
		printf("student name = %s\n",stu_data.stu_name);
	}
}
int main(void)
{
	int ret = -1;
	int sockfd = -1; //存放套接字文件描述符
	/*創建使用TCP協議通信的套接字文件*/
	sockfd =  socket(PF_INET,SOCK_STREAM,0); //指定TCP協議
	if(-1 == sockfd) //進行出錯處理
		print_err("socket fail",__LINE__,errno);

	signal(SIGINT,signal_fun);//

	/*調用bind函數綁定套接字文件/ip/端口*/
	struct sockaddr_in saddr;
	saddr.sin_family = AF_INET;//指定ip格式
	saddr.sin_port= htons(SPROT);//指定端口
	saddr.sin_addr.s_addr = inet_addr(SIP);//設置ip
	ret = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//綁定
	if(-1 ==ret)
		print_err("bind fail",__LINE__,errno);

	/*將主動的套接字文件描述符轉換爲被動的文件描述符,用於被動監聽客戶連接。*/
	ret = listen(sockfd,3);
	if(-1 ==ret)
		print_err("listen fail",__LINE__,errno);
	
	/*調用accep函數,被動監聽客戶的連接*/
	struct sockaddr_in clnaddr = {0};/*用來保存連接客戶端的IP和端口*/
	int clnaddr_size  = sizeof(clnaddr);/*存放結構體變量的大小*/
	cfd = accept(sockfd,(struct sockaddr *)&clnaddr,&clnaddr_size);
	if(-1 ==ret)
		print_err("accept fail",__LINE__,errno);
	/*打印客戶端的端口和ip,一定要記得進行端序轉換*/
	printf("client_port = %d\n,client_ip = %s\n",ntohs(clnaddr.sin_port),inet_ntoa(clnaddr.sin_addr));
	
	/*創建次線程用於接收對方發送的數據*/
	pthread_t tid;
	pthread_create(&tid,NULL,pth_fun,NULL);
	/*循環發送數據*/
	Data stu_data = {0};
	int tmp_num;
	while(1)
	{
		/*獲取學生的學號,但是需要將學號從主機端序轉換爲網絡端序*/
		printf("input student number:\n");
		scanf("%d",&tmp_num);
		stu_data.stu_num = htonl(tmp_num);

		/*char類型不需要端序轉換*/	
		printf("input student name:\n");
		scanf("%s",stu_data.stu_name);

		ret = send(cfd,(void *)&stu_data,sizeof(stu_data),0);
		if(-1 ==ret)
			print_err("send fail",__LINE__,errno);
		
	}
	return 0;
}					

· 客戶端調用shutdown
將在編寫TCP連接客戶端代碼時進行演示。

第六步實現之後,服務器端程序就寫好了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章