目录
TCP状态转换
- 了解TCP状态转换图可以帮助开发人员查找问题.
- 说明: 上图中粗线表示主动方, 虚线表示被动方, 细线部分表示一些特殊情况, 了解即可, 不必深入研究.
- 对于建立连接的过程客户端属于主动方, 服务端属于被动接受方(图的上半部分)
- 对于关闭(图的下半部分), 服务端和客户端都可以先进行关闭.
- 处于ESTABLISHED状态的时候就可以收发数据了, 双方在通信过程当中一直处于ESTABLISHED状态, 数据传输期间没有状态的变化.
- TIME_WAIT状态一定是出现在主动关闭的一方
- 主动关闭的Socket端会进入TIME_WAIT状态,并且持续2MSL时间长度,MSL就是maximum segment lifetime(最大分节生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间将在网络中消失。
使用netstat -anp可以查看连接状态
注:数据传输的时候带了一个字节的数据, 所以server发送给client的ACK=x+2
- 三次握手过程:
- 客户端: SYN_SENT---connect()
- 服务端: LISTEN--listen() SYN_RCVD
- 当三次握手完成后, 都处于ESTABLISHED状态
- 数据传输过程中:状态不发生变化, 都是ESTABLISHED状态
- 四次挥手过程:
- 主动关闭方: FIN_WAIT_T FIN_WAIT_2 TIME_WAIT
- 被动关闭方: CLOSE_WAIT LAST_ACK
- 注意:
- SYN_SENT状态出现在哪一方? 客户端
- SYN_RCVD状态出现在哪一方? 服务端
- TIME_WAIT状态出现在哪一方? 主动关闭方
- 在数据传输的时候没有状态变化.
- TIME_WAIT是如何出现的:
- 启动服务端, 启动客户端, 连接建好, 而且也可以正常发送数据;
- 然后先关闭服务端, 服务端就会出现TIME_WAIT状态.
为什么需要2MSL?
- 原因之一: 让四次挥手的过程更可靠, 确保最后一个发送给对方的ACK到达;
- 若对方没有收到ACK应答, 对方会再次发送FIN请求关闭, 此时在2MS时间内被动关闭方仍然可以发送ACK给对方.
- 原因之二: 为了保证在2MS时间内, 不能启动相同的SOCKET-PAIR.
- TIME_WAIT一定是出现在主动关闭的一方, 也就是说2MS是针对主动关 闭一方来说的;由于TCP有可能存在丢包重传, 丢包重传若发给了已经断 开连接之后相同的socket-pair(该连接是新建的, 与原来的socket-pair完 全相同, 双方使用的是相同的IP和端口), 这样会对之后的连接造成困扰, 严重可能引起程序异常.
- 如何避免问题2呢??
- 很多操作系统实现的时候, 只要端口被占用, 服务就不能启动.
- 测试:
- 启动服务端和客户端, 然后先关闭服务端, 再次启动服务端, 此时服务端报错: bind error: Address already in use;
- 若是先关闭的客户端, 再关闭的服务端, 此时启动服务端就不会报这个错误.
- socket-pair的概念: 客户端与服务端连接其实是一个连接对, 可以通过使用netstat -anp | grep 端口号 进行查看.
端口复用
- 解决端口复用的问题: bind error: Address already in use, 发生这种情况是在服务端主动关闭连接以后, 接着立刻启动就会报这种错误.
setsockopt函数
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(int));
- 函数说明可参看<<UNIX环境高级编程>>
- 由于错误是bind函数报出来的, 该函数调用要放在bind之前, socket之后
半关闭状态
半关闭的概念:
- 如果一方close, 另一方没有close, 则认为是半关闭状态, 处于半关闭状态的时候, 可以接收数据, 但是不能发送数据. 相当于把文件描述符的写缓冲区操作关闭了.
- 注意: 半关闭一定是出现在主动关闭的一方.
shutdown函数
长连接和短连接的概念
- 长连接: 连接建立好之后,一直保持连接不关闭
- 短连接: 连接使用完之后就立刻关闭.
shutdown和close的区别:
- shutdown可以实现半关闭, close不行
- shutdown关闭的时候, 不考虑文件描述符的引用计数, 是直接彻底关闭
- close考虑文件描述符的引用计数, 调用一次close只是将引用计数减1,
- 只有减小到0的时候才会真正关闭.
- 如: 调用dup函数或者dup2函数可以复制一个文件描述符, close其中一个并不影响另一个文件描述符, 而shutdown就不同了, 一旦shutdown了其中一 个文件描述符, 对所有的文件描述符都有影响
心跳包
- 什么是心跳包?
- 用于监测长连接是否正常的字符串.
- 在什么情况下使用心跳包?
- 主要用于监测长连接是否正常.
- 如何使用心跳包?
- 通信双方需要协商规则(协议), 如4个字节长度+数据部分
- 方法1:
keepAlive = 1; setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
- 由于不能实时的检测网络情况, 一般不用这种方法
- 方法2:
- 在应用程序中自己定义心跳包, 使用灵活, 能实时把控.
- 【注】:参考黑马linux C++教程