网络编程需要关注的问题
- 连接建立(接受客户端的连接,服务器去连接第三方服务mysql redis)
- 连接断开
- 消息到达(从readbuffer中取数据)
- 消息发送完毕(将数据发到writebuffer中)
连接建立

来自客户端的连接产生的fd,设置为读事件进行监测。
连接上游服务时,采用一个非阻塞的I/O,第一次调用connect时返回值为-1,错误号是EINPROGRESS,说明当前连接正在继续。然后将事件注册为写事件,添加到epoll中进行管理。(服务器与上游服务建立连接三次握手的过程,第二步上游服务返回ack包,本地服务器收到以后会触发epollout事件)
epollout被触发,连接建立成功,可以在回调函数中进行一些对上游服务的初始化操作
连接断开

客户端主动断开连接时,客户端主动关闭写端发送fin包,服务器收到fin后关闭读端,read返回0得知客户端已关闭,注册EPOLLRDHUP事件。
服务端收到fin包有两种情况,一种是read返回0 另一种是服务端的读端关闭触发epollrdhup事件
四次挥手第三步服务器端将写通道关闭发送fin包,客户端接收到以后,把读端关闭,发送ack包,此时会触发EPOLLRDHUP事件,同时等待2msl事件,进入closed状态。
支持半关闭就先关闭服务端的读,服务端继续发送业务代码,发送完成后再发fin给客户端
skynet支持半关闭状态,而redis memcached nginx都是全关闭的,直接调用close(fd)引用计数-1
半关闭状态
背景
客户端关闭写通道,此时服务器还想继续推送完所有数据再关闭。
服务端先关闭读端,再发送业务数据,再调用closewrite或者close关闭写端
客户端接收数据以后,再close读端
实现
需要实现半关闭状态;close_wait阶段;必须要收到ack包,才能知道客户端收到了所有发送的数据
细节
发送端:
shutdown(SHUT_WR)发送一个FIN包,并且标记该socket为SEND_SHUTDOWN
shutdown(SHUT_RD)不发送任何包,但是标记socket为RCV_SHUTDOWN;
接收端:
收到FIN包标记该socket为RCV_SHUTDOWN
对于epoll来说,一个socket同时被标记为SEND和RCV的SHUTDOWN状态,那么epoll就会返回EPOLLHUP
如果被标记为RCV_SHUTDOWN,epoll会返回EPOLLRDHUP
消息到达

raed返回0时,说明对端的连接已经关闭
read返回-1
-
错误号为EWOULDBLOCK, 此时readbuffer为空,数据没有准备就绪,需要重试
-
错误号为EINTR时,说明系统调用被中断打断(中断的优先级高于系统调用),需要重试
-
错误号为ETIMEOUT时,说明tcp探活包超时
tcp-keepalive
背景
tcp是面向连接的,一般情况下,两端应用可以通过发送接收数据得知对端饿的存活;当两端都没有数据时,又该如何判断连接是否正常?系统默认keepalive是关闭的,当keepalive开启时,可以保持连接检测对方主机是否崩溃
tcpkeepalive属性
tcp_keepalive_time 两端多久没有数据交换,开始发送tcp探活包
tcp_keepalive_probes 发送多少次探活包
tcp_keepalive_intvl 探活包发送间隔
tcpkeepalive是在什么层进行的
传输层
tcp探活包和心跳检测
心跳检测是在应用层完成的,应用层无法感知syn,ack这些包,read一直收不到探活包,会收到ETIMEOUT错误
为什么应用层需要开启心跳检测
因为传输层探活检测无法判断进程阻塞或者死锁的情况
心跳检测 每十秒发送一次心跳包,三次没收到close
心跳包应用场景
数据库间,主从复制用心跳检测
客户端与服务器之间,用心跳检测
反向代理与上游服务器之间使用探活检测
服务器到数据库使用探活检测,对于数据库来说服务端是否阻塞与他无关
消息发送完毕

当write的返回值大于0时,必然是size
当非阻塞时,写缓冲区空间不足,则需要注册写事件,等待写事件触发,再重试写入操作
本文详细探讨了TCP网络编程中的关键环节,包括连接建立(服务器与客户端及第三方服务的三次握手)、连接断开(四次挥手与半关闭状态)、消息到达与发送的处理,以及TCP的Keepalive机制和心跳检测在确保连接稳定性和检测对端状态中的作用。同时,文章提到了不同场景下对半关闭状态的支持以及如何处理各种异常情况。
1079

被折叠的 条评论
为什么被折叠?



