网络编程实战
盛延敏
前大众点评云平台首席架构师
44207 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 40 讲
网络编程实战
15
15
1.0x
00:00/00:00
登录|注册

35 | 答疑:编写高性能网络编程框架时,都需要注意哪些问题?

内存占用优化
动态增长
负责将output_buffer中的数据发送到套接字缓冲区
若条件不满足,则将数据拷贝到output_buffer中
先尝试直接发送数据
调用tcp_connection_send_data发送数据
欢迎转发和交流
帮助梳理高性能网络编程的方方面面
内存方面的设计
指针数组
多线程下共享变量竞争的问题
加锁的目的
保存http_request和http_response数据
用于传递有用的数据结构
包含channel对象
connectionClosedCallBack
writeCompletedCallBack
messageCallBack
connectionCompletedCallBack
handle_write函数
tcp_connection_send_data方法
tcp_connection_send_buffer方法
总结
channel_map的设计
主线程等待子线程完成的同步锁问题
tcp_connection对象设计
回调函数的设计
发送数据时的处理流程
答疑:编写高性能网络编程框架时,都需要注意哪些问题?

该思维导图由 AI 生成,仅供参考

你好,我是盛延敏,这里是网络编程实战的第 35 讲,欢迎回来。
这一篇文章是实战篇的答疑部分,也是本系列的最后一篇文章。非常感谢你的积极评论与留言,让每一篇文章的留言区都成为学习互动的好地方。在今天的内容里,我将针对评论区的问题做一次集中回答,希望能帮助你解决前面碰到的一些问题。
有关这部分内容,我将采用 Q&A 的形式来展开。

为什么在发送数据时,会先尝试通过 socket 直接发送,再由框架接管呢?

这个问题具体描述是下面这样的。
当应用程序需要发送数据时,比如下面这段,在完成数据读取和回应的编码之后,会调用 tcp_connection_send_buffer 方法发送数据。
//数据读到buffer之后的callback
int onMessage(struct buffer *input, struct tcp_connection *tcpConnection) {
printf("get message from tcp connection %s\n", tcpConnection->name);
printf("%s", input->data);
struct buffer *output = buffer_new();
int size = buffer_readable_size(input);
for (int i = 0; i < size; i++) {
buffer_append_char(output, rot13_char(buffer_read_char(input)));
}
tcp_connection_send_buffer(tcpConnection, output);
return 0;
}
而 tcp_connection_send_buffer 方法则会调用 tcp_connection_send_data 来发送数据:
int tcp_connection_send_buffer(struct tcp_connection *tcpConnection, struct buffer *buffer) {
int size = buffer_readable_size(buffer);
int result = tcp_connection_send_data(tcpConnection, buffer->data + buffer->readIndex, size);
buffer->readIndex += size;
return result;
}
在 tcp_connection_send_data 中,如果发现当前 channel 没有注册 WRITE 事件,并且当前 tcp_connection 对应的发送缓冲无数据需要发送,就直接调用 write 函数将数据发送出去。
//应用层调用入口
int tcp_connection_send_data(struct tcp_connection *tcpConnection, void *data, int size) {
size_t nwrited = 0;
size_t nleft = size;
int fault = 0;
struct channel *channel = tcpConnection->channel;
struct buffer *output_buffer = tcpConnection->output_buffer;
//先往套接字尝试发送数据
if (!channel_write_event_is_enabled(channel) && buffer_readable_size(output_buffer) == 0) {
nwrited = write(channel->fd, data, size);
if (nwrited >= 0) {
nleft = nleft - nwrited;
} else {
nwrited = 0;
if (errno != EWOULDBLOCK) {
if (errno == EPIPE || errno == ECONNRESET) {
fault = 1;
}
}
}
}
if (!fault && nleft > 0) {
//拷贝到Buffer中,Buffer的数据由框架接管
buffer_append(output_buffer, data + nwrited, nleft);
if (!channel_write_event_is_enabled(channel)) {
channel_write_event_enable(channel);
}
}
return nwrited;
}
这里有同学不是很理解,为啥不能做成无论有没有 WRITE 事件都统一往发送缓冲区写,再把 WRITE 事件注册到 event_loop 中呢?
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文主要讨论了在编写高性能网络编程框架时需要注意的问题。作者通过Q&A的形式回答了读者关于数据发送时先尝试通过socket直接发送,再由框架接管的疑问。文章指出,这种处理方式旨在提高发送效率,通过直接发送数据到套接字缓冲区,避免了数据拷贝的过程,在大部分场景下已经满足数据发送的需要。而当网络条件变差或待发送数据较大时,框架会将数据缓冲到发送缓冲区中,并由事件分发机制负责发送。这种设计能够在高效处理条件下直接发送数据,同时在处理效率变慢或待发送数据较大时进行缓冲,保证了整体的发送效率和稳定性。 文章还详细介绍了回调函数的设计,以及tcp_connection对象的设计和其与channel对象的联系和区别。此外,还讨论了主线程等待子线程完成的同步锁问题以及channel_map的设计,特别是内存方面的设计。总的来说,本文通过具体的代码分析和解释,深入浅出地阐述了高性能网络编程框架的设计理念和实现细节,为读者提供了有益的技术指导。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《网络编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(32)

  • 最新
  • 精选
  • 酸葡萄
    为什么在发送数据时,会先尝试通过 socket 直接发送,再由框架接管呢? 老师你好,这个问题中,发送缓冲区有数据说明发送效率低(数据多,网络差等原因导致),没有注册WRITE事件是什么意思呢?(感觉这时一个基础问题[小尴尬])

    作者回复: 有两种发送数据的方式,第一种是通过注册WRITE事件,等待reactor来驱动我们把数据发送出去;第二种是不需要reactor驱动,直接往套接字上发送。这里的解释是说,在大部分情况下,为了效率,直接往套接字上发送,当一次解决不了时,再通过reactor来驱动数据发送。

    2019-11-30
    10
  • Geek_63bb29
    谢谢盛老师,链接是关于实战代码的流程图 https://app.yinxiang.com/fx/7e601cad-6501-4fe7-8e4e-f0fbd9d02c4b

    作者回复: ������

    2020-07-30
    2
    6
  • 范闲
    这是我改造的c++版本,目前还在调试中 https://gitee.com/willam-dialogue/net_reactor 调试过程中遇到了几个问题: 1.在telnet以后, 客户端第一次发送消息,可以正常收到消息. 客户端第二次发送消息,会导致server coredump. 目前初步定位到问题是发生在TcpConnection.h中的sendData函数,更具体的原因没有找到 2.如果将回调函数注册为如下方式: TcpConnection 公有云继承了enabled_shared_from_this typedef std::shared_ptr<TcpConnection> TcpConnectionPtr; typedef std::function<void (const TcpConnectionPtr &)> ConnCompleteCallBack; typedef std::function<void (const TcpConnectionPtr &)> ConnCloseCallBack; typedef std::function<void (const TcpConnectionPtr &)> WriteCompleteCallBack; typedef std::function<void (Buffer*, const TcpConnectionPtr &)> MessageCallBack; 在TcpConnection调用ConnCompleteCallBack就没有问题. 但是在channel中绑定了TcpConnection.h 中的handleRead和handleWrite的回调,在调试过程中会报weak_ptr的相关错误.实际定位发现在handleRead里面调用了MessageCallBack的回调,MessageCallBack的入参是shared_from_this(),weak_ptr的错误由这个产生的,目前还在看是否因为使用方法的原因引起的. 希望同学们能够一起帮忙看看 这些问题, 我还没找到好的方法.邮箱hy572801400@163.com

    作者回复: 给你顶一下,大家一起帮忙看(PS:我最近有点忙,闲下来也来帮你一起看)

    2020-03-27
    2
    5
  • 王小白白白
    首先非常感谢老师的课程,系统的学习了网络编程相关知识,受益匪浅。 这里是我改写的c++ epoll服务器版本,https://github.com/wangxiaobai-dd/BowServer 主要改动有: 1,使用c++语法,智能指针,variant,std::mutex,std::thread等,代码结构有些改变 2,消除一些内存泄漏,(buffer相关待做) 3,加入一个事件队列channel_queue,这样event_dispatch可以改为非阻塞,取消唤醒机制 4, 有新连接时选择任务数最少的连接 5,epoll del ,update 接口有所修改 会持续完善优化项目,一起学习进步~ 有帮助的话点个星星嘻嘻

    作者回复: 赞,已点。

    2020-08-01
    4
  • CofCai
    我最开始是直接一头代码的细节里面去,没先从宏观上有个把握,然后读的很痛苦。于是自己就借助一些工具,比如思维导图画一下函数调用关系、各种结构体对象的关系,总算有一点头绪了。贴上我的学习笔记(笔记是边读源码边写的,有的理解后来觉得不对,但可能没来得及修改,希望各位伙伴带着思考): 各种结构体对象关系:https://www.processon.com/view/link/5ead14555653bb6efc7cbe59

    作者回复: 强大,请问我可以引用么?

    2022-01-22
    3
    2
  • 日就月将
    老师 您写的代码好像没有加内存释放处理

    作者回复: 有可能遗漏的,能帮忙在githut提MR么?

    2021-10-20
    2
  • 范闲
    https://gitee.com/willam-dialogue/net_reactor 这是目前我改造的cpp版本,正在调试中。 调试过程中遇到一些问题 1.telnet连接以后,第一次发送消息正常。但是第二次发送消息就会coredump, 初步定位到问题出在TcpConnection.h 中的sendData函数中,具体原因还在排查 2.如果将回调函数改成 typedef std::shared_ptr<TcpConnection> TcpConnectionPtr; typedef std::function<void (const TcpConnectionPtr &)> ConnCompleteCallBack; typedef std::function<void (const TcpConnectionPtr &)> ConnCloseCallBack; typedef std::function<void (const TcpConnectionPtr &)> WriteCompleteCallBack; typedef std::function<void (Buffer*, const TcpConnectionPtr &)> MessageCallBack;

    作者回复: C++的模板太强大了,不过也很复杂,加油~

    2020-03-27
    2
  • yusuf
    // add event read for the new connection struct channel *channel1 = channel_new(connected_fd, EVENT_READ, handle_read, handle_write, tcpConnection); 请问这里第4个参数设置了handle_write函数,为什么第2个参数没有设置EVENT_WRITE呢? 原本以为这个地方是漏掉了EVENT_WRITE,可添加上EVENT_WRITE后,发现tcp服务器收到数据后会一直打印,而http服务器响应一次请求后会崩溃。这又是为什么呢?

    作者回复: 这里是向reactor注册了数据可读的事件,注意这个时候缓冲区是没有写入的需求的,如果注册了可写事件,相当于这个事件是肯定会发生的(因为套接字写缓冲区都是空的,可以往里写),所以这个时候你会看到一直会打印。 也就是说,只有在真正有数据需要发送的时候,才需要注册EVENT_WRITE,让reator驱动把需要发送的数据发送完。

    2019-11-26
    2
  • 范龙dragon
    哪位大神回答下,框架中哪里有释放tcp_connection和channel资源的地方,从代码中看到这两个对象都是malloc出来的,但没找到在哪里free的,求指教!

    作者回复: 我的锅,应该在tcp_connection_shutdown函数里面释放channel和tcp_connection两个对象。抽空改下。

    2020-07-15
    3
    1
  • bbbi
    老师,好像您封装的这个框架跟netty神似

    作者回复: 嗯,应该说基于事件分发机制的框架,大致的思想都是相同的,不信你可以再去看看ACE, libevent或者其他的库。

    2020-03-26
    2
    1
收起评论
显示
设置
留言
32
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部