помахал четыре раза
На следующем рисунке показан процесс типичной волны TCP из четырех рук, а также изменения состояния стороны активного закрытия и стороны пассивного закрытия. На рисунке клиент активно разрывает соединение, это просто пример, сервер тоже может активно отключаться.
Почему он машет четыре раза, потому что если бы только 1 или 2 раза было сделано. Поскольку TCP полнодуплексный, он может находиться в состоянии Half-Close.В это время он находится в состоянии Half-Close.Канал от клиента к серверу был закрыт, а канал от сервера к клиент не был закрыт, поэтому требуется третий и четвертый раз, чтобы полностью закрыть соединение.который добровольно закрыл
Клиент активно закрывается
Если клиент активно закрывает соединение, это обычное закрытие. Сначала клиент инициирует запрос на закрытие соединения.После того, как сервер его получает, он сначала отправляет клиенту ACK, чтобы указать, что запрос на закрытие был получен, а затем соединение между клиентом и сервером может быть закрыто. В это время сервер не закроет соединение сразу, а сервер отправит клиенту незавершенные данные.
В это время клиент просто не может отправлять данные на сервер, но может принимать данные, что является полузакрытием TCP. В это время клиент находится в FIN_WAIT2, а сервер в CLOSE_WAIT (если сервер не закроет соединение, он всегда будет в этом состоянии, если соединений много, он будет тратить много ресурсов сокета).
Сервер отправляет клиенту запрос на закрытие соединения после отправки данных, а клиент подтверждает закрытие после получения данных, в это время полнодуплексное TCP-соединение закрывается.
Активное отключение сервера
Если сервер активно инициирует завершение работы, порядок четырех волн будет обратным. Тогда, когда клиент отправляет данные на сервер, согласно положениям протокола TCP, это считается аварийным завершением соединения, и клиент получитСброс RSTОтвет (вместо ответа ACK), если клиент снова отправляет данные на сервер, система отправитСигнал SIGPIPEКлиентскому процессу скажите клиентскому процессу, что соединение закрыто и больше не пишите. Обработка сигнала SIGPIPE системой по умолчанию заключается в непосредственном завершении процесса, который получает сигнал, поэтому клиентский процесс будет завершен в это время неохотно.
Если вы не хотите, чтобы клиентский процесс завершался, вы можете настроить функцию для обработки сигнала и обрабатывать сигнал, вызывая сигнал функции (SIGPIPE, обработчик), где обработчик является настраиваемой функцией.
Поэтому, когда сервер активно закрыт, клиент продолжает писать дважды, что приведет к завершению клиентского процесса (сервер не может его получить), и клиент не может записать данные на сервер.
В этот момент можно заметить, что полнодуплексное соединение TCP было закрыто, но клиентский процесс не был закрыт, потому что обработчик сигнала настроен.
Тестовая программа (под linux)
Сервер
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
// socket(int domain, int type, int protocol);
//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int StartUp(char* ip,char* port)
{
//listen_sock
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("socket...");
exit(-1);
}
struct sockaddr_in sockaddr;
sockaddr.sin_family=AF_INET;
sockaddr.sin_port=htons(atoi(port)); //16 bit port
//sockaddr.sin_addr.s_addr=inet_addr(ip);
inet_aton(ip,&sockaddr.sin_addr);
if(bind(sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr))<0){
perror("bind..\n");
exit(-2);
}
struct sockaddr_in addrtmp;
socklen_t Len;
getsockname(sock,(struct sockaddr*)&addrtmp,&Len);
printf("com:%d\n",ntohs(addrtmp.sin_port));
if(listen(sock,5)<0){
perror("listen...\n");
exit(-3);
}
return sock;
}
//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int main(int argc,char* argv[])
{
if(argc!=3){
printf("Usg: %s [ip] [port]\n",argv[0]);
return 3;
}
printf("hello servet..\n");
int id=fork();
if(id==0){
printf("this is child...%d\n",getpid());
printf("debug 1\n");
int listen_sock=StartUp(argv[1],argv[2]);
struct sockaddr_in addr;
socklen_t Len=0;
while(1){
int fd=accept(listen_sock,(struct sockaddr*)&addr,&Len);
printf("It's %s address send message\n",\
inet_ntoa(addr.sin_addr));
if(fd < 0){
perror("accept...\n");
return 2;
}
else if(fd==0){
printf("client is quit...\n");
}
char buf[1024];
while(1){
ssize_t _s=read(fd,buf,sizeof(buf));
if(_s==0){
printf("client is quit..\n");
break;
}
else if(_s<0){
perror("read...");
exit(1);
}
buf[_s-1]='\0';
printf("client #");
fflush(stdout);
printf("%s\n",buf);
}
}
}
else{
//father proc
wait(NULL);
}
return 0;
}
клиент
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <signal.h>
//int socket(int domain, int type, int protocol);
//int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
void SigpipeRun(int signo)
{
printf("this is in client SigpipeRun..,signo:%d\n",signo);
sleep(2);
}
int main(int argc,char* argv[])
{
signal(SIGPIPE,SigpipeRun);
if(argc!=3){
printf("Usegs: %s [ip] [port]",argv[1]);
return 3;
}
printf("Hello client\n");
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0){
perror("socket..");
exit(1);
}
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(atoi(argv[2]));
addr.sin_addr.s_addr=inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&addr,sizeof(addr))<0){
perror("connect...");
return 2;
}
printf("connect success...\n");
char buf[1024];
while(1){
ssize_t _s=read(0,buf,1024);
if(_s<0){
perror("read...\n");
exit(1);
}
buf[_s-1]='\0';
printf("client #");
fflush(stdout);
printf("%s\n",buf);
write(sock,buf,_s);
}
close(sock);
return 0;
}