加入收藏 | 设为首页 | 会员中心 | 我要投稿 PHP编程网 - 黄冈站长网 (http://www.0713zz.com/)- 数据应用、建站、人体识别、智能机器人、语音技术!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

Linux内核分析 - 网络[十六]:TCP三次握手

发布时间:2016-10-16 06:18:26 所属栏目:Linux 来源:站长网
导读:副标题#e# 内核:2.6.34 TCP是应用最广泛的传输层协议,其提供了面向连接的、可靠的字节流服务,但 也正是因为这些特性,使得TCP较之UDP异常复杂,还是分两部分[创建与使用]来进行分析。这篇主要包括TCP的创建及三次握手 的过程。 编程时一般用如下语句创建

几步重要的代码如下, tcp_connect_init()中设置了tp->rcv_nxt=0,tcp_transmit_skb()负责发送报文,其中seq=tcb->seq=tp->write_seq ,ack_seq=tp->rcv_nxt。

tcp_connect_init(sk);     
tp->snd_nxt = tp->write_seq;     
……     
tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);

*收到服务端的SYN+ACK,发送ACK
tcp_rcv_synsent_state_process()

此时已接收到对方的ACK,状态变迁到TCP_ESTABLISHED。最 后发送对方SYN的ACK报文。

tcp_set_state(sk, TCP_ESTABLISHED);     
tcp_send_ack(sk);

服务端流程

*bind() -> inet_bind()

bind操作的主要作用 是将创建的socket与给定的地址相绑定,这样创建的服务才能公开的让外部调用。当然对于socket服务器的创建来说,这一步不 是必须的,在listen()时如果没有绑定地址,系统会选择一个随机可用地址作为服务器地址。

一个socket地址分为ip和 port,inet->inet_saddr赋值了传入的ip,snum是传入的port,对于端口,要检查它是否已被占用,这是由sk->sk_prot ->get_port()完成的(这个函数前面已经分析过,在传入port时它检查是否被占用;传入port=0时它选择未用的端口)。如果 没有被占用,inet->inet_sport被赋值port,因为是服务监听端,不需要远端地址,inet_daddr和inet_dport都置0。

注意bind操作不会改变socket的状态,仍为创建时的TCP_CLOSE。

snum = ntohs

(addr->sin_port);     
……     
inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;     
if (sk->sk_prot->get_port(sk, snum)) {     
 inet->inet_saddr = inet->inet_rcv_saddr = 0;     
 err = -EADDRINUSE;     
 goto out_release_sock;     
}     
……     
inet->inet_sport = htons(inet->inet_num);     
inet->inet_daddr = 0;     
inet->inet_dport = 0;

listen() -> inet_listen()

listen操作开始服务器的监 听,此时服务就可以接受到外部连接了。在开始监听前,要检查状态是否正确,sock->state==SS_UNCONNECTED确保仍是未连 接的socket,sock->type==SOCK_STREAM确保是TCP协议,old_state确保此时状态是TCP_CLOSE或TCP_LISTEN,在其它状态下 进行listen都是错误的。

if (sock->state != SS_UNCONNECTED || sock-

>type != SOCK_STREAM)     
 goto out;     
old_state = sk->sk_state;     
if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))     
 goto out;

如果已是TCP_LISTEN态,则直接跳过,不用再执行listen了,而只是重新设置listen队列长度 sk_max_ack_backlog,改变listen队列长也是多次执行listen的作用。如果还没有执行listen,则还要调用 inet_csk_listen_start()开始监听。

inet_csk_listen_start()变迁状态至TCP_LISTEN,分配监 听队列,如果之前没有调用bind()绑定地址,则这里会分配一个随机地址。

if 

(old_state != TCP_LISTEN) {     
 err = inet_csk_listen_start(sk, backlog);     
 if (err)     
  goto out;     
}     
sk->sk_max_ack_backlog = backlog;

accept()

accept() -> sys_accept4() -> inet_accept() - > inet_csk_accept()

accept()实际要做的事件并不多,它的作用是返回一个已经建立连接的 socket(即经过了三次握手),这个过程是异步的,accept()并不亲自去处理三次握手过程,而只是监听icsk_accept_queue队列 ,当有socket经过了三次握手,它就会被加到icsk_accept_queue中,所以accept要做的就是等待队列中插入socket,然后被唤 醒并返回这个socket。而三次握手的过程完全是协议栈本身去完成的。换句话说,协议栈相当于写者,将socket写入队列, accept()相当于读者,将socket从队列读出。这个过程从listen就已开始,所以即使不调用accept(),客户仍可以和服务器建立 连接,但由于没有处理,队列很快会被占满。

if (reqsk_queue_empty

(&icsk->icsk_accept_queue)) {     
 long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);     
 ……     
 error = inet_csk_wait_for_connect(sk, timeo);     
 ……     
}     
         
newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);

(编辑:PHP编程网 - 黄冈站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读