提到tcp的三次握手,网上可以搜到很多资料,大体都是类似的,比如下面这张图示,client和server端的状态转换,报文标志位看上去挺清晰的。
image.png
但是从源码角度看会有几个不一样的地方,比如
a. 服务器端收到客户端的syn报文,回复syn+ack后,就会进入syn_recv状态吗?
b. 服务器端的状态会从listen转换到 syn_recv,再到established吗?
下面是看过源码后根据自己理解画的一张图
image.png
server收到client发送的syn报文后,为了防止syn攻击,会首先创建request_sock结构,保存到hash表中(半连接队列),而不是直接分配sock结构。可参考kernel代码中 request_sock和sock结构体的定义,sock比request_sock需要更多内存。然后发送syn+ack报文给client。
如果client是正常连接的话,会发送ack报文给server,server收到后会进行判断,如果是正常ack报文,就会接受此连接,将request_sock从hash表删除,真正分配sock结构,并设置sock->sk_state为TCP_SYN_RECV,将sock结构赋给request_sock->sock后,将request_sock再次添加到另一个链表(全连接队列),后面又紧接着把sock->sk_state为TCP_ESTABLISHED,最后唤醒accept进程到全连接队列取出sock,由accept进程使用newsock和client进行数据传输。
上面这种防止syn攻击的方式成为syn cache,还有另一个syn cookie,可参考函数tcp_v4_conn_request,这种方法不会保存任何信息就可以达到防攻击目的。
接下来回答上面的两个问题,对于问题a,server从收到syn到再次收到ack的这段时间内,因为还没有分配sock结构,所以从代码看,是在收到ack后,分配sock才设置TCP_SYN_RECV。但是还是可以认为是TCP_SYN_RECV,可以通过查看cat /proc/net/tcp确认。
对于问题b,server sock一直是处于listen状态的,收到新连接后,会分配新的sock,改变新sock的状态。
堵塞函数
socket编程中有如下几个堵塞函数,简单介绍一下
a. connect
调用connect后,kernel会自动发送syn报文发起tcp三次握手。这个调用默认是堵塞的,等待三次握手完成后,才会返回成功与否。
b. accept
accept等待从全连接队列接收新连接,如果全连接队列为空,就会一直堵塞等待,直到新连接到来。
c. read/write
读写数据默认也都是堵塞的。
如果不想堵塞的话,可以使用select/poll/epoll等io多路复用技术来实现。
网友评论