Socket
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
socket()函数
int socket(int protofamily, int type, int protocol);//返回sockfd
sockfd是描述符。
...
fd(文件描述符)
TManageServer ManageServer;
实例化ManageServer,创建一个服务端。在服务端进行各种处理。
TTcpServer::TTcpServer(TNotify& notify, size_t nBulkLen)
TTcpServer构造函数中,对变量初始化
- TTcpServer::TTcpServer(TNotify& notify, size_t nBulkLen)
- : m_IsListen(false), m_Poll_Fd(::epoll_create1(EPOLL_CLOEXEC)),
- m_BlockMgr(nBulkLen), m_Notify(notify)
- {
- int loopnum = std::min(sysconf(_SC_NPROCESSORS_ONLN) * 2 + 2, 16L);
- for(int i = 0; i < loopnum; i++)
- {
- TEventLoop* loop = new TEventLoop(m_BlockMgr, m_ClientMgr, notify);
- m_EventLoops.push_back(loop);
- }
- }
定义:bool TManageServer::Listen()
if (m_TcpServer.Listen((unsigned short)atoi(cfg)))
{
...
声明:class TManageServer : public TTcpServer::TNotify
TManageServer 类中声明了TCPServer的实例。
TTcpServer m_TcpServer;
定义:bool TTcpServer::Listen(const unsigned short port)
TCPServer实例调用TCPServer的listen函数
创建一个//socket
accept_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
//端口重用,快速重启服务
- int opt = 1;
- :setsockopt(accept_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
-
- //bind
- sockaddr_in addr;
- memset(&addr, 0, sizeof(sockaddr_in));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- ::inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr);
- if (0 != ::bind(accept_fd, (sockaddr*)&addr, (socklen_t)sizeof(sockaddr_in)))
-
-
- //listen
- if (0 != ::listen(accept_fd, SOMAXCONN))
-
- //add epoll
- epoll_event event;
- event.data.u64 = 0; //清空数据
- event.data.fd = accept_fd; //绑定client指针
- event.events = POLLIN | POLLPRI;
- ::epoll_ctl(m_Poll_Fd, EPOLL_CTL_ADD, accept_fd, &event);
-
//启动accept工作线程
Start();// if (0 != ::pthread_create(&m_Thread, 0, &TThread::ThreadFunction, (void*)this))// Start()进入创建线程,将这个实例作为参数导入,运行起点函数为标准线程函数,将传入的参数指针转换为线程类指针,指向run,进行启动(class TTcpServer : public TThread)TThread是一个抽象类,在这里重写了RUN函数。
//accept工作线程来自TTcpServer ,所以指向这个函数
- void TTcpServer::Run()
- {
- epoll_event ActiveEvents[1]; //应该1个就够用了
-
- while (!isTerminated())
- {
- int ActiveCount = ::epoll_wait(m_Poll_Fd, ActiveEvents, sizeof(ActiveEvents)/sizeof(epoll_event), -1);
- if (ActiveCount <= 0) //EINTR 4号错误 epoll_wait调用被系统打断
- continue;
-
- //处理active事件
- if (0 == ActiveEvents[0].data.fd)
- continue;
-
- do_accept(ActiveEvents[0].data.fd);
- }
- }
//启动eventloop工作线程
for(std::vector::iterator iter = m_EventLoops.begin(); iter != m_EventLoops.end(); iter++)
(*iter)->Start();
// Start()同上,也是来自TThread的创建线程,同时将参数指向this,转换为TEventLoop
的RUN
class TEventLoop : public TThread
在这TEventLoop 也继承自TThread,所以重写RUN函数
- void TEventLoop::Run()
- {
- epoll_event ActiveEvents[128];
-
- while (!isTerminated())
- {
- int ActiveCount = ::epoll_wait(m_Poll_Fd, ActiveEvents, sizeof(ActiveEvents)/sizeof(epoll_event), -1);
- if (ActiveCount <= 0) //EINTR 4号错误 epoll_wait调用被系统打断
- continue;
-
- //处理active事件
- for(int i = 0; i < ActiveCount; i++)
- {
- if (0 == ActiveEvents[i].data.ptr) //无需读取,一直增长没关系
- continue;
-
- do_active((TClientMgr::ClientCell*)ActiveEvents[i].data.ptr, ActiveEvents[i].events);
- }
-
- //执行任务队列
- do_task();
- }
- }
对于eventLoop,在TTcpServer的构造函数中进行创建,返回的fd值赋值给m_Poll_Fd
1. m_Poll_Fd(::epoll_create1(EPOLL_CLOEXEC)),
作为函数返回值,epoll_create()返回了代表新创建的epoll实例的文件描述符。这个文件描述符在其他几个epoll系统调用中用来表示epoll实例。flags参数目前只支持一个flag标志:EPOLL_CLOEXEC,它使得内核在新的文件描述符上启动了执行即关闭标志。
2.
系统调用epoll_ctl()能够修改由文件描述符epfd所代表的epoll实例中的兴趣列表。
- //add epoll
- epoll_event event;
- event.data.u64 = 0; //清空数据
- event.data.fd = accept_fd; //绑定client指针
- event.events = POLLIN | POLLPRI;
- ::epoll_ctl(m_Poll_Fd, EPOLL_CTL_ADD, accept_fd, &event);
创建epoll_event实例,清空,绑定accept_fd
- events字段是一个位掩码,它指定了我们为待检查的描述符fd上所感兴趣的事件集合。
- data字段是一个联合体,当描述符fd稍后称为就绪态时,联合的成员可用来指定传回给调用进程的信息。
- struct epoll_event
- {
- uint32_t events; /* Epoll events */
- epoll_data_t data; /* User data variable */
- } __attribute__ ((__packed__));
参数accept_fd指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是、套接字设备,甚至是另一个epoll实例的文件描述符。
EPOLL_CTL_ADD:将描述符accept_fd添加到epoll实例中的兴趣列表中去。对于accept_fd上我们感兴趣的事件,都指定在event所指向的结构体中。
.
TEventLoop构造函数中也有epoll_create1、epoll_ctl。不同于accept_fd
- 最终
TTcpServer类中的 std::vector m_EventLoops变量
在监听函数中被调用
for(std::vector::iterator iter = m_EventLoops.begin(); iter != m_EventLoops.end(); iter++) (*iter)->Start();
迭代器遍历TEventLoop*,通过Start()启动RUN()
这条语句中,在TEventLoop*这个vector的迭代器,指向了 class TEventLoop : public TThread
TEventLoop类中的Run()
1 struct epoll_event ev; 2 //设置与要处理的事件相关的文件描述符 3 ev.data.fd=listenfd; 4 //设置要处理的事件类型 5 ev.events=EPOLLIN|EPOLLET; 6 //注册epoll事件
0 extern int epoll_wait (int __epfd, struct epoll_event *__events,11 int __maxevents, int __timeout);12 13
#define POLLIN 0x001 /* There is data to read. */
#define POLLPRI 0x002 /* There is urgent data to read. */
#define POLLOUT 0x004 /* Writing now will not block. */
该函数用于轮询I/O事件的发生;
参数: epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值(单位应该是ms);-1相当于阻塞,0相当于非阻塞。一般用-1即可
返回值:返回发生事件数。如出错则返回-1。
3.事件等待:epoll_wait()
系统调用epoll_wait()返回epoll实例中处于就绪态的文件描述符信息。单个epoll_wait()调用能够返回多个就绪态文件描述符的信息。
int ActiveCount = ::epoll_wait(m_Poll_Fd, ActiveEvents, sizeof(ActiveEvents)/sizeof(epoll_event), -1);
epoll_wait被调用
参数evlist所指向的结构体数组中返回的是有关就绪态文件描述符的信息。(结构体epoll_event已经在上一节中描述。)数组evlist的空间由调用者负责申请,所包含的元素个数在参数maxevents中指定。
在数组evlist中,每个元素返回的都是单个就绪态文件描述符的信息。events字段返回了在该描述符上已经发生的事件掩码。data字段返回的是我们在描述符上使用epoll_ctl()注册感兴趣的事件时在ev.data中所指定的值。注意,data字段是唯一可获知同这个事件相关的文件描述符的途径。因此,当我们调用epoll_ctl()将文件描述符添加到感兴趣列表中时,应该要么将ev.date.fd设为文件描述符号,要么将ev.date.ptr设为指向包含文件描述符号的结构体。
参数timeout用来确定epoll_wait()的阻塞行为,有如下几种。
如果timeout等于-1,调用将一直阻塞,直到兴趣列表中的文件描述符上有事件产生或者直到捕获到一个信号为止。
调用成功后,epoll_wait()返回数组evlist中的元素个数。如果在timeout超时间隔内没有任何文件描述符处于就绪态的话,返回0.,出错时返回-1,
默认情况下,一旦通过epoll_ctl()的EPOLL_CTL_ADD操作将文件描述符添加到epoll实例的兴趣列表中后,它会保持激活状态(即,之后对epoll_wait()的调用会在描述符处于就绪态时通知我们)直到我们显示地通过epoll_ctl()的EPOLL_CTL_DEL操作将其从列表中移除。如果我们希望在某个特定的文件描述符上只得到一次通知,那么可以在传给epoll_ctl()的ev.events中指定EPOLLONESHOT标志。如果指定了这个标志,那么在下一个epoll_wait()调用通知我们对应的文件描述符处于就绪态之后,这个描述符就会在兴趣列表中被标记为非激活态,之后的epoll_wait()调用都不会再通知我们有关这个描述符的状态了。如果需要,我们可以稍后用过调用epoll_ctl()的EPOLL_CTL_MOD操作重新激活对这个文件描述符的检查。
使用epoll_wait对某个文件描述符进行事件监听,监听到事件后会返回相关的结构体,得到其中有事件到来的fd,使用对应的回调函数(手动实现fd到回调函数的映射)来处理该fd上的事件:读数据或者写数据之类的。
Epoll通过检测,当有线程被激活时候,对这里进行读写。
一个accept工作线程,多个eventloop工作线程。
初始化:先生成一个eventfd,初始化计数器为1,再生成一个空队列Q和互斥锁,此eventfd,队列Q和互斥锁可以通过一些方法在下面两个线程间共享,
线程A:处理一些来自外部的请求,每处理完一个请求后会从eventfd的计数器read数据,加1之后再write,将处理结果写入到队列末尾,然后接着处理下一个请求。
线程B:对eventfd进行Epoll监听,回调函数的功能是对eventfd的计数器read数据出来然后判断,如果大于1就自减1然后从队列头部取出数据,并将结果进行分发
,最后再写入新的计数器数据。如果等于1那么就直接返回,代表没有新的数据到来。
epoll_wait运行的原理是 等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。 并且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。
试试:
开始监听
首先构造函数中 m_Poll_Fd(::epoll_create1(EPOLL_CLOEXEC)),
创建一个epoll实例,描述值给到m_Poll_Fd,处理accept
创建socket连接,描述值给到accept_fd
创建sock端口地址addr
accept_fd bind addr
开始监听
创建epoll结构 event 对其清空,绑定accept_fd这个描述值
m_Poll_Fd注册,往epoll对象中增加一个accept_fd流的某一个事件event
即有缓冲区内有数据时epoll_wait返回
启动accept工作线程,实际上是启动了 TTcpServer::Run()
创建一个 epoll_event结构 ActiveEvents[1];
循环等待epoll_wait,m_Poll_Fd这epoll等待自身注册的accept事件发生,处理ActiveEvents的事件 do_accept(ActiveEvents[0].data.fd);
ActiveEvents是epoll_wait函数填写的,每次清空,是由m_Poll_Fd注册的accept_fd,绑定的addr
在accpect线程之中,TTcpServer::do_accept(int fd)
Do_accecpt之中
TCP进行监听过程中,会监听客户端的请求,通过accept接受请求,建立好连接,进行读写
int client_fd = ::accept4(fd, ...
监听ActiveEvents,返回制定客户端的地址。client_fd是客户端的描述字
设置client_fd的缓冲区。同时定义接受和发送端的数据缓冲区
设置keepalive,当tcp发现有keepidle秒未收到对端数据后,开始以间隔keepinterval秒的频率发送的空心跳包,如果连续keepcount次以上未响应代码对端已经down了,close连接
将client_fd分配个给多客户端管理类,是一个ClientCell,关注的事件自己分配,
ClientCell的ClientFd即为client_fd
Add(client);,即将ClientCell的client,赋值给taskcell
最终PutTask( taskcell),taskcell写入双缓冲
eventfd_write(m_Wakeup_Fd, 1);执行写入fd(m_Wakeup_Fd是一个eventfd)
active线程处理完毕
启动eventloop工作线程,即启动TEventLoop::Run()
同样,创建 epoll_event 结构 ActiveEvents[128];
m_Poll_Fd等待accept事件发生,处理ActiveEvents
do_active((TClientMgr::ClientCell*)ActiveEvents[i].data.ptr, ActiveEvents[i].events);
之后 执行任务队列 do_task();
处理eventloop线程 TEventLoop::Run()
同样创建epoll_event结构 ActiveEvents
循环等待epoll_wait,m_Poll_Fd这epoll等待自身注册的accept事件发生,处理
TEventLoop::do_active内部,首先判断
是错误、关闭EPOLLHUP | EPOLLERR | EPOLLRDHUP))?是读、外带数据(EPOLLIN | EPOLLPRI))?是写POLLOUT)?
错误就task_del
之后进行do_recv,执行ActiveEvents
判断有没有错误,缓冲有诶有空间
对数据进行处理,进入OnRecvData
如果是POLLOUT写数据就do_send
处理发送, 直到发送失败为止,选择发送方式批量模式还是普通发送模式
最后 do_task();执行队列任务,判断之前置位的taskcell->Action)
执行task_sendtask_addtask_del,追加发送,等 //任务回调函数
OnRecvData检测帧头
进行OnDistribute数据分发
在TManageServer构造函数中,将每一个线程
m_ThreadVec.push_back(new TDealThread(*this));push进去vector
在TDealThread中start中有创建了线程
进行TDealThread::Run()、
创建双缓冲, m_Buffer->TakeWait(buf, len);
对读到的数据进行OnFrame.
这个线程应该是双缓冲线程
OnFrame中进行数据库的连接,调用存储过程
查找协议号,对请求进行执行
(SHead->ProtocolCode)case到CMD_M_CurrencyChangeInfoAdd_Req:这个请求
就执行 OnCurrencyChangeInfoAdd(buf, len);
处理该请求,调用存储过程等
- 创建socket
accept_fd=socket(AF_INET,SOCK_STREAM,0)
- 设置socket属性。
setsockopt(accept_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))
SO_REUSEADDR,打开或关闭地址复用功能。
- 准备通信地址,就是服务端的地址,初始化,设置为IPV4,事件为端口地址
sockaddr_in addr;
{addr.sin_family = AF_INET;
addr.sin_port = htons(port);
::inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr)}监听9999端口
- 端口地址绑定socket,
bind(accept_fd, (sockaddr*)&addr, (socklen_t)sizeof(sockaddr_in))
bind()函数就是将给这个描述字accept_fd绑定一个地址。
- 开始监听listen(accept_fd, SOMAXCONN)
要监听的描述字,最大连接数。listen是被动的,等待客户的请求。
- epoll初始化,创建epoll描述符。
int epollfd;
epollfd=epoll_create(EPOLL_CLOEXEC);
在ITAP中,m_Poll_Fd=epollfd
- 将accept_fd封装进入event里面,初始化event
epoll_event event;
event.data.u64 = 0; //清空数据
event.data.fd = accept_fd; //绑定client指针
event.events = POLLIN | POLLPRI;
- epoll_ctl设置属性,注册事件
epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&EPOLLIN| POLLPRI)
Itap: epoll_ctl(m_Poll_Fd, EPOLL_CTL_ADD, accept_fd, &event);
向m_Poll_Fd这个epoll对象添加一个(socket)accept_fd流的事件
- 缓冲区内有数据时epoll_wait返回
int ret=epoll_wait(epollfd,eventList,MAX_EVENTS,timeout);
ret会返回在规定的时间内获取到IO数据的个数,并把获取到的event保存在eventList中,注意在每次执行该函数时eventList都会清空,由epoll_wait函数填写。
Itap中:
int ActiveCount = ::epoll_wait(m_Poll_Fd, ActiveEvents, sizeof(ActiveEvents)/sizeof(epoll_event), -1);
- 循环读取eventList中的值
epoll直接就把有事件的文件描述符按顺序保存在eventList中
判断envent的值 if (events & (EPOLLHUP | EPOLLERR | EPOLLRDHUP))进行相应的操作send/rev
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。
创建一个自身的socket,接受流。监听的端口只要有一个9999
被动地接收消息,只需要一个接口就可以了
Accept这个线程只需要一个,当事件有活动,就进行分发,分发到线程池里面,client递增。
currloop(unsigned int clientid) { return (m_EventLoops[clientid % m_EventLoops.size()]); }求余数
Eventloop线程池,对接受到的epoll进行处理,自动选择有时间发生的线程进行处理。
每一个线程都有一个epoll-wait,循环等待
m_Poll_Fd是自身的一个epoll描述字。
每一个ClientCell都有连接FD,关注事件等
感悟:
首先噢我觉得光看是没有用的,已经看了一个星期epoll,我应该写一个小的管理。
其次,说一下EPOLL
首先创建一个socket对9999进行监听
这个就是用来接受999的数据流的
进而创建一个epoll对象,将socket注册到这个epoll,进行数据的接收
创建一个接收和分发的工作线程,m_Poll_Fd进行等待,当m_Poll_Fd满足的事件(ClientFd或者是wakeup)发生时候(读写),出参写入活动事件。进行处理
这个活动事件描述字接收数据后,将客户的地址传出来
给活动事件分配client,分配一个FD和地址,id是递增的,然后对现在的eventloop中添加新建的客户端。设定任务执行方式,写入eventfd描述字的唤醒fd
创建一个eventloop工作线程
事件轮询,当active事件发生((0 == ActiveEvents[i].data.ptr),处理活动事件,选择执行删除,接收或者发送(轮询执行函数)
执行任务队列,获取缓冲区的东西,对任务缓冲区所有的任务进行处理,选择是发送、添加、还是删除(任务回调函数)
在接受轮训执行函数中,接收数据。对数据帧进行解密,分发,然后写入缓存,PutBuffer,putwait
在TManageServer的函数中创建了10个线程,和一个心跳线程
在每一个线程中进行TakeWait,等待接受,接收到之后处理帧头和数据会话头
在OnFrame中进行数据库的连接,对渠道的数据帧进行检索,对特殊处理的单独函数进行处理,否则进入默认处理,在默认处理中根据协议,偏移和长度,拼出sql语句,对数据库的otl流进行传入参数和读取输出的操作。
输出的结果集存入rspdata中,将这个RSP的头进行发送,偏移一个会话头的数据
在加密发送函数中调用m_TcpServer的发送数据函数,寻找现在的事件clientid,找出之后进入TEventLoop::Send,PutTask为发送标志,存入任务缓存,写入唤醒描述字m_Wakeup_Fd,等待发送
读取配置中的用户名,代码中的到期日,本机的mac地址
读取u.aut的授权文件,进行验证。
启动事件日志、文本日志进程
处理协议文件
========================
TManageServer实例化了ManageServer
构造函数中对四个线程进行初始化,每一个线程传入ManageServer对象,调用各自的构造函数,进行各自的实例化。m_TcpServer特殊传入的ManageServer对象作为一个notify对象。
TNotify是TTcpServer的嵌套类。TManageServer继承自TNotify
TTcpServer的构造函数传入的是TManageServer对象,notify作为对象的引用,直接操纵对象本身: TTcpServer(TNotify& notify, size_t nBulkLen)
在TTcpServer构造函数中:m_Notify(notify)对TTcpServer的TNotify的引用成员变量m_Notify进行初始化,赋值为ManageServer整个对象
至此,TNotify的m_Notify就是ManageServer本身了。
在TEventLoop中,就可以通过m_Notify来调用ManageServer的OnRecvData函数
===================================
TManageServer构造函数中创建了m_TcpServer,TTcpServer构造函数中创建了至少16个TEventLoop的对象,notify均为ManageServe。每一个对象添加到vector:m_EventLoops之中。作为线程池,每一个均有一个m_Poll_Fd,均注册了m_Wakeup_Fd。
=========================
M_client 如果为空,就新建一个client用完之后回收,push到M_client之中。,如果M_client取完了,就在新建一个,每次从之中取,M_client减一
=====================================
TTcpClient 只有在心跳时候调用了?
========================================
TTcpServer在TManageServer实例化的时候,构造函数中对m_TcpServer赋值为ManageServer本身这个对象。
m_ClientMgr在TTcpServer类中是一个TClientMgr的对象,没有被初始化。
在TEventLoop的构造函数中,m_ClientMgr赋值为TTcpServer的TClientMgr对象。
m_ClientMgr在TEventLoop类中是一个引用成员变量,是TClientMgr的引用,直接操纵TClientMgr本身
难道不需要为TClientMgr分配内存,需要,在分配中 new了
m_Wakeup_Fd被注册到了m_Poll_Fd这个之中
active线程:只做一件事,(tcpserver中的m_Poll_Fd)绑定了accept_fd。,当accept_fd有读入时候通知Poll_Fd。Poll_Fd会在tcpserver的accept线程中等待。epoll_wait返回给用户tcpserer中的m_Poll_Fd中满足注册条件(accept_fd)的事件,do_accept中接收该端口的信息,设定KeepAlive,对其分配ClientCell,
通过currloop求余找出clientid应该分配的m_EventLoops线程数组的编号,进入该线程的ADD函数PutTask,将监听到的taskcell的Client数据put进入m_TaskBuffer双缓存,并且m_Wakeup_Fd(eventloop唯一)写入1。即该线程进行接收和分发Client,并激活Wakeup
eventloop线程池:(eventloop中的m_Poll_Fd)绑定了m_Wakeup_Fd。,当Wakeup有数据变化时候通知Poll_Fd。Poll_Fd会在TEventLoop中等待,当有通知时候,返回事件的集合ActiveEvents[]。
处理active:对事件集合ActiveEvents分别执行do_active。区别task_del、do_recv、do_send。如果是接收则OnRecvData,从ClientFd读取数据,进行OnDistribute,调用不同的线程(m_SettleThread四个、m_ThreadVec一般的十个)进行PutBuffer操作。该线程用来接收数据,将数据进行put,或者将数据发送到ClientFd,或者是删除操作,取消这个ClientFd的m_Poll_Fd注册,回收client。
执行任务队列:对事件打上不同的处理标志之后,do_task() 进行m_TaskBuffer.Take(buf, len),取出缓冲区的Client数据,按照取出的数据分配任务执行 task_send、 task_add、 task_del ,
发送则将数据写入缓存,后发送到ClientFd。
添加则将这个client注册到m_Poll_Fd进行监听,然后插入到m_LinkMap==??
=======================================
同时,在thread的十个线程的Run之中进行TakeWait,取到的buf执行OnFrame,对不同的协议进行特殊操作,一般的DealData,执行otl_stream,输入参数,读取结果集,调用Send_Lzo_Idea_Block_Notice进行发送SendData,之中获取当前(clientid),进行Send,实质是将TaskCell的ClientId、Block等进行put进入m_TaskBuffer双缓冲,并eventfd_write激活m_Wakeup_Fd。如果EventLoop任务队列已满则返回错误。
=====
Clinet 在 构造中初始化分配内存