yter1  

项目亮点:

多Reactor中的核心思想:one loop per thread

  • 如果一个TCP链接在多个线程中处理,会出现如下情况:
    • socket被意外关闭。A线程要从socket中读/写消息,但是该socket被B线程给close了,更糟的情况是,B close后,新的连接的socket刚好使用的是B关闭的socket,那A线程再次进行读/写早已经不是原来的那个连接了(这种现象叫串话)
    • 不考虑关闭,只考虑读写也有问题。比如A、B线程同时对一个TCP连接进行读操作,如果两个线程几乎同时各自收到一部分消息,那如何把数据拼接成完整的消息,如何知道哪部分数据先到达。如果同时写一个socket,每个线程只发送半条消息,接收方又该怎么处理,如果加锁,那还不如直接就让一个线程处理算了
  • socket 对sockfd的操作,比如禁用Nagel算法、 设置地址复用、关闭sockfd等都可以封装在Socket类中。
    禁用Nagle算法的目的:避免发送大量的小包,网络上每次只能一个小包存在,在小包被确认之前,只能积累发送大包,如果包长度达到MSS,则允许发送;如果该包含有FIN,则允许发送;但发生了超时(一般为200ms),则立即发送, 启动TCP_NODELAY,就意味着禁用了Nagle算法。键盘打字就是小包传输。TCP_NODELAY 修改。
  • Channel类:每个fd上都有自己感兴趣的事件和实际发生的事件(比如EPOLLIN、EPOLLOUT),我们可以把fd和其感兴趣的事件、实际发生的事件、事件发生后要执行的回调函数一起封装成Channel类。
  • 基于Timing Wheel实现毫秒级定时器垃圾回收;
  • 改造Buffer模块实现零拷贝文件传输;
    为什么要output buffer?

当网络不好的时候,服务器内核中TCP发送缓冲区可能不够了,如果用户有很多数据要发送,那就只能等待内核发送缓冲区空闲,但是你也不知道要等多久,因为取决于对方接收了多少,解决方案是:如果操作系统无法把用户要发送的数据一次性发送完毕,那就把剩余的数据暂存在output buffer中,下次再发送,这样就可以立马让出控制权去处理其他业务。(更详细请参考P205, 7.4.2)

为什么要input buffer?

TCP接收缓冲区收到数据,如果应用层一次性不能读取完毕,就会频繁触发EPOLLIN事件,降低效率,所以需要尽可能一次性把TCP接收缓冲区的数据全部读取出来,然而,尽管把数据全部读取出来了,但是可能接收的依然不是完整的消息,如果我们等着接收完整的数据就太浪费时间了,所以可以把取出来的数据暂时存储再input buffer中,然后就可以立马处理别的业务,当input buffer中收到完整的消息的时候,在通知程序进行业务处理。(更详细请参考P206, 7.4.2)

以太网帧 MTU 最大 1500字节,MSS TCP 最大传输单元1460字节,TCP发送缓冲区存在数据可发,就会频繁触发对等方IO复用的POLLIN可读事件,将TCP缓冲区的内容读取出来到哪?,到对等方的缓冲区中,但是呢?又不是完整的应用层面定义的通信包大小 256 * 1024 字节 也就是256k为一个通信包,才会会处理包,那cpu不就空闲了嘛。所以把数据包放到应用层的缓冲区中。
而滑动窗口会分为 已发送确认区 | 发送待确认区 | 未发送区 ,按序确认TCP包。

TCPClient: 封装客户端逻辑,比如建立TCP连接,支持慢系统调用处理(TEMP_FAILURE_RETRY),继承Thread线程类中的run方法,然后主线程循环发送测试信号并处理接收到的指令,的在实际的应用过程中,我们一般需要继承这个基类,去实现它的msgPrase方法(协议命令解析),带缓存的,先处理读逻辑,后处理写逻辑,读写数据都有时,都处理,避免读写各自长时间处理。
TCPTaskQueue: 处理TCP连接的验证,对TCP连接中的任务进行处理和状态进行管理。
等待接受验证指令,并进行验证

struct pollfd = pfds;
pSocket->filldPollFD(pfds,POLLIN);//填充可读事件
int res_time = usleep_time;
while(!isFinal()){
	// 动态设置事件掩码:当需要发送数据时添加POLLOUT
	if (hasDataToSend()) {  // 假设存在判断发送缓冲区的函数
		pfd.events = POLLIN | POLLOUT;
	} else {
		pfd.events = POLLIN;
	}
 // 单次poll调用,统一处理读写事件
	int ret = TEMP_FAILURE_RETRY(::poll(&pfd, 1, timeout_ms));
	if (ret > 0) {
		// 错误处理(优先级最高)
		if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
			LOGFATAL << __PRETTY_FUNCTION__  << ": 套接口错误" <<  ;
			break;
		}
		// 处理读事件(优先级次之)
		if (pfd.revents & POLLIN) {
			if (!ListeningRecv()) {
				LOGDEBUG << __PRETTY_FUNCTION__ << ": 读操作失败";
				break;
			}
		}
		// 处理写事件(最后处理)
		if (pfd.revents & POLLOUT) {
			if (!ListeningSend()) {
				LOGDEBUG << __PRETTY_FUNCTION__ << ": 写操作失败";
				break;
			}
		}
	} else if (ret < 0) { // 错误处理
		break;
	}

	// 重置超时为固定间隔,或根据业务调整
	res_time = usleep_time / 1000; 
	}
	sync(); //确保所有缓冲的数据被发送出去,然后设置_buffered为false,表示不再缓冲。
	_buffered = false;
}

muduo库的TCPClient:用于编写网络客户端,能发起连接,并且有连接断开之后的重试功能

posted on 2025-04-16 16:33  花生米与花生酱  阅读(10)  评论(0)    收藏  举报