文章目录
- 前引
- (十七)---- 重新阅读Muduo服务器编程书籍 做最后的小改小动 项目终究要迎来终声了
- 1、修改了 channel无法确定Tcpconnection是否存活问题 添加了weak_ptr 为运行回调过程中保驾护航
- 2、修改了 condition的包装函数名称 Signal/Broadcast -> Notify/NotifyAll
- 3、增加基础功能 可在main.cc设置线程数(main.cc 设置线程数)
- 4、拙谈 单线程单进程/单进程多线程/多进程单线程/多进程多线程 适用场合
- 5、取缔了 pthread_t/pthread_self等函数 gettid新王登基
- 5、修改了 增加SIGPIPE信号处理(之前已经解决了)
- 6、可控制日志库日志等级(DEBUG/INFO/WARN/ERRNO/FATAL)
- 7、修改了 TCPNODELAY部分 默认打开TCPNODELAY
- 8、修改了 在写入状态不允许Shutdown而后再无Shutdown的问题
- 9、更新了g++编译选项 影响最大开启了-Wconversion 重新对整个服务器代码进行强制类型转换
- 10、优化了 新增了channel的error回调函数 可显示error错误信息
- 11、修改了 日志名字ERROR取名错误问题(低级错误)
- 12、修改了 日志库负数转换字符串错误的函数(itoa参考实现)
- 结束语
前引
本来打算在寝室呆一会的 结果还是没有呆 就回图书馆了
结果回图书馆了 感觉人有点疲倦 就趴着睡了会觉
下午4点睡 现在7点… 属实是报复性补觉了
感觉这种睡觉太不健康了 每次一睡我就要睡这么久 而且睡觉起来的时候 头还有晕那么久 不知道是自己差睡眠 还是自己确实是想睡那么久
算了 睡都睡了 睡醒了起来看书了
(十七)---- 重新阅读Muduo服务器编程书籍 做最后的小改小动 项目终究要迎来终声了
1、修改了 channel无法确定Tcpconnection是否存活问题 添加了weak_ptr 为运行回调过程中保驾护航
刚刚把之前就很有疑惑的地方 做出了修改
也算是完善了channel
与 tcpconnection
这部分了
刚刚看到muduo
库做出的注释 一下子就豁然开朗了
就是关于在channel
中 为什么有个weak_ptr
之前我一直不理解 然后看了书当时也不理解 所以在之前的文件中就一直没有加这个成员
刚刚我仔细也仔细思考了 对于我们的程序 确实在运行回调函数 我们的tcpconnection
基本不可能会被析构 或者说死亡掉
因为能够引起tcpconnection
死亡的 也就是我们先去主动shutdown
然后再进行运转一轮read
到0字节 再调用destructor
函数 期间也没有机会再会让channel
可读了(仔细思考了一会 应该是这样的)
我也怕我有没有考虑到的地方 这里我也就说 应该是这样的 但也有可能有其他情况 例如我们这边读到0字节
了 但我们这边还有writing
对的 情况还是有很多的 而且这个是网络库 我们应该要考虑更多的情况
就着安全性来考虑 我看到muduo
的注释 在调用前lock
住 至少在运行期间tcpconnection
对象不会被析构掉 我觉得这才是最正确的
但为什么else
的时候 还是能调用HandleEventWithGuard
呢 我认为那个时候调用的 里面函数都不是被注册的回调函数 而是默认初始化的… 所以也就还好 最多也就core dump
但也不会出现 没有被EnableReading
就会出现HandleEvent
的情况 在被EnableReading
前 我们是需要Tie
的
这一切就make sense
了 确实得加
一看时间 还没吃晚饭 9:17了 现在程序已经加了 吃完晚饭回来看看自己写的部分运行正确不
运行正确了 下面提一下
主要添加代码这部分还是提一下吧
添加代码如下
最后TcpConnection::ConnectionEstablished
加个Channel->Tie(shared_from_this)
即可
void Channel::HandleEvent() {
if (tied_) {
std::shared_ptr<void> guard = tie_.lock();
HandleEventWithGuard();
} else {
HandleEventWithGuard();
}
}
void Channel::HandleEventWithGuard() {
if (recv_events_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) {
if(read_callback_) {
read_callback_();
}
}
if (recv_events_ & EPOLLOUT) {
if(write_callback_) {
write_callback_();
}
}
}
2、修改了 condition的包装函数名称 Signal/Broadcast -> Notify/NotifyAll
看书看到说 Signal
在C/C++
有不同的含义
而且大家对Signal/Broadcast
的包装函数名字都是这个
那我们也不另辟蹊径了 也不叫另辟蹊径了 改邪归正吧
名称改正了
3、增加基础功能 可在main.cc设置线程数(main.cc 设置线程数)
其实这个很早就应该支持了
但是我之前都一直在httpserver
用static const int
来默认设置线程数
这部分很简单 也就让httpserver
多个可调节线程数函数而已
没什么很多的地方
如果不设置线程数的话 则默认是单线程Reactor
接受数据和处理连接都在同一个线程中
设置线程数 线程数则是一开始就固定的 在运行途中线程数固定
4、拙谈 单线程单进程/单进程多线程/多进程单线程/多进程多线程 适用场合
这个话题 我迟迟没有写到过 也没有提及到过 原因就是 在之前我也思考过这究竟有什么差异
为了我自己彻底搞懂这部分有什么差异 我自己也才写了这部分 所以这一小部分的开头是 拙谈
刚刚和老乡群里面的同学聊了聊天 忽然意识到 这个世界真的是多元化的 我自己有的时候会为了一些 争论一些意见 会自己情绪激动 发表一些很冲 比较激烈的言论 也就是说的不好听一点 叫开喷
我一直觉得我自己在这方面做的很不好 自己应该很多时候 就静下来 听别人 他们聊他们的 我做的我的事情 毕竟以后也不会再有任何交集 忽略别人的意见 抵御外界的言论 这方面我还没有做的很好 以后这方面自己要克制一点 如果这个习惯带到了职场 带到了以后的生活 会吃很多亏的…
必须使用单线程的场合:
1、需要fork
例如fork
exec
等
2、限制CPU使用率 例如8核 用单线程也只不过最多占用12.5%
的CPU
单线程的优势与劣势
优势:简单
劣势:非抢占性的
多线程性能优势:
如果IO 和 CPU两者都很早就到了瓶颈 则无优势
对于有多个CPU 单核机器上多线程没有优势
提供非均质服务 有共享数据
有异步操作
具有可预测的性能
每个模块很清楚划分责任和功能
多线程不可以提高吞吐量 对于运算密集型的
但可以提高响应时间
说了半天 其实我也挺绕的
但最后说两句重点的
究竟怎么抉择 单个进程多线程 和 单线程多进程呢
取决于工作集 如果工作集大 且共享内存也多
为了防止CPU CACHE
频发换进换出 那么就用单进程多线程
但如果工作集小的话 单线程编程更方便 更容易一些的话 就用多进程单线程吧 享受单线程编程的便利
5、取缔了 pthread_t/pthread_self等函数 gettid新王登基
这个之前我记得我就改过一部分了 但好像没有改完
pthread_t
有部分缺点 无法打印输出pthread_t
只能保证在同一进程中同一时刻的线程ID不同 不能包装同一线程先后多个线程ID不同
之前我没有看书的时候 当时去查什么系统调用返回tid
就显示pthread_self
在早期的代码时候 我都是用pthread_self
来返回tid
的
书上推荐使用gettid
本来linux
中的线程 对于调度器而言 也就是进程 没什么区别的 都需要用和进程相同的task_struct
所以gettid
标识符的话 我们就用pid_t
吧 而pid_t
通常是一个小整数(muduo书上写的)我个人觉得不是 因为目前我的操作系统pid
也来到了1000000
我们是用int
来保存的
当然__thread t_cacheTid
是为了线程库 我们才做的优化 那里面存储PID用的是int
5、修改了 增加SIGPIPE信号处理(之前已经解决了)
这里其实之前我们就解决了 这里只是想提一嘴
这也是为什么我们调用webbench
有的时候服务器莫名其妙就消失 意外被终止的原因
当对面提早的关闭了文件描述符 我们再往描述符里面write
就会触发SIGPIPE
信号
我们采取的手段呢 是在eventloop
设置了一个全局变量
忽略了SIGPIPE
信号
就此成功解决了问题 当然如果gdb
来调试的话 也有可能gdb
会提前捕获SIGIPIPE
信号导致gdb
终止 这个时候也需要让gdb
不提前捕获SIGPIPE
信号才能正常运转
class IgnoreSigPipe {
public:
IgnoreSigPipe() {
::signal(SIGPIPE, SIG_IGN);
}
};
6、可控制日志库日志等级(DEBUG/INFO/WARN/ERRNO/FATAL)
刚刚也才完善了这部分的代码 把日志库可以控制输出等级了
刚刚也是好好的修改了两个无用代码
分别是我的SetLogLevel
原本我就没有
还有个是LogLevel
LogLevel
原来把他写成成员函数了 我就说啥情况…
也算是发现惊喜 修改了几个小问题
修改日志库消息等级 直接在main
函数多加一句
调用全局函数SetLogLevel
我们对Logging::DEBUG
Logging::INFO
可以屏蔽掉
但是对于WARN
ERRNO
FAULT
都是无法屏蔽的 除非你修改源代码
把LOG_ERRNO
给注释掉 哈哈
int main(int argc, char* argv[])
{
if (argc <= 1) {
printf("Usage: %s port\n", argv[0]);
return 0;
}
// async logging
/*asynclog.reset(new AsyncLogging());
SetOutputFunc(AsyncOutputFunc);
SetFlushFunc(AsyncFlushFunc);
asynclog->StartAsyncLogging();*/
SetLogLevel(Logger::Level::ERRNO); // 就这里加一句
EventLoop loop;
Address listen_address(argv[1]);
HttpServer server(&loop, listen_address);
server.SetThreadNums(6);
server.SetHttpResponseCallback(HttpResponseCallback);
server.Start();
loop.Loop();
return 0;
}
tiny_muduo::Logger::Level LogLevel();
void SetLogLevel(tiny_muduo::Logger::Level nowlevel);
#define LOG_DEBUG if (LogLevel() <= tiny_muduo::Logger::DEBUG) \
tiny_muduo::Logger(__FILE__, __LINE__, tiny_muduo::Logger::DEBUG).stream()
#define LOG_INFO if (LogLevel() <= tiny_muduo::Logger::INFO) \
tiny_muduo::Logger(__FILE__, __LINE__, tiny_muduo::Logger::INFO).stream()
#define LOG_WARN tiny_muduo::Logger(__FILE__, __LINE__, tiny_muduo::Logger::WARN).stream()
#define LOG_ERRNO tiny_muduo::Logger(__FILE__, __LINE__, tiny_muduo::Logger::ERRNO).stream()
#define LOG_FATAL tiny_muduo::Logger(__FILE__, __LINE__, tiny_muduo::Logger::FATAL).stream()
7、修改了 TCPNODELAY部分 默认打开TCPNODELAY
刚刚打开acceptor
才发现 我的Tcpnodelay
写错了…
里面的打开参数的时候 才发现我的TCPNODELAY
里面是SO_KEEPALIVE
我竟然调用了两次 赶紧改了
然后就把TCP_NODELAY
作为默认设置了
8、修改了 在写入状态不允许Shutdown而后再无Shutdown的问题
刚刚才修正了这个问题 也就是Shutdown
遗留问题
具体是什么问题呢 就是shutdown只在停止Writing
的时候 shutdown
必须要等它shutdown
完了 再做
而之前的话 是没有这个动作的 我们在HandleWrite
部分 如果之前想要Shutdown
但我们那个时候buffer缓冲区中还有没发送的字节
那我们需要在下一次之后等HandleWrite
输出到字节为0的时候 我们再shutdown
由我们的Tcpconnection
状态 disconnecting
来表示
确实之前是想要shutdown
的
之前我的TcpConnection
状态设置的很混乱 现在好了
变为了4个
解决上面问题只需要在HandleWrite
里面添加一句话
if (state_ == kDisconnecting) shutdown;
而 kDisconnecting
则是在调用Shutdown
时设置
void TcpConnection::HandleWrite() {
....
....
....
assert(remaining <= len);
if (!output_buffer_.readablebytes()) {
channel_->DisableWriting();
if (state_ == kDisconnecting) {
Shutdown();
}
}
}
}
9、更新了g++编译选项 影响最大开启了-Wconversion 重新对整个服务器代码进行强制类型转换
主要更新了如下更新选项
SET(CXX_FLAGS
-g
-Wall
-Wextra
-Werror
-Wconversion
-Wshadow
-std=c++11
)
这部分更新选项是看到muduo
的第十章C++ 编译链接模型精要
而做的改动
基本上所有的隐式转换都全部改成了显示转换
而且也对logging
中三个变量的名称做了修改
一个是Outputfunc
一个是Flushfunc
一个是level
由于我们变量名中 设置Logger::Level
中带有这个名字 我们重新为其命名
就用g_Outputfunc
g_Flushfunc
g_level
来替换
还好开了这个编译选项 发现我EventLoop
最核心的有个地方出错了 还好开了
里面所有的隐式转换 还有一些由于作用域局部变量覆盖全局变量的地方 我都全部改过来了
这些编译选项真的太有用了 以后每次编译都带上 免得出现一些低级错误
10、优化了 新增了channel的error回调函数 可显示error错误信息
这部分增加了Channel
的回调函数
因为发现 到最后Webbench
提前关闭连接
会导致很多Error Broken Pipe
发生 故此做了一下增加函数 增加了一个error
的处理
由于没找到muduo
库的strerror_tl
函数的头文件 上网搜了好久 自己也找了好久都没有找到 之后只能退而求其次 使用strerror_r
了
但是额外需要开一个字符数组来存放err信息
之后就在Thread
包装了这个函数 名字为ErrnoToString
本质调用了strerror_r
然后错误存储的地方 是用__thread
开了一个t_errorbuf
11、修改了 日志名字ERROR取名错误问题(低级错误)
原来我怎么把ERROR
等级设置为了ERRNO
了
发现的时候 还是我在设置上面的 想取包装函数的名字才发现的
这也太尴尬了
把很多地方写错单词的都换了回来
12、修改了 日志库负数转换字符串错误的函数(itoa参考实现)
刚刚看书看到最后一部分的时候
看到高效的处理 数字转换为字符串的部分 我才发现之前我写的那个是有问题的 一测试果然发现有问题 之后赶紧修改优化了
在更新优化后 测试了一下INT_MAX
INT_MIN
LONG_MAX
LONG_MIN
一切运行良好
更新函数如下
添加了个static
字符数组 取缔了之前强制转换为char
的问题
static const char digits[] = {'9', '8', '7', '6', '5', '4', '3', '2', '1', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9'};
template <typename T>
void FormatInteger(T num) {
if (buffer_.writablebytes() >= kNum2MaxStringLength) {
char* buf = buffer_.current();
char* now = buf;
const char* zero = digits + 9;
bool negative = num < 0;
do {
int remainder = static_cast<int>(num % 10);
*(now++) = zero[remainder];
num /= 10;
} while (num != 0);
if (negative) *(now++) = '-';
*now = '\0';
std::reverse(buf, now);
buffer_.Add(static_cast<int>(now - buf));
}
}
结束语
这部分可能就到这里了
我原本以为我的代码不需要改动很多 在经历了一天多的折腾 也还算好 找出了这么多问题 自己也挨个挨个修复了
可能里面仍有小部分bug
如果有发现的话 欢迎留言告诉我一下
在写这段话时 muduo
陈硕大佬写的这本书 也已经被我囫囵吞枣地吞完了
为何说是囫囵吞枣 因为我觉得我目前的水平还没有到能够完全理解书里面地每一句话 对于很多东西 我知道那是精华 那是陈硕大佬做了几十年的经验所总结出来的话
就像我觉得有个道理 很早很早我就听过了 但是到现在自从学了计算机这个专业的时候 我就越发的感触的明显
囫囵吞枣 但也没有那么焦急 因为可能现在目前我还没有到那个阶段 可能还需要多沉淀一段时间 之后再回来看这本书的时候 才会忽然有一天恍然大悟
这篇就写到这里吧~ 下一篇是对我们的服务器关掉所有的I/O
正式的压力测试了
那下一篇再见啦~