linux中TCP的socket、bind、listen、connect和accept的实现
来源:互联网 发布:ida远程调试linux 编辑:程序博客网 时间:2024/05/18 01:02
首先介绍和服务器端相关的系统调用,依次为socket->bind->listen->accept
socket:
当服务器程序调用socket系统调用之后,内核会创建一个struct socket和一个struct sock结构,两者可以通过指针
成员变量相互访问对方。内核直接操作的是struct sock结构。struct socket的存在是为了适应linux的虚拟文件系统,
把socket也当作一个文件系统,通过指定superblock中不同的操作函数实现完成相应的功能。
在linux内核中存在不同的sock类型,与TCP相关的有struct sock、 struct inet_connection_sock,、struct tcp_sock等。
这些结构的实现非常灵活,可以相互进行类型转换。这个机制的实现是结构体的一层层包含关系:struct tcp_sock的
第一个成员变量是struct inet_connection_sock,struct inet_connection_sock的第一个成员变量是struct sock。
通过这种包含关系,可以将不同的sock类型通过指针进行相互转换。比如:
struct tcp_sock tcp_sk; struct sock *sk = (struct sock *)&tcp_sk;
不同类型的sock的包含关系如下:
为了避免从小的结构体转换到大的结构体造成内存越界,对于TCP协议,内核在初始化一个stuct sock时给它分配的空间
大小是一个struct tcp_sock的大小。这样sock类型的相互转换便可以灵活的进行。
另外,在内核创建完sock和socket之后,还需要绑定到对应的文件描述符以便应用层能够访问。一个task_struct中有一个
文件描述符数组,存储所有该进程打开的文件,因为socket也可以看做是文件,也存储在这个数组中。文件描述符就是
该socket在该数组中的下标,具体的实现请参照虚拟文件系统。
bind:
创建完socket之后就是地址的绑定了,通过bind系统调用实现。该调用通过传递进来的文件描述符找到对应的socket
结构,然后通过socket访问sock结构。操作sock进行地址的绑定。如果指定了端口检查端口的可用性并绑定,否则随
机分配一个端口进行绑定。但是怎样获知当前系统的端口绑定状态呢?通过一个全局变量inet_hashinfo进行,每次成
功绑定一个端口会都将该sock加入到inet_hashinfo的绑定散列表中。加入之后bind的系统调用已基本完成了。
listen:
接下来是listen系统调用,该过程比较复杂。和listen相关的大部分信息存储在inet_connection_sock结构中。同样的
内核通过文件描述符找到对应的sock,然后将其转换为inet_connection_sock结构。在inet_connection_sock结构体
中含有一个类型为request_sock_queue的icsk_accept_queue变量,存储一些希望建立连接的sock相关的信息。
结构为:
struct request_sock_queue {struct request_sock *rskq_accept_head;struct request_sock *rskq_accept_tail;rwlock_t syn_wait_lock;u8rskq_defer_accept;struct listen_sock *listen_opt;};
listen_opt用了存储当前正在请求建立连接的sock,称作半连接状态,用request_sock表示。request_sock有个成员
变量指针指向对应的strut sock。rskq_accept_head和rskq_accept_tail分别指向已经建立完连接的request_sock,称
作全连接状态,这些sock都是完成了三次握手等待程序调用accept接受。程序调用listen之后会为icsk_accept_queue
分配内存,并且将当前的监听sock放到全局变量inet_hashinfo中的监听散列表中。
当内核收到一个带有skb之后会通过tcp_v4_rcv函数进行处理。因为只有skb,还需找到对应的sock。该过程通过
__inet_lookup_skb进行实现。该函数主要调用__inet_lookup,其中:
1.首先看看这个包是不是一个已经建立好连接的sock中的包,通过__inet_lookup_established函数进行操作
(一个连接通过源IP,目的IP,源PORT和目的PORT标识)。
2.失败的话可能是一个新的SYN数据包,此时还没有建立连接所以没有对应的sock,和该sock相关的只可能是监听sock了。
所以通过__inet_lookup_listener函数找到在本地的监听对应端口的sock。
无论哪种情况,找到sock之后便会将sock和skb一同传入tcp_v4_do_rcv函数作统一处理:
if (sk->sk_state == TCP_ESTABLISHED) {sock_rps_save_rxhash(sk, skb->rxhash);TCP_CHECK_TIMER(sk);if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {rsk = sk;goto reset;}TCP_CHECK_TIMER(sk);return 0;}if (sk->sk_state == TCP_LISTEN) {struct sock *nsk = tcp_v4_hnd_req(sk, skb);if (!nsk)goto discard;if (nsk != sk) {if (tcp_child_process(sk, nsk, skb)) {rsk = nsk;goto reset;}return 0;}}if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {rsk = sk;goto reset;}
1.如果是一个已建立连接的sock,调用tcp_rcv_established函数进行相应的处理。
2.如果是一个正在监听的sock,需要新建一个sock来保存这个半连接请求,该操作通过tcp_v4_hnd_req实现。
这里我们只关注tcp的建立过程,所以只分析tcp_v4_hnd_req和tcp_child_process函数:
static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb){struct tcphdr *th = tcp_hdr(skb);const struct iphdr *iph = ip_hdr(skb);struct sock *nsk;struct request_sock **prev;struct request_sock *req = inet_csk_search_req(sk, &prev, th->source, iph->saddr, iph->daddr);if (req)return tcp_check_req(sk, skb, req, prev);nsk = inet_lookup_established(sock_net(sk), &tcp_hashinfo, iph->saddr,th->source, iph->daddr, th->dest, inet_iif(skb));if (nsk) {if (nsk->sk_state != TCP_TIME_WAIT) {bh_lock_sock(nsk);return nsk;}inet_twsk_put(inet_twsk(nsk));return NULL;}return sk;}
1.首先调用inet_csk_search_req查找在半连接队列中是否已经存在对应的request_sock。有的话说明这个请求
连接已经存在,调用tcp_check_req处理第三次握手的情况,当sock的状态从SYN_RCV变迁到ESTABLISHED
状态时,连接建立完成。需要将该request_sock从request_sock_queue队列中的listen_opt半连接队列取出,
放入全连接队列等待进程调用accept取走,同时是request_sock指向一个新建的sock并返回。
2.没有的话调用inet_lookup_established从已经建立连接sock中查找,如果找到的话说明这是一条已经建立的连
接,当该sock不处于timewait将sock返回状态时将sock返回,否则返回NULL。
3.当上述两种情况都失败了,表示这是一个新的为创建的连接,直接返回sk。
这样通过tcp_v4_hnd_req函数就能够找到或创建和这个skb相关的sock。
A.如果返回的sock和处于Listen状态的sock不同,表示返回的是一个新的sock,第三次握手已经完成了。调用
tcp_child_process处理。该函数的逻辑是让这个新的tcp_sock开始处理TCP段,同时唤醒应用层调用accept阻
塞的程序,告知它有新的请求建立完成,可以从全连接队列中取出了。
B.如果返回的sock没有变化,表示是一个新的请求,调用tcp_rcv_state_process函数处理第一次连接的情况。
该函数的逻辑较为复杂,简单的可以概括为新建一个request_sock并插入半连接队列,设置该request_sock的
sock为SYN_RCV状态。然后构建SYN+ACK发送给客户端,完成TCP三次握手连接的第二步。
accept:
最后的是accept系统调用。该调用创建新的struct socket表示新的连接,搜寻全连接队列,如果队列为空,将程
序自身挂起等待连接请求的完成。否则从队列中取出头部request_sock,并设置新的struct socket和request_sock
中的struct sock的对应关系。这样一个连接至此就建立完成了。客户端可以通过新返回的struct socket进行通信,
同时旧的struct socket继续在监听。
接下来介绍客户端的操作,客户端依次调用的是socket->connect
socket:
socket的实现和服务器端一样,不再复述了。
connect:
connect系统调用根据文件描述符找到socket和sock,如果当前socket的状态时SS_UNCONNECTED的情况下才
正常处理连接请求。首先调用tcp协议簇的connect函数(即tcp_v4_connect)发送SYN,然后将socket状态置为
SS_CONNECTING,将进程阻塞等待连接的完成。剩下的两次握手由协议栈自动完成。
tcp_v4_connect函数:
该函数首先进行一些合法性验证,随后调用ip_route_connect函数查找路由信息,将当前sock置为SYN_SENT状态,
然后调用inet_hash_connect函数绑定本地的端口,和服务器端绑定端口的过程类似,但是会额外的将sock添加
inet_hashinfo中的ehash散列表中(添加到这的原因是因为希望以后收到的SYN+ACK时能够找到对应的sock,
虽然当前并没有真正意义上的建立连接)。到最后调用tcp_connect构建SYN数据包发送。
tcp_connect:
该函数逻辑比较简单,构造SYN数据段并设置相应的字段,将该段添加到发送队列上调用tcp_transmit_skb发送skb,
最后重设重传定时器以便重传SYN数据段。
当客户端收到SYN+ACK之后,首先会通过tcp_v4_rcv从已建立连接散列表中找到对应的sock,然后调用tcp_v4_do_rcv
函数进行处理,在该函数中主要的执行过程是调用tcp_rcv_state_process。
又回到了tcp_rcv_state_process函数,它处理sock不处于ESTABLISHED和LISTEN的所有情况。当发现是一个SYN+ACK
段并且当前sock处于SYN_SENT状态时,表示连接建立马上要完成了。首先初始化TCP连接中一些需要的信息,如窗口大小,
MSS,保活定时器等信息。然后给该sock的上层应用发送信号告知它连接建立已经完成,最后通过tcp_send_ack发送ACK
完成最后一次握手。
- linux中TCP的socket、bind、listen、connect和accept的实现
- Socket通信——Linux下,socket、bind、listen、accept、connect的含义
- 【Linux网络编程】TCP网络编程中connect()、listen()和accept()三者之间的关系
- Linux网络协议栈之TCP socket/bind/listen/connect/accept/close/shutdown
- linux网络编程二:基础socket, bind, listen, accept, connect
- linux网络编程二:基础socket, bind, listen, accept, connect
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- TCP网络编程中connect()、listen()和accept()三者之间的关系
- Socket中listen/accept函数的区别
- 4W2H快速学习方法
- Java笔记——1
- 学习笔记&&心情小记--网络互连
- Ajax的介绍
- LINUX 文件目录管理
- linux中TCP的socket、bind、listen、connect和accept的实现
- 梯度、散度和旋度
- 基于ADT bundle快速搭建Android开发环境
- poj 4084:拓扑排序
- Java多线程学习中遇到的一个有趣的问题
- C++ 资源大全
- Linux下的防火墙iptables
- UVA 1252 Twenty Questions(状态压缩dp+记忆化)
- Android类库常用类型解析