Unix网络编程(二)——bio

目录

首先从最简单的BIO开始,参考了csdn上一系列不错的blog,上来先看看代码

/* A simple server in the internet domain using TCP
   The port number is passed as an argument */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>

void error(const char *msg)
{
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[])
{
     if (argc < 2) {
         fprintf(stderr,"ERROR, no port provided\n");
         exit(1);
     }
     int sockfd, newsockfd, portno;
     socklen_t clilen;
     char buffer[256];
     struct sockaddr_in serv_addr, cli_addr;
    
     //1. create socket
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0) 
        error("ERROR opening socket");

     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = atoi(argv[1]);
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     // 2. 绑定端口
     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0) 
              error("ERROR on binding");
     // 3. 开始监听
     listen(sockfd,1);	 
     clilen = sizeof(cli_addr);
     while(1){
	//sleep(10000);
     // 4. 阻塞在这里,直到有连接进来
     newsockfd = accept(sockfd, 
                 (struct sockaddr *) &cli_addr, 
                 &clilen);
     
     if (newsockfd < 0) 
          error("ERROR on accept");
     bzero(buffer,256);
     int n;
     while((n=read(newsockfd,buffer,255))>0){
        printf("get data:%s",buffer);
        if(write(newsockfd,buffer,n) != n){
            error("ERROR write");
        }
     }
     close(newsockfd);
     }
     close(sockfd);
     return 0; 
}

里面大部分的代码都比较简单易懂,这里主要是listen(2)的第二个参数不太容易理解。比如虽然设置了backlog的值为1,但是还是可以同时accept多个连接,这是为什么呢?查到了有人介绍说:
The behaviour of the backlog parameter on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using the tcp_max_syn_backlog sysctl. When syncookies are enabled there is no logical maximum length and this sysctl setting is ignored. See tcp(7) for more information.

可以看到这里backlog限定了已经完成握手成为ESTABLISHED状态等待accept的连接队列长度,但是这个队列的长度是多少,好像还真的看内核的代码是怎么写的。比如这里有linux 2.6.33内核中request_sock.c的源码,其中涉及队列长度计算部分的代码如下:

 int sysctl_max_syn_backlog = 256;
 int reqsk_queue_alloc(struct request_sock_queue *queue, unsigned int nr_table_entries) {
         size_t lopt_size = sizeof(struct listen_sock);
         struct listen_sock *lopt;
 
         nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
         nr_table_entries = max_t(u32, nr_table_entries, 8);
         nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
         lopt_size += nr_table_entries * sizeof(struct request_sock *);
         if (lopt_size > PAGE_SIZE)
                 lopt = __vmalloc(lopt_size,
                         GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
                         PAGE_KERNEL);
         else
                 lopt = kzalloc(lopt_size, GFP_KERNEL);
         if (lopt == NULL)
                 return -ENOMEM;
 
         for (lopt->max_qlen_log = 3;
              (1 << lopt->max_qlen_log) < nr_table_entries;
              lopt->max_qlen_log++);
 
         get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
         rwlock_init(&queue->syn_wait_lock);
         queue->rskq_accept_head = NULL;
         lopt->nr_table_entries = nr_table_entries;
 
         write_lock_bh(&queue->syn_wait_lock);
         queue->listen_opt = lopt;
         write_unlock_bh(&queue->syn_wait_lock);
 
         return 0;
 }

可以看到,内核中计算的方式是预先定义了一个maxvalue,取调用时传入值和他比较,采用较小的一个;然后最小值不能小于8;最后是用一个>=backlog+1 的2的指数。在我们的CentOS6.5测试机(2.6.32-279.11.1.el6.x86_64)上,我做了个试验,设置backlog值为5,按照上面源码的计算,队列的长度应该是8。但是最后测试结果却发现等待accept的队列值是6,对于超出这个数值的连接,还是会进行三次握手,如果客户端向server发出[psh ack],就会收到server发来RST信号断开连接(Telnet协议)。也就是说我使用内核版本貌似执行了backlog+1并没有使用>=这个值2的指数,真是各种不靠谱啊……

参考:

  1. linux man