`
diecui1202
  • 浏览: 98698 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Linux内核网络协议栈8—socket监听

阅读更多

几个问题
了解以下几个问题的同学可以直接忽略下文:

1、listen 库函数主要做了什么?
2、 什么是最大并发连接请求数?
3、什么是等待连接队列?

socket 监听相对还是比较简单的,先看下应用程序代码:

listen( server_sockfd, 5) ; 
 

其中,第一个参数 server_sockfd为服务端 socket所对应的文件描述符,第二个参数5 代表监听socket 能处理的最大并发连接请求数,在2.6.26 内核中,该值为 256

listen 库函数调用的主要工作可以分为以下几步:
1 、根据 socket文件描述符找到内核中对应的 socket结构体变量;这个过程在《socket地址绑定 一文中描述过,这里不再重述;
2 、设置 socket的状态并初始化等待连接队列;
3 、将 socket放入listen 哈希表中;

listen 调用代码跟踪
下面是 listen库函数对应的内核处理函数:

    asmlinkage long sys_listen(int fd, int backlog)
    {
       struct socket *sock;
       int err, fput_needed;
       int somaxconn;
       // 根据文件描述符取得内核中的socket
       sock = sockfd_lookup_light(fd, &err, &fput_needed);
       if (sock) {
           // 根据系统中的设置调整参数backlog
           somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
           if ((unsigned)backlog > somaxconn)
               backlog = somaxconn;
           err = security_socket_listen(sock, backlog);
           // 调用相应协议簇的listen函数
           if (!err)
               err = sock->ops->listen(sock, backlog);
           fput_light(sock->file, fput_needed);
       }
       return err;
    }
 

根据《创建socket 一文的介绍,例子中,这里sock->ops-> listen(sock, backlog) 实际上调用的是 net/ipv4/Af_inet.c:inet_listen() 函数:

    int inet_listen(struct socket *sock, int backlog)
    {
       struct sock *sk = sock->sk;
       unsigned char old_state;
       int err;
       lock_sock(sk);
       err = -EINVAL;
       // 1 这里首先检查socket的状态和类型,如果状态或类型不正确,返回出错信息
       if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
           goto out;
       old_state = sk->sk_state;
       // 2 这里检查sock的状态是否是TCP_CLOSE或TCP_LISTEN,如果不是,返回出错信息
       if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
           goto out;
       /* Really, if the socket is already in listen state
        * we can only allow the backlog to be adjusted.
        */
       // 3 当sock的状态不是TCP_LISTEN时,做监听相关的初始化
       if (old_state != TCP_LISTEN) {
           err = inet_csk_listen_start(sk, backlog);
       if (err)
           goto out;
       }
       // 4 设置sock的最大并发连接请求数
       sk->sk_max_ack_backlog = backlog;
       err = 0;
    out:
       release_sock(sk);
       return err;
    }
 

上面的代码中,有点值得注意的是,当 sock 状态已经是 TCP_LISTEN 时,也可以继续调用 listen() 库函数,其作用是设置 sock 的最大并发连接请求数;
下面看看 inet_csk_listen_start() 函数:

int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)
{
  struct inet_sock *inet = inet_sk(sk);
  struct inet_connection_sock *icsk = inet_csk(sk);
  // 初始化连接等待队列
  int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);
  if (rc != 0)
      return rc;
  sk->sk_max_ack_backlog = 0;
  sk->sk_ack_backlog = 0;
  inet_csk_delack_init(sk);
  // 设置sock的状态为TCP_LISTEN
  sk->sk_state = TCP_LISTEN;
  if (!sk->sk_prot->get_port(sk, inet->num)) {
      inet->sport = htons(inet->num);
      sk_dst_reset(sk);
      sk->sk_prot->hash(sk);
      return 0;
  }
  sk->sk_state = TCP_CLOSE;
  __reqsk_queue_destroy(&icsk->icsk_accept_queue);
  return -EADDRINUSE;
}
 

这里 nr_table_entries 是参数 backlog 经过最大值调整后的值;

相关数据结构
先看下接下来的代码中提到了几个数据结构,一起来看一下:

1 request_sock

    struct request_sock {
           struct request_sock *dl_next; /* Must be first */
           u16 mss;
           u8 retrans;
           u8 cookie_ts; /* syncookie: encode tcpopts in timestamp */
           /* The following two fields can be easily recomputed I think -AK */
           u32 window_clamp; /* window clamp at creation time */
           u32 rcv_wnd; /* rcv_wnd offered first time */
           u32 ts_recent;
           unsigned long expires;
           const struct request_sock_ops *rsk_ops;
           struct sock *sk;
           u32 secid;
           u32 peer_secid;
    };
 

socket 在侦听的时候,那些来自其它主机的 tcp socket 的连接请求一旦被接受(完成三次握手协议),便会建立一个 request_sock ,建立与请求 socket 之间的一个 tcp 连接。该 request_sock 会被放在一个先进先出的队列中,等待 accept 系统调用的处理;

2 listen_sock

    struct listen_sock {
           u8 max_qlen_log;
           /* 3 bytes hole, try to use */
           int qlen;
           int qlen_young;
           int clock_hand;
           u32 hash_rnd;
           u32 nr_table_entries;
           struct request_sock *syn_table[0];
    };
 

新建立的 request_sock 就存放在 syn_table 中;这是一个哈希数组,总共有 nr_table_entries 项;

成员 max_qlen_log 2 的对数的形式表示 request_sock 队列的最大值;

qlen 是队列的当前长度;

hash_rnd 是一个随机数,计算哈希值用;

3 request_sock_queue

    struct request_sock_queue {
           struct request_sock *rskq_accept_head;
           struct request_sock *rskq_accept_tail;
           rwlock_t syn_wait_lock;
           u16 rskq_defer_accept;
           /* 2 bytes hole, try to pack */
           struct listen_sock *listen_opt;
    };
 

结构体 struct request_sock_queue 中的 rskq_accept_head rskq_accept_tail 分别指向 request_sock 队列的队列头和队列尾;

 

等待连接队列初始化

先看下 reqsk_queue_alloc() 的源代码:

    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;
       // 1 控制nr_table_entries在8~ sysctl_max_syn_backlog之间
       nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
       nr_table_entries = max_t(u32, nr_table_entries, 8);
       // 2 向上取2的幂
       nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
       // 3 申请等待队列空间
       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;
       // 4 设置listen_sock的成员max_qlen_log最小为3,最大为nr_table_entries的对数
       for (lopt->max_qlen_log = 3;
            (1 << lopt->max_qlen_log) < nr_table_entries;
            lopt->max_qlen_log++);
     
       // 5 相关字段赋值
       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;
    }
 

整个过程中,先计算 request_sock 的大小并申请空间,然后初始化 request_sock_queue 的相应成员的值;

 

TCP_LISTEN socket 管理

在《端口管理》一文中提到管理 socket 的哈希表结构 inet_hashinfo ,其中的成员 listening_hash[INET_LHTABLE_SIZE] 用于存放处于 TCP_LISTEN 状态的 sock

socket 通过 listen() 调用完成等待连接队列的初始化后,需要将当前 sock 放到该结构体中:

    if (!sk->sk_prot->get_port(sk, inet->num)) {
      // 这里再次判断端口是否被占用
      inet->sport = htons(inet->num);
      sk_dst_reset(sk);
      // 将当前socket哈希到inet_hashinfo中
      sk->sk_prot->hash(sk);
      return 0;
    }
 

这里调用了 net/ipv4/Inet_hashtables.c:inet_hash() 方法:

    void inet_hash(struct sock *sk)
    {
           if (sk->sk_state != TCP_CLOSE) {
                  local_bh_disable();
                  __inet_hash(sk);
                  local_bh_enable();
           }
    }
    static void __inet_hash(struct sock *sk)
    {
           // 取得inet_hashinfo结构
           struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
           struct hlist_head *list;
           rwlock_t *lock;
           // 状态检查
           if (sk->sk_state != TCP_LISTEN) {
                  __inet_hash_nolisten(sk);
                  return;
           }
           BUG_TRAP(sk_unhashed(sk));
           // 计算hash值,取得链表
           list = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];
           lock = &hashinfo->lhash_lock;
     
           inet_listen_wlock(hashinfo);
           // 将sock添加到链表中
           __sk_add_node(sk, list);
           sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
           write_unlock(lock);
           wake_up(&hashinfo->lhash_wait);
    }
 

了解到这里,回答文初提出的 3 个问题,应该没什么问题了吧 :)

分享到:
评论

相关推荐

    linux内核协议栈源码分析

    Linux内核协议栈是操作系统核心的一部分,负责...通过深入研究Linux内核协议栈源码,不仅可以提升对网络协议的理解,还能为开发高效、稳定的网络应用提供理论支持。同时,这也有助于解决网络问题,提高系统的整体性能。

    linux内核中sock和socket数据结构

    在Linux内核中,sock和socket数据结构的实现不仅关系到网络协议栈的运作,也关系到文件系统和虚拟文件系统。在内核中,socket被注册为一种特殊的文件类型,因此可以通过标准的文件操作接口来操作套接字。这种设计...

    基于Linux网络协议栈实现及应用.pdf

    《基于Linux网络协议栈实现及应用》这篇文献主要探讨了Linux操作系统中的TCP/IP协议栈的实现细节和网络编程模型。Linux以其强大的网络服务功能著称,支持多种网络协议,并能提供多种网络服务,如TCP/IP、IPX/SPX、...

    linux TCP IP协议栈源码解析

    - `net/`目录下包含所有网络协议栈的源代码,如`tcp.c`处理TCP协议,`ipv4/`处理IPv4。 - `socket`层是用户空间与内核空间通信的接口,通过系统调用如`socket()`、`bind()`、`connect()`等实现。 - `sk_buff`...

    Linux网络体系结构:Linux内核中网络协议的设计与实现(english)

    1. **Linux内核网络模型**: - Linux网络模型分为五层:数据链路层、网络层、传输层、会话层和应用层,这与OSI七层模型相对应。 - 内核网络子系统的核心是网络协议栈,它负责数据包的接收、处理和发送。 2. **...

    Linux TCP_IP协议栈的设计及实现特点.pdf

    3. **抽象数据结构**:在Linux协议栈的实现中,每一层都定义了一个抽象数据结构,如sk_buff(socket buffer),用于在不同协议层之间传递数据,提供统一的接口。这种设计使得上层协议可以透明地处理下层协议的具体...

    linux socket详细分析

    Linux Socket是操作系统提供的网络通信接口,它允许应用程序通过网络协议(如TCP/IP)进行通信。本文将深入探讨Linux Socket的工作原理,主要包括以下几个方面:socket发送数据的函数流程、sys_socket流程、以及...

    linux socket 实现原理

    这些API是用户程序与内核网络协议栈之间的桥梁,允许数据的发送和接收,以及对网络连接的管理等。 Linux内核通过统一的系统调用接口来处理所有与socket相关操作。在内核版本2.6.12中,系统调用的入口函数是`sys_...

    linux socket基础文档

    在设计模式中,Socket被视为门面模式的一个实例,它隐藏了底层复杂的网络协议细节,简化了网络编程过程。 2. **Socket模型**:Socket可以视为一种特殊的文件。在Unix/Linux哲学中,“一切皆文件”,这意味着Socket...

    Linux网络socket核心技术

    socket可以支持多种协议栈,如TCP/IP协议栈,使得不同主机间的进程能够通过网络进行通信。 #### 二、创建socket 创建socket是网络编程的第一步,主要通过`socket()`函数完成: ```c #include &lt;sys/socket.h&gt; int...

    Linux下Socket编程

    Linux内核框架在"Linux内核框架.doc"中可能描绘了Socket接口如何与内核其他部分交互,包括网络协议栈。网络协议栈负责将应用层的数据拆分成数据包,加上适当的头部信息,并通过网络接口发送出去。同样,它也处理接收...

    linux协议栈源码阅读笔记

    首先,Linux内核中的IPv4网络部分可以分为几个层次: 1. **BSD Socket层**:这是用户空间与内核交互的接口,主要处理BSD socket相关的操作,如创建、绑定、连接、监听、接受和发送等。在内核中,每个socket以`...

    linux tcpip协议栈.doc

    首先,我们来看Linux内核网络栈的分层结构: 1. BSD Socket层:这是用户空间应用程序与内核网络接口交互的起点。`struct socket`结构体在内核中代表了一个socket。主要处理如socket创建、绑定、监听、接受等操作,...

    linux socketCAN.7z

    在Linux系统中,SocketCAN提供了类似TCP/IP协议栈的socket接口,允许开发者使用标准的socket编程模型来处理CAN网络。通过这个接口,你可以创建、绑定、连接、监听和发送CAN报文,就像处理TCP或UDP套接字一样。 1. *...

    SYN Cookie原理及在Linux内核中的实现

    Linux内核在TCP监听socket(LISTEN状态)中维护了一个open_request数组,用于存储半开连接信息。当达到最大连接数时,新的SYN请求会替换旧的请求,但SYN Cookie机制确保了合法连接不会因资源耗尽而丢失。 Linux内核...

    linux-socket-file-transfer.rar_arm linux socket_linux arm pc soc

    1. **Socket概念**:Socket是进程间通信的一种形式,它允许两个网络应用程序通过TCP/IP协议栈进行通信。在Linux系统中,Socket接口被封装在`&lt;sys/socket.h&gt;`头文件中。 2. **ARM Linux**:ARM是一种广泛应用于...

    linux网络开发知识

    Linux内核中的Socket接口是应用程序与网络协议栈之间的桥梁。它提供了一种标准的方式,让程序员可以编写网络通信程序。通过`socket()`、`bind()`、`listen()`、`accept()`、`connect()`、`send()`和`recv()`等系统...

    深入理解linux内核第三版 中文高清版

    - **TCP/IP协议栈**:Linux内核如何实现TCP/IP协议,包括网络层的IP协议、传输层的TCP和UDP协议。 - **套接字接口**:如何使用socket API进行网络通信,包括连接、监听、发送和接收数据。 - **网络设备驱动**:...

Global site tag (gtag.js) - Google Analytics