listen()函数中backlog参数分析

背景知识

Unix网络编程描述如下:



总结

0. accept()函数不参与三次握手,而只负责从已建立连接队列中取出一个连接和sockfd进行绑定;
1. backlog参数决定了未完成队列和已完成队列中连接数目之和的最大值(从内核角度看,是否这个和就是等于sock->recv_queue ?);
2. accept()函数调用,会从已连接队列中取出一个“连接”(可以是一个描述连接的数据结构,listensocket->sock->recv_queue[sk_buff] ? ),未完成队列和已完成队列中连接数目      之和将减少1;即accept将监听套接字对应的sock的接收队列中的已建立连接的sk_buff取下(从该sk_buff中可以获得对端主机的发送过来的tcp/ip数据包)
3. 监听套接字的已完成队列中的元素个数大于0,那么该套接字是可读的。
4. 当程序调用accept的时候(设置阻塞参数),那么判定该套接字是否可读,不可读则进入睡眠,直至已完成队列中的元素个数大于0(监听套接字可读)而唤起监听进程。


实例分析1

将服务器端的listen函数backlog设置为2,用20个客户端与服务器建立连接,查看连接的建立情况。

服务器代码:

#include <stdio.h>
#include<unistd.h>
#include<sys/types.h>       /* basic system data types */
#include<sys/socket.h>      /* basic socket definitions */
#include<netinet/in.h>      /* sockaddr_in{} and other Internet defns */
#include<arpa/inet.h>       /* inet(3) functions */
#include<sys/epoll.h>       /* epoll function */
#include<fcntl.h>
#include<stdlib.h>
#include<errno.h>
#include<stdio.h>
#include<string.h>


int main(int argc,char*argv[])
{
    int listenfd,connfd;
    struct sockaddr_in cliaddr,servaddr;
    int queuelen=5;

    if(argc!=2){
        puts("usage# ./aworker listenqueuelen");
        exit(0);
    }   
    queuelen=atoi(argv[1]);

    listenfd = socket(AF_INET,SOCK_STREAM,0);

    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(2989);
    
    bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    
    listen(listenfd,queuelen);
    sleep(60); //将这个注释,会出现另一种情况哟~~  
    while(1)
    {
        connfd = accept(listenfd,NULL,0);
        if(connfd == -1)
        {
            perror("accept error");
            continue;
        }
        puts("new connection...");
    }
    return 0;
}

client代码

#include "client.h"

//void cli_hander(int sockfd,)

int main()
{
    int sockfd;
    int rc; 
    int cpid;
    struct sockaddr_in servaddr;
    
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);
    servaddr.sin_port = htons(2989);
    
    for(int i=0;i<20;i++)
    {   
        cpid = fork();
        if(cpid == 0)
        {   
            sockfd = socket(AF_INET,SOCK_STREAM,0);
            rc = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
            if(rc == -1) 
            {   
                perror("connect error");
                exit(0);
            }   
            printf("pid#%d connected...\n",getpid());
            sleep(3);
            close(sockfd);
            exit(0);
        }   
    }   

    while(1)
    {   
        cpid = wait(NULL);
        if(cpid==-1){
            perror("end of wait");
            break;
        }
        printf("pid#%d exit...\n",cpid);
    }
    return 0;
}

实验结果:

服务器端显示:

root@cloud2:~/slp/NetWrokProgram/server# ./aworker 2
new connection...
new connection...
new connection...
new connection...
new connection...

客户端显示:

root@cloud2:~/slp/NetWrokProgram/client# ./a.out 
pid#16697 connected...
pid#16699 connected...
pid#16698 connected...
pid#16697 exit...
pid#16699 exit...
pid#16698 exit...
pid#16700 connected...
pid#16701 connected...
pid#16700 exit...
pid#16701 exit...
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
connect error: Connection timed out
pid#16702 exit...
pid#16703 exit...
pid#16704 exit...
pid#16705 exit...
pid#16706 exit...
pid#16707 exit...
pid#16708 exit...
pid#16709 exit...
pid#16710 exit...
pid#16711 exit...
pid#16712 exit...
pid#16713 exit...
pid#16714 exit...
pid#16715 exit...
pid#16716 exit...
end of wait: No child processes

结果分析:

同时建立连接的客户端进程共有20个,可是只有5个完成了连接的建立,其他15个没有成功。有趣的是,建立的5个链接中有3个是马上建立的,2个是过了一段时间后后来才建立的。

实例分析2

将server端的代码中的sleep(60)注释,即服务端listen即开始进入while循环中的accept阻塞:

    ...
    listen(listenfd,queuelen);
    sleep(60); //将这个注释,会出现另一种情况哟~~  
    while(1)
    {
        connfd = accept(listenfd,NULL,0);
        ....

同样的运行,结果如下:

root@cloud2:~/slp/NetWrokProgram/server# ./aworker 2
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...
new connection...

客户端:
root@cloud2:~/slp/NetWrokProgram/client# ./a.out 
pid#16736 connected...
pid#16737 connected...
pid#16738 connected...
pid#16739 connected...
pid#16740 connected...
pid#16741 connected...
pid#16742 connected...
pid#16743 connected...
pid#16744 connected...
pid#16745 connected...
pid#16746 connected...
pid#16747 connected...
pid#16748 connected...
pid#16749 connected...
pid#16750 connected...
pid#16751 connected...
pid#16752 connected...
pid#16753 connected...
pid#16755 connected...
pid#16754 connected...
pid#16736 exit...
pid#16737 exit...
pid#16738 exit...
pid#16739 exit...
pid#16740 exit...
pid#16741 exit...
pid#16742 exit...
pid#16743 exit...
pid#16744 exit...
pid#16745 exit...
pid#16746 exit...
pid#16747 exit...
pid#16748 exit...
pid#16749 exit...
pid#16750 exit...
pid#16751 exit...
pid#16752 exit...
pid#16753 exit...
pid#16755 exit...
pid#16754 exit...
end of wait: No child processes

结果分析:
由于每个连接在建立之后,已完成队列中的连接马上就被accept给读取了,所以已完成和未完成队列中的连接数之和根本不可能超过backlog限定的个数。

原文链接:
http://blog.csdn.net/ordeder/article/details/21551567

  • 9
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
实验(No. 4)题目:简单的客户/服务器程序设计与实现 实验目的及要求: 1、熟悉Microsoft Visual Studio 2008编程环境。 2、了解TCP与UDP协议,以及它们之间的区别。 3、了解客户/服务器模型原理。 4、熟悉Socket编程原理,掌握简单的套接字编程。 实验设备: 硬件:PC机(两台以上)、网卡、已经设定好的以太网环境 软件:Microsoft Visual Studio 2008 实验内容及步骤: 1、编写用TCP协议实现的Client端和Server端程序并调试通过。 程序分两部分:客户程序和服务器程序。 工作过程是: 服务器首先启动,它创建套接字之后等待客户的连接;客户启动后创建套接字,然后 和服务器建立连接;建立连接后,客户接收键盘输入,然后将数据发送到服务器,服 务器收到到数据后,将接收到的字符在屏幕上显示出来。或者服务器接收键盘输入, 然后将数据发送到客户机,客户机收到数据后,将接收到的字符在屏幕上显示出来。 程序流程如下: 2、编写用UDP协议实现的Client端和Server端程序并调试通过(做完第一个实验的基础 上做该实验)。 3、编写用TCP协议实现Client端与Server端的一段对话程序。Server端根据用户的输入 来提示Client端下一步将要进行操作。 所用函数及结构体参考: 1、创建套接字——socket() 功能:使用前创建一个新的套接字 格式:SOCKET PASCAL FAR socket(int af, int type, int procotol); 参数:af:代表网络地址族,目前只有一种取值是有效的,即AF_INET,代表internet地 址族; Type:代表网络协议类型,SOCK_DGRAM代表UDP协议,SOCK_STREAM代表TCP协议; Protocol:指定网络地址族的特殊协议,目前无用,赋值0即可。 返回值为SOCKET,若返回INVALID_SOCKET则失败。 2、指定本地地址——bind() 功能:将套接字地址与所创建的套接字号联系起来。 格式:int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen); 参数:s: 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。 其它:没有错误,bind()返回0,否则SOCKET_ERROR 地址结构说明: struct sockaddr_in { short sin_family;//AF_INET u_short sin_port;//16位端口号,网络字节顺序 struct in_addr sin_addr;//32位IP地址,网络字节顺序 char sin_zero[8];//保留 } 3、建立套接字连接——connect()和accept() 功能:共同完成连接工作 格式:int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen); SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR * name, int FAR * addrlen); 参数:s: 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。 4、监听连接——listen() 功能:用于面向连接服务器,表明它愿意接收连接。 格式:int PASCAL FAR listen(SOCKET s, int backlog); 5、数据传输——send()与recv() 功能:数据的发送与接收 格式:int PASCAL FAR send(SOCKET s, const char FAR* buf, int len, int flags); int PASCAL FAR recv(SOCKET s, const char FAR * buf, int len, int flags); 参数:buf:指向存有传输数据的缓冲区的指针。 6、多路复用——select() 功能:用来检测一个或多个套接字状态。 格式:int PASCAL FAR select(int nfds, fd_set FAR* readfds, fd_set FAR* writefds, fd_set FAR * exceptfds, const struct timeval FAR* timeout); 参数:readfds:指向要做读检测的指针 writefds:指向要做写检测的指针 exceptfds:指向要检测是否出错的指针 timeout:最大等待时间 7、

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值