Stanford_CS144_Lab4
在这一关要求实现一个TCPConnection类,基于TCP的有限状态机,将send和recieve封装起来。也就是说,作为一个TCP客户端,我们有自己的sender和receiver,现在要用这个客户端与其他主机进行联系,所以我们要实现TCP的各种FSM。
在tests文件夹中,我们可以看到很多关于fsm的测试文件,这些就是基于不同的有限状态机的测试,因此,我们掌握了TCP的有限状态机,才能将这个实验完成。我们就先来讲讲这些fsm:
状态机 | 含义 |
---|---|
fsm_ack_active | 主动关闭 |
fsm_ack_rst | |
我们先来看看到底要实现哪些类
在上述代码中,可以看到有一个之前没有接触过的类——TCPConfig
,所以我们首先来了解一下TCPConfig
。我们到TCPConfig
类中可以看到,它是对一些相关参数设置默认大小,如下所示:
1 |
|
在_receiver
等变量中通过TCPConfig
默认大小。刚看到TCPConnection
类时可能无从下手,我们先运行一下check程序,从第一个faild入手(没错,本懒狗是边做lab边写博客,面向tests编程)。
我们可以看到第一个faild出现在第35个测试,也就是fsm_passive_close,这个测试检测的是有限状态机中的初始状态(?)我们在项目中找到指定位置,开始debug,发现是void TCPConnection::connect()
方法的错误,当我们要建立连接的时候,我们想到的还是三次握手,如下图:
(还是以客户端为例子)首先,我们要进行第一次握手,也就是客户端发送SYN报文,在TCPSender类中,我们实现了fill_window方法,里面包含了三次握手中第一次握手的过程。在TCPConnection中,有相关的建立连接的方法,即void TCPConnection::connect()
方法,于是我们将sender中的握手的过程加入函数当中,如下所示:
1 |
|
这就是第一次握手时的场景,调用了fill_window(),在没有预设参数的情况下,fill_window会默认是第一次握手,从而设置对用的syn和seqno值。这样就完成了建立连接的第一步。为了使得发送报文可以模块化操作,实现real_send函数,real_send函数负责将_sender.segment_out队列中的报文拿出来,放入到 _segment_out队列中,代码如下所示:
1 |
|
当收到服务器的第二次握手时,就会调用TCPConnection中的void TCPConnection::segment_received(const TCPSegment &seg)
函数,这个函数的参数是收到的segment,当收到这个segment的时候,客户端就要进行第三次握手,当然,我们的第三次握手就可以看成正常的发送报文了。那么我们来看看收到报文之后应该如何处理。首先,我们将segment给到我们的 _receiver中:
1 |
|
在TCP协议中,rst用来异常的关闭连接。在TCP的设计中它是不可或缺的,发送rst段关闭连接时,不必等缓冲区的数据都发送出去,直接丢弃缓冲区中的数据,而接收端收到rst后,也不必发送ack来确认。
什么时候发送RST包:
- 建立连接的SYN到达某端口,但是该端口上没有正在监听的服务。
- TCP收到了一个根本不存在的连接上的分节。
- 请求超时。 使用setsockopt的SO_RCVTIMEO选项设置recv的超时时间。接收数据超时时,会发送RST包。
当收到的segment的rst段为1,那么我们直接断开连接(非正常关闭),而不必发送ack来确认,如下所示:
1 |
|
在write
函数中,我们需要将数据写入到stream中,然后返回写入stream的data的长度,如下所示:
1 |
|
在关闭TCP连接也就是“四次握手”的时候,我们会有一个TIME_WAIT
的状态,在这个状态下客户端会等待2MSL才断开,这其实有两个原因:
- 保证全双工的连接能够可靠关闭。
- 保证这次连接的数据段彻底从网络中消失。
在这里我们要创建一个real_send()函数,我们都知道在sender中其实只是把数据传到了队列segment_out里面来作为发送数据的操作,在这个任务中我们将完成剩余的操作,也就是将数据发送出去,所以我们需要完成real_send()函数。
在segment_received()函数中,我们要模拟的是TCP收到数据的情况,其框架如下:
1 |
|
在tick函数中,
在lab4中,我们可以检测出前面实验的错误:
我删除了很多不必要的代码
TCP可靠传输
做到这里,我们的TCP也就算实现了基本功能,我们也就知道了TCP协议如何保证可靠传输:
- 流量控制:TCP连接双方都有固定大小的缓冲空间,TCP接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据的时候,就提示发送方降低发送的速率。
- 拥塞控制:当网络拥塞时,减少数据的发送(这个在lab中没有什么体现)。
- 超时重传:当TCP发出一个段后,就会启动一个定时器,如果不能及时收到确认,就重发这个报文段。
- 校验和:(这个好像在后面的实验中有体现)。
再补充一个知识点——socket
TCP长连接
在HTTP1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束后就中断连接。但从HTTP1.1开始,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入如下字段:
1 |
|
HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
TCP保活机制
保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务器端检测到这种半开放的连接。对于设置了keepalive来说,当tcp检测到对端socket不再可用时(不能发出探测包,或探测包没有收到ACK的响应包),select会返回socket可读,并且在recv时返回-1,同时置上errno为ETIMEDOUT。此时TCP的状态是断开的。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!