說明
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連接客戶端代碼時進行演示。
第六步實現之後,服務器端程序就寫好了。