这一讲的任务不多,就是实现最后一个函数,HandleIo的第二个版本,这个函数是供线程调用的,不过他也挺简单,他负责维护着整个库的运转,好吧,我们直接来看代码:
//=========HandleIo===================
void MyNet::HandleIo(PER_HANDLE_DATA* perHandle, PER_IO_DATA* perIo, DWORD ByteTransfered){
//如果客户下线,那么完成端口会从队列中取出一个操作,我们会发现这个操作中不论是发送还是接收到的字节数为0,这是我们就可以关闭套接字,当然如果客户端没有下线的话,那么ByteTransfered将是接收到的字节数或者是发送出去的字节数。
if (ByteTransfered == 0 &
&
(perIo->_op == RECV_POSTED || perIo->_op == SEND_POSTED)){
//如果客户下线,关闭套接字
CloseSocket(perHandle);
return;
}
m_rcmtx.lock();
//我在下面使用的if……else if……else来判断完成了个什么操作,当然因为我们使用的enum常量,所以我们可以更简单的使用switch……case……完成各项操作
if (perIo->_op == ACCEPT_POSTED){
HandleIo(perIo);
}
else if (perIo->_op == RECV_POSTED){
//如果完成的是一个接收请求的话,就做如下操作
perHandle->DecCount();
//因为我在客户端里面定义了在客户端协构的时候会发送一个"
BYE"
,所以我们也可以通过捕获这个字符串BYE来决定是不是该关闭套接字。
if (strcmp(perIo->_wsabuf.buf, "
BYE"
) == 0){
CloseSocket(perHandle);
}
//下面这个判断是当我们客户连接进来但昵称重复然后客户重新取的新昵称时的操作,因为我在客户端里面指定了新的昵称前面会添加这个UserName:,所以我们可以通过这个判断来确认接收到的信息是不是客户的昵称,如果是就更新客户列表。
else if (std::string(perIo->_wsabuf.buf).find("
UserName:"
) != std::string::npos){
pushUserName(perHandle,perIo);
}
//如果都不是上面两种情况,那么剩下的就是实实在在的聊天消息
else{
//将消息打包,然后转发出去,大家爱怎么打包怎么打包
std::string sendmsg;
sendmsg = perHandle->GetName();
sendmsg += "
:rnt"
;
sendmsg += perIo->_wsabuf.buf;
// 自己注册一个回调函数,下面这里会调用到,当然这也是给用户(使用这个库的人)提供了一个开放强大的操作策略,因为这个函数需要一个单句柄指针,有了这个指针就可以获取当前的操纵的对象,可以手动选择对他发送消息等等,第二个参数是该对象发来的数据,所以用户感兴趣的东西这里都已经给出了。
m_fun(perHandle, sendmsg);
SendAllMsg(sendmsg);
//对当前的句柄投递一个接收请求,这样就能够收到该客户的下一个消息了
RecvMsg(perHandle, perIo);
}
}
else if (perIo->_op == SEND_POSTED){
//如果是发送操作,那么就投递一个接收吧
perHandle->DecCount();
RecvMsg(perHandle, perIo);
}
m_rcmtx.unlock();
}
=====================================
ok,就这样,我们这个服务器网络库算是完成了,下面我们来展示一下如何使用这个库,我们今天先简单的来写一个控制台的例子,下一讲我们再来用Qt写一个窗口程序出来(想必现在很多朋友想要知道C++到底怎么写窗口程序,因为我看到很多留言关于这方面的话题)。
//=================Test MyNet===========
#include "
MyNet.h"
#include <iostream>
using namespace MyCode;
class Fun:public binary_function<PER_HANDLE_DATA,std::string,void>{
public:
void operator()(PER_HANDLE_DATA* p,std::string str){
std::cout<<str<<Std::endl;
}
};
void Fun(PER_HANDLE_DATA* p,std::string str){
std::cout<<str<<std::endl;
}
int main()
try{
MyNet s;
//1.我们使用lambad表达式来这是回调操作
s.SetFun([&
](PER_HANDLE_DATA* p, std::string str){
std::cout <<str <<std::endl;
});
//2.我们使用函数对象来回调处理
s.SetFun(Fun());
//3.我们使用函数指针来操作
s.SetFun(Fun);
//上面的三个操作结果都是一样的,但是操作1的效率可能是最高的,至少也和2是一样的吧,因为操作1是C++11的新特性,我想效率应该不俗,操作3是效率最低的了,因为这是函数指针,无法在线化(内联展开),这就是一开始我们说function模板的强大之处。
s.CreatAndListen();
while (1)
;
return 0;
}
catch (const char* s){
std::cout <<s <<std::endl;
return 0;
}
catch (std::bad_alloc c){
std::cout <<c.what() <<std::endl;
return 0;
}
=================================
就这样,一个网络聊天服务器就出来了,是不是很简单呢?上面我们展示了三种使用这个网络库的方法,等下一讲我们再来展示第四种中法,我们直接将成员函数传递给这个类,让他来处理单句柄上面的消息。
ok,这个网络库我们算是说完了,当然没有经过很严格的测试,但是简单的拿来当群聊服务器目前测试过几遍都没发现问题。
总结一下,我们这个网络库抛开一些我们只要接触后就很好理解的socket外,大部分的功能都使用了STL的算法来解决,将很多看似麻烦的问题简单化了,其实我们还可以使这个库更加紧凑一下更加良好一些,如果我们使用Boost的话,虽然我们使用智能指针更减轻了不少负担,但是智能指针始终还是一个栈对象,每个智能指针需要sizeof(std::share_ptr)个字节,虽然这不算什么,但是当大量的客户连接进来时候就会产生大家的碎片内存,所以如果我们使用Boost里面的内存池或者对象池的话,就可以避免了这种碎片化,这个问题就留给大家去思考吧。
==========================================
回复D或者d直接查看目录,回复相应数字查看章节内容
原文始发于微信公众号(
C/C++的编程教室
):第九十讲 综合运用(5)
|