listen()第二个参数的意义
- listen()的定义
- 客户端与服务器的连接
- listen的第二个参数
- 实践演示
1.listen的定义
在Linux
下listen()
的定义是这样的
#include <sys/type.h>
#include <sys/socket.h>
int listen(int fd , int backlog)
listen()
一般用在bind()
之后accept()
之前,其中第一个参数是通过scoket()
函数获得的套接字编号,第二个参数backlog
是他所能监听的最大套接字数量;
2.客户端与服务器的连接
2.1TCP的三次握手
在客户端和服务器连接时会进行三次握手当进行了三次握手以后客户端和服务器才开始传送数据。
2.2三次握手的具体实现
第一次:建立连接时,客户端发送SYN包到服务器,并进入SYN_SENT状态,等待服务器确认;
第二次:服务器收到SYN包,确认收到后,向客户端发送一个SYN+ACK包,
此时服务器进入SYN_RECV状态;
第三次:客户端收到服务端的SYN+ACK包,向服务器发送ACK包,此包发送完毕,客户端和服务器进入ESTABLISHED状态,三次握手结束;
完成三次握手以后客户端与服务器开始传送数据
2.3两个队列
在服务器里面有两个队列,一个SYN队列,还有一个是ACCEPT队列,他们的大小可以通过下面两条命令:
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
cat /proc/sys/net/core/somaxconn
当服务器收到SYN包以后回复SYN+ACK后将收到的套接字放入SYN队列中,
收到最后客户端发来的ACK包后将SYN 队列中的东西拿出来放到ACCEPT队列中去;
服务器通过accept()
函数从ACCEPT队列中取出客户端数据并返回一个新的套接字编号;
2.4客户端和服务器的连接的具体实现
上图是客户端和服务器连接传送数据的一个模型;
客户端在connect 时阻塞当三次握手结束后connect返回,所以客户端向服务器connect并不需要服务器accept只需要完成三次握手在后面的实践演练中会证明;
2.5为什么一定是三次握手
为什么不是两次握手,服务器接收到客户端发来的SYN包,然后回话确定服务器和客户端已经连上了;
因为在计算机内部,每一次传送数据走的线路可能是不一样的,就像两个同学上课传纸条一样,小明给小红传可能给左手边的人送左边传过去,而小红给小明的回信可能是给了右手边的同学;
第一次是服务器确定可以收到客户端的消息,第二次是客户端确定可以收到服务器的消息,最后一次是服务器确定客户端可以收到自己发送的消息;
listen的第二个参数
listen的第二个参数backlog,backlog的含义众说纷纭,其中有以下说法:
1.SYN队列的大小;
2.ACCEPT队列的大小;
3.是SYN+ACCEPT的大小;
具体的我觉得要用实践验证;
实践验证
具体验证有1.connect在三次握手后就会返回;2.backlog的具体含义:
我写了一个简单的客户端与服务器连接的代码来验证上面两个疑问
//服务器代码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <fcntl.h>
int main()
{
int sock_fd;
struct sockaddr_in seve,cli;
sock_fd = socket(AF_INET,SOCK_STREAM,0);
memset(&seve,0,sizeof(struct sockaddr_in));
seve.sin_family = AF_INET;
seve.sin_port = htons(4555);
seve.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sock_fd,(struct sockaddr*)&seve,sizeof(struct sockaddr_in));
if(listen(sock_fd,5)<0)
perror("listen");
int len = sizeof(struct sockaddr_in);
sleep(30);
printf("连接一个\n");
int newfd = accept(sock_fd,(struct sockaddr*)&cli,&len);
sleep(30);
printf("连接一个\n");
newfd = accept(sock_fd,(struct sockaddr*)&cli,&len);
sleep(30);
printf("连接一个\n");
newfd = accept(sock_fd,(struct sockaddr*)&cli,&len);
while(1)
{
}
}
//客户端代码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <fcntl.h>
int main(int argc , char *argv[])
{
int sock_fd , ret;
int haha;
struct sockaddr_in seve;
sock_fd = socket(AF_INET,SOCK_STREAM,0);
seve.sin_family = AF_INET;
seve.sin_port = htons(4555);
inet_aton("10.101.14.250",&seve.sin_addr); //所连接的局域网的IP
if(connect(sock_fd,(struct sockaddr*)&seve,sizeof(struct sockaddr_in)) < 0)
perror("connect");
printf("连接成功\n");
while(1)
{
}
}
以上就是我用来测试的两个代码,通过在root下执行
watch -n 1 -d 'netstat -natp | grep "4555"'
来监控4555端口的情况(此数据是在一台电脑下开多个终端测试)
首先我们先来讲解一下上面那条命令出来的东西是啥;
0.0.0.0:4555 0.0.0.0:*
目标IP:目标端口<---->源IP:源端口
因为是一个电脑开多个终端所以每一次执行客户端代码都会显示两行
这是最开始将打开了多个终端去连接服务器,此时正在sleep,我们可以看到ACCEPT队列里有6个内容,而我们的backlog值为5;所以我们可以得出结论backlog的值是规定ACCEPT队列大小的;
当ACCEPT队列满了时后来的客户端,就会被放到SYN队列中;