<TCP网络编程本质>
TCP网络编程最本质是的处理三个半事件:
连接建立:服务器accept(被动)接受连接,客户端connect(主动)发起连接
连接断开:主动断开(close、shutdown),被动断开(read返回0)
消息到达:文件描述符可读
消息发送完毕:这算半个。对于低流量的服务,可不必关心这个事件;这里的发送完毕是指数据写入操作系统缓冲区(如果数据全部填到了内核缓冲区那么调用回掉函数OnWriteComplete函数),将由TCP协议栈负责数据的发送与重传,不代表对方已经接收到数据。对于高流量的服务就要关心这个事件了,因为内核缓冲区可能不足以容纳数据,此时我们要将数据追加到应用层的发送缓冲区,如果内核缓冲区空闲了,那么就把应用层缓冲区中的数据发送出去,发送完了之后,也会调用OnWriteComplete函数。
下图演示了消息到达后和消息发送的数据处理流程:
下面我们看一下TcpSerer如何设置上面三个半事件的回调函数的:
EchoServer类是用户自己实现的类,由于muduo使用的是基于对象编程思想,所以从上面可以看出EchoServer包含TcpServer实例,而EcpServer这个类中又包含了上面set*开头的几个函数,它门分别用于注册链接建立和断开、消息到达、数据发送完毕这三个回调函数,其中setConnectionCallback同时是链接建立和断开的回调函数。所以我们如何使用EchoServer呢?很明显我们可以先定义EchoServer类型,这个类型包含我们自己实现的事件回调函数,并且包含TcpServer实例,然后调用TcpServer实例中的函数注册回调函数。
TcpServer使用实例如下:
EchoServer.h源码如下:
class EchoServer
{
public:
EchoServer(muduo::net::EventLoop* loop,
const muduo::net::InetAddress& listenAddr);
void start(); // calls server_.start();
private:
void onConnection(const muduo::net::TcpConnectionPtr& conn);//链接建立或断开回调函数
void onMessage(const muduo::net::TcpConnectionPtr& conn, //收到消息后回调函数
muduo::net::Buffer* buf,
muduo::Timestamp time);
muduo::net::EventLoop* loop_;
muduo::net::TcpServer server_; //包含注册三个半事件回调函数的方法
};
EchoServer.cpp源码如下:
#include "echo.h"
#include <muduo/base/Logging.h>
#include <boost/bind.hpp>
// using namespace muduo;
// using namespace muduo::net;
EchoServer::EchoServer(muduo::net::EventLoop* loop,
const muduo::net::InetAddress& listenAddr)
: loop_(loop),
server_(loop, listenAddr, "EchoServer")
{//注册回调函数
server_.setConnectionCallback(
boost::bind(&EchoServer::onConnection, this, _1));
server_.setMessageCallback(
boost::bind(&EchoServer::onMessage, this, _1, _2, _3));
}
void EchoServer::start()
{
server_.start();
}
void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
{
LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> "
<< conn->localAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN"); //<span style="font-family: Arial, Helvetica, sans-serif;">conn->connected()判断链接断开还是建立链接 </span>
}
void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn, //只是简单回射
muduo::net::Buffer* buf,
muduo::Timestamp time)
{
muduo::string msg(buf->retrieveAllAsString());
LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, "
<< "data received at " << time.toString();
conn->send(msg);
}
main.cpp源码如下:
#include "echo.h"
#include <muduo/base/Logging.h>
#include <muduo/net/EventLoop.h>
// using namespace muduo;
// using namespace muduo::net;
int main()
{
LOG_INFO << "pid = " << getpid();
muduo::net::EventLoop loop;
muduo::net::InetAddress listenAddr(2007);
EchoServer server(&loop, listenAddr);
server.start();
loop.loop(); //监听事件
}
<这里首先分析什么也不做的EventLoop类,后面再深入分析>
one loop per thread意思是说每个线程最多只能有一个EventLoop对象。
EventLoop对象构造的时候,会检查当前线程是否已经创建了其他EventLoop对象,如果已创建,终止程序(LOG_FATAL)
EventLoop构造函数会记住本对象所属线程(threadId_)。
创建了EventLoop对象的线程称为IO线程,其功能是运行事件循环(EventLoop::loop)
//EventLoop.cpp的代码本身很复杂,这里是把它精简后的源码
#include <muduo/net/EventLoop.h>
#include <muduo/base/Logging.h>
#include <poll.h>
using namespace muduo;
using namespace muduo::net;
namespace
{
// 当前线程EventLoop对象指针
// __thread线程局部存储,也就是每个线程都有一个这个变量
__thread EventLoop* t_loopInThisThread = 0;
}
EventLoop* EventLoop::getEventLoopOfCurrentThread()
{
return t_loopInThisThread;
}
EventLoop::EventLoop()
: looping_(false), //是否处于循环状态
threadId_(CurrentThread::tid()) //把线程id设置为EventLoop对象所属的线程
{
LOG_TRACE << "EventLoop created " << this << " in thread " << threadId_;
// 如果当前线程已经创建了EventLoop对象,终止(LOG_FATAL)
if (t_loopInThisThread)
{
LOG_FATAL << "Another EventLoop " << t_loopInThisThread
<< " exists in this thread " << threadId_;
}
else
{
t_loopInThisThread = this; //设置当前线程EventLoop对象指针
}
}
EventLoop::~EventLoop()
{
t_loopInThisThread = NULL;
}
// 事件循环,该函数不能跨线程调用
// 只能在创建该对象的线程中调用
void EventLoop::loop()
{
assert(!looping_);
// 断言当前处于创建该对象的线程中
assertInLoopThread();
looping_ = true;
LOG_TRACE << "EventLoop " << this << " start looping";
::poll(NULL, 0, 5*1000); //什么都没做只是简单地休息5秒钟
LOG_TRACE << "EventLoop " << this << " stop looping";
looping_ = false;
}
void EventLoop::abortNotInLoopThread()
{
LOG_FATAL << "EventLoop::abortNotInLoopThread - EventLoop " << this
<< " was created in threadId_ = " << threadId_
<< ", current thread id = " << CurrentThread::tid();
}
//EventLoop.h精简后的源码如下
#ifndef MUDUO_NET_EVENTLOOP_H
#define MUDUO_NET_EVENTLOOP_H
#include <boost/noncopyable.hpp>
#include <muduo/base/CurrentThread.h>
#include <muduo/base/Thread.h>
namespace muduo
{
namespace net
{
///
/// Reactor, at most one per thread.
///
/// This is an interface class, so don't expose too much details.
class EventLoop : boost::noncopyable
{
public:
EventLoop();
~EventLoop(); // force out-line dtor, for scoped_ptr members.
///
/// Loops forever.
///
/// Must be called in the same thread as creation of the object.
///
void loop();
//断言EventLoop是否处于创建它的线程当中
void assertInLoopThread()
{
if (!isInLoopThread())
{
abortNotInLoopThread();
}
}
//如果当前线程id和创建EventLoop的线程id一致那么返回true
bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }
static EventLoop* getEventLoopOfCurrentThread();
private:
void abortNotInLoopThread();
bool looping_; /* atomic */ //标志是否处于EventLoop状态
const pid_t threadId_; // 当前对象所属线程ID
};
}
}
#endif // MUDUO_NET_EVENTLOOP_H
正确使用的实例:
#include <muduo/net/EventLoop.h>
#include <stdio.h>
using namespace muduo;
using namespace muduo::net;
void threadFunc()
{
printf("threadFunc(): pid = %d, tid = %d\n",
getpid(), CurrentThread::tid());
EventLoop loop;
loop.loop();
}
int main(void)
{
printf("main(): pid = %d, tid = %d\n",
getpid(), CurrentThread::tid());
EventLoop loop;
Thread t(threadFunc);
t.start();
loop.loop();//只是等待5秒
t.join();
return 0;
}
错误的使用实例:
#include <muduo/net/EventLoop.h>
#include <stdio.h>
using namespace muduo;
using namespace muduo::net;
EventLoop* g_loop;
void threadFunc()
{
g_loop->loop(); //错误!!!在不是创建该EventLoop实例的线程中调用了该实例的loop函数,loop函数不能跨线程调用
}
int main(void)
{
EventLoop loop;
g_loop = &loop;
Thread t(threadFunc);
t.start();
t.join();
return 0;
}