TCP 网络编程API - listen()、accept()

目录

listen()

listen() 函数介绍

listen() 函数的例子

 应用层 listen()函数和内核函数之间的关系

accept ()

accept ()函数介绍

accept ()函数的例子

应用层 accept ()函数和内核函数之间的关系


listen()

服务器模式中有 listen() 和 accept() 两个函数,而客户端则不需要这两个函数。
函数 listen() 用来初始化服务器可连接队列,服务器处理客户端连接请求的时候是顺序处理的,同一时间仅能处理一个客户端连接。当多个客户端的连接请求同时到来的时候,服务器并不是同时处理,而是将不能处理的客户端连接请求放到等待队列中,这个队列的长度由 listen() 函数来定义。

listen() 函数介绍

listen() 函数的原型如下,其中的 backlog 表示等待队列的长度。

# include 
int listen(int sockfd, int backlog);

当 listen() 函数成功运行时,返回值为0;当运行失败时,它的返回值为﹣1,并且设置 errno 值,错误代码的含义如下所示:

TCP 网络编程API - listen()、accept()_第1张图片

在接受一个连接之前,需要用 listen() 函数来侦听端口,listen() 函数中参数 backlog 的参数表示在 accept() 函数处理之前在等待队列中的客户端的长度,如果超过这个长度,客户端会返回一个 ECONNREFUSED 错误。

listen() 函数仅对类型为 SOCK_STREAM 或者 SOCK_SEQPACKET 的协议有效,例如,如果对一个 SOCK_DGRAM 的协议使用函数 listen(),将会出现错误 errno 应该为值 EOPNOTSUPP,表示此 socket 不支持函数 listen() 操作。大多数系统的设置为20,可以将其设置修改为5或者10,根据系统可承受负载或者应用程序的需求来确定。

listen() 函数的例子

下面是一个 listen() 函数的实例代码,在成功进行 socket() 函数初始化和 bind() 函数端口之后,设置 listen() 函数队列的长度为5。

# define MYPORT 3490 //端口地址
int main (int argc, char *argv[])
{
	int sockfd;//套接字文件描述符变量
	struct sockaddr_in my_addr ;//以太网套接字地址结构
	sockfd = socket(AF_NET, SOCK_STREAM, 0);//初始化 socket 
  	if(sockfd == -1)//检查是否正常初始化 socket 
  	{
		perror (" socket ");
		exit ( EXIT_FAILURE );
	}

	my_addr.sin_family = AF_INET ;//地址结构的协议族
	my_addr.sin_port = htons(MYPORT);//地址结构的端口地址,网络字节序
	my_addr.sin_addr.s_addr = inet_addr ("192.168.1.150");// IP ,将字符串的 IP 地址转化为网络字节序

	bzero(my_addr.sin_zero, sizeof(my_addr.sin_zero));//将 my_addr.sin_zero 置0
	if(bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) ==- 1)//判断是否绑定成功
	{
		perror (" bind ");//打印错误信息
		exit ( EXIT _ FAILURE );//退出程序
	}

	if (listen(sockfd, 5) == -1)//判断是否1isten成功
	{
		perror ("1isten");//打印错误信息
		exit ( EXIT_FAILURE );//退出程序
	}
	//接收数据、发送数据和数据的处理过程
	//关闭套接字*/

	 close ( sockfd );
}

 应用层 listen()函数和内核函数之间的关系

应用层 listen() 函数和内核层 listen()函数的关系如图所示:

TCP 网络编程API - listen()、accept()_第2张图片

应用层的 listen()函数对应于系统调用 sys_listen()函数。 sys_listen()函数首先调用 sockfd_lookup_light() 函数获得 sockfd 对应的内核结构 struct socket,查看用户的 backlog 设置值是否过大,如果过大则设置为系统默认最大设置。然后调用抽象的 listen()函数,这里指的是 AF_INET 的 listen()函数和 inet_listen()函数。

inet_listen()函数首先判断是否合法的协议族和协议类型,再更新 socket 的状态值为TCPLISTEN ,然后为客户端的等待队列申请空间并设定侦听端口。

accept ()

当一个客户端的连接请求到达服务器主机侦听的端口时,此时客户端的连接会在队列中等待,直到使用服务器处理接收请求。

函数 accept 成功执行后,会返回一个新的套接字文件描述符来表示客户端的连接,客户端连接的信息可以通过这个新描述符来获得。因此当服务器成功处理客户端的请求连接后,会有两个文件描述符,老的文件描述符表示正在监听的 socket ,新产生的文件描述符表示客户端的连接,函数 send() 和 recv() 通过新的文件描述符进行数据收发。

accept ()函数介绍

accept 函数的原型如下

# include 
# include 
 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

通过 accept 函数可以得到成功连接客户端的 IP 地址、端口和协议族等信息,这个信息是通过参数 addr 获得的。当 accept 函数返回的时候,会将客户端的信息存储在参数 addr 中。参数 addrlen 表示第2个参数( addr )所指内容的长度,可以使用 sizeof ( struct sockaddr_in )来获得。需要注意的是,在 accept 中 addrlen 参数是一个指针而不是结构, accept 函数将这个指针传给 TCP / IP 协议栈。

accpet() 函数的返回值是新连接的客户端套接字文件描述符,与客户端之间的通信是通过 accept()函数返回的新套接字文件描述符来进行的,而不是通过建立套接字时的文件描述符,这是在程序设计的时候需要注意的地方。如果 accept() 函数发生错误, accept()函数会返回 -1。通过 errno 可以得到错误值,含义在下表中进行介绍。

TCP 网络编程API - listen()、accept()_第3张图片

accept ()函数的例子

下面是一个简单的使用 accept() 函数的例子。这个例子先建立一个流式套接字,然后对套接字进行地址绑定,当绑定成功后,初始化侦听队列的长度,然后等待客户端的连接请求。

# define MYPORT 3490 //端口地址
int main (int argc, char *argv[])
{
	int sockfd;//套接字文件描述符变量
	struct sockaddr_in my_addr ;//以太网套接字地址结构
	sockfd = socket(AF_NET, SOCK_STREAM, 0);//初始化 socket 
  	if(sockfd == -1)//检查是否正常初始化 socket 
  	{
		perror (" socket ");
		exit ( EXIT_FAILURE );
	}

	my_addr.sin_family = AF_INET ;//地址结构的协议族
	my_addr.sin_port = htons(MYPORT);//地址结构的端口地址,网络字节序
	my_addr.sin_addr.s_addr = INADDR_ANY; // 自动IP地址获取,表示任意的本地IP地址

	bzero(my_addr.sin_zero, sizeof(my_addr.sin_zero));//将 my_addr.sin_zero 置0
	
	//绑定
	if(bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) ==- 1)//判断是否绑定成功
	{
		perror (" bind ");//打印错误信息
		exit ( EXIT _ FAILURE );//退出程序
	}

	//监听
	if (listen(sockfd, 5) == -1)//判断是否1isten成功
	{
		perror ("1isten");//打印错误信息
		exit ( EXIT_FAILURE );//退出程序
	}
	
	struct sockaddr_in client_addr; //连接的客户端地址
	socklen_t addr_length = sizeof(struct sockaddr_in); //地址长度
	int client_fd = accept(sockfd, &client_addr, &addr_length);
	if(-1 == addr_length)
	{
		perror ("accept");//打印错误信息
		exit ( EXIT_FAILURE );//退出程序
	}
 
	//接收数据、发送数据和数据的处理过程
	//关闭套接字*/

	close ( client_fd );

	close ( sockfd );
}

应用层 accept ()函数和内核函数之间的关系

应用层的 accept()函数和内核层的 accept 函数的关系如图所示:

TCP 网络编程API - listen()、accept()_第4张图片

应用层的 accept()函数对应内核层的 sys_accept()函数系统调用函数。函数 sys_accept()查找文件描述符对应的内核 socket 结构、申请一个用于保存客户端连接的新的内核 socket 结构、获得客户端的地址信息、将连接的客户端地址信息复制到应用层的用户、返回连接客户端 socket 对应的文件描述符。

函数 sys_accept()调用函数 sockfd_lookup_light() 查找到文件描述符对应的内核 socket 结构后,然后会申请一块内存用于保存连接成功的客户端的状态。 socket 结构的一些参数,如类型 type 、操作方式 ops 等会继承服务器原来的值,假如原来服务器的类型为 AF_INET ,则其操作模式仍然是 af_inet.c 文件中的各个函数。然后会查找文件描述符表,获得一个新结构对应的文件描述符。
accept()函数的实际调用根据协议族的不同而不同,即函数指针 sock->ops->accept 要由socket()函数初始化时的协议族而确定。当为 AF_INET 时,此函数指针对应于 af_inet.c 文件中的 inet_accept() 函数。

当客户端连接成功后,内核准备连接的客户端的相关信息,包含客户端的 IP 地址、客户端的端口等信息,协议族的值继承原服务器的值。在成功获得信息之后会调用 move_addr_to_user()函数将信息复制到应用层空间,具体的地址由用户传入的参数来确定。

你可能感兴趣的