1,accept
从listen 监听队列中接受一个连接。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int socked, struct socked *addr, sickle_t *addrlen);
sockfd参数是执行过listen 系统调用的监听socket。
addr参数用来获取被接受连接的远端socket 地址,该socket地址的长度由addrlen参数指出。
accept成功时返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可通过读写该socket来与被被接受连接对应的客户端通信。
accept失败时返回-1并设置error。
注;监听 socket 和连接 socket 的区别:
其实一个socket是一个五元组(协议,源IP,源端口,目的ip,目的端口)。
监听socket 是服务器用来监听新的连接,状态是 listen。如下图,服务器监听5678的端口。
连接socket表示服务器与客户端建立一次的连接,状态是 ESTABLISHED。如下图,client 连到服务器的5678 端口,accept返回的socket表示的就是这条连接。
2,如果监听队列中处于ESTABLISHED 状态的连接对应的客户端出现网络异常(比如掉线),或者提前退出,那么服务器对这个连接执行的accept调用是否成功???
3,模拟client提前退出,server 再accept的情况。
server在listen后,立马sleep 30秒,此过程中,client connect服务端,建立三次握手,client和server都进入established状态。client连接后,又立马close,并退出程序。此时server还在sleep中,未进入accept,刚好模拟了client在server accept前提前退出的情形。
client 程序:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <unistd.h>
int main()
{
int sockfd;
int ret;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(sockfd != -1);
struct sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(5678);
serAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = connect(sockfd, (struct sockaddr *)&serAddr, sizeof(serAddr));
if(ret < 0)
{
close(sockfd);
printf("connect error\n");
return -1;
}
else
{
printf("connetc succ\n");
}
sleep(5);
printf("client quit\n");
close(sockfd);
//shutdown(sockfd, SHUT_RDWR);
return 0;
}
服务端:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <unistd.h>
int main()
{
int listenFd, clientFd;
int ret;
listenFd = socket(AF_INET, SOCK_STREAM, 0);
assert(listenFd != -1);
struct sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(5678);
serAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = bind(listenFd, (struct sockaddr *)&serAddr, sizeof(serAddr));
assert(ret != -1);
ret = listen(listenFd, 5);
assert(ret != -1);
printf("listen...\n");
sleep(30);
printf("sleep over\n");
struct sockaddr_in cliAddr;
socklen_t len = sizeof(cliAddr);
clientFd = accept(listenFd, (struct sockaddr *)&cliAddr, &len);
if(clientFd < 0)
{
close(listenFd);
printf("accept error\n");
return -1;
}
else
{
printf("client [fd: %d] [ip: %s] [port: %d] connect\n", clientFd, inet_ntoa(cliAddr.sin_addr), ntohs(cliAddr.sin_port));
printf("pause before\n");
pause();
printf("pause after\n");
close(clientFd);
}
close(listenFd);
return 0;
}
A,启动服务器监听,紧接着服务器进入sleep 30秒。
B,启动客户端,连接服务器,client进入sleep,close前。此时,已经完成了三次握手,建立了连接。
wireshark 抓包也能看出来此过程,client发起了syn连接。
C,client sleep时间到了,5秒后,close掉socket,客户端随之退出。(此时服务器还在sleep中,还未accept)
此时,client的socket状态是FIN_WAIT_2,服务器的socket状态是 CLOSE_WAIT。是由于client close动作,向服务器单方面发起了fin请求,服务端回复了ack。wireshark抓包情况:
client打印:
D,当服务器的sleep 30秒到期后,进行accept,看下服务端此时的打印情况:
说明,accept成功返回了。再次查看服务端的状态:
由此可见,accept只是从监听队列中取出连接,而不论连接处于何种状态,如上面的CLOSE_WAIT状态。
E,ctrl+c强制退出服务端后,查看服务端的状态:
wireshark的抓包如下:
奇怪的是,此时client程序早已经退出了,ctrl+c服务端,server会发送fin请求,但client居然回复了ack,相应的状态也是TIME_WAIT???
查阅书籍发现,原来连接停留在FIN_WAIT_2状态的情况可能发生在:客户端执行半关闭后,未等服务器的关闭连接就强行退出了。(上面模拟情况,client connect建立连接后,server sleep 30秒后才accept,这期间client已经退出了,未等server最后的fin请求,这时客户端属于半关闭状态)
此时客户端连接由内核来接管,可称之为孤儿连接(和孤儿进程类似)。linux为了防止孤儿连接长时间存留在内核中,定义了两个内核变量:/proc/sys/net/ipv4/tcp_max_orphans和 /proc/sys/net/tcp_fin_timeout。前者指定内核能接管的孤儿连接数目,后者指定孤儿连接在内核中的生存时间。
如果,accept成功后,server 向该连接socket发送数据会怎么样呢???(即将上图中的ctrl+c操作变成send 数据)
wireshark抓包情况:
最后发现客户端回了一个rst复位报文段,终止了此次连接,只剩下listen socket。
上面蓝色对应的是client提前退出,服务端的状态(close_wait)。
红色,是当server accept后,继续send后的情况,此时client发送了rst,导致终止了此次连接。
4,模拟client 断开网络连接。
启动客户端程序后,立即断开该客户端的网络连接(连接和断开连接的过程要在服务器启动后的30秒内完成,此时server在sleep)。结果发现accept调用也能正常返回。
A,启动服务器,并相应启动客户端。
其实已经建立了连接,立马剥掉客户端的网线。
待服务器sleep时间到,并调用accept的时候,打印如下:表示accept正常返回。
如果此时服务器send数据会怎么样???
wireshar抓包情况:
由于网络是断开的,这包数据会一直重传,重传的过程中,服务器的状态是:
依然是建立完成态,如果不是send,而是close,表示发送的是fin,那此时应该是fin_wait_1,一直重传fin报文,未收到client的ack。当达到一定重传次数后,服务器发送rst报文,终止了此次连接。
而此时client的状态是:
如果在server重发data的过程中,client网络连接上了,会怎么办呢???
表明client连上后,在某次重传后收到了该数据,并进行啦ack回复。