EpServerEngine - 使用 C++ 和 Windows Winsock 的轻量级模板服务器-客户端框架
如何使用模板服务器-客户端框架 EpServerEngine (C++ 和 Windows Winsock) 快速创建服务器-客户端网络。
目录
简介
有时编写服务器/客户端应用程序会非常令人沮丧。对一些人来说,他们可能厌倦了反复编写相同的代码,其中包含大量的初始化。而对另一些人来说,服务器/客户端(Winsock)本身的初始化代码可能过于复杂。我曾有一段时间需要为小型网络编写大量服务器/客户端应用程序,我决定构建一个简单的模板服务器-客户端框架,并决定与大家分享。老实说,我不会推荐在性能是一个非常重要的问题时使用这个框架(我没有进行任何性能测试,也没有与其他实现进行比较),而是将其用于构建快速原型或测试数据包通信。此外,可以将此作为构建自己的模板服务器-客户端框架的指南。
背景
有关“Winsock”的详细信息,请参阅 Microsoft 的 MSDN。Microsoft 提供了使用 Winsock 库创建客户端和服务器的简易指南。我强烈建议您使用 Microsoft Visual Studio 遵循此教程。
可选
- EpServerEngine 中使用的实用库
- EpServerEngine 中使用的锁框架
- “如何为 C++ 同步创建一个简单的锁框架”(作者:Woong Gyu La)
- EpServerEngine 中使用的对象管理
- “用于 C++ 的 SmartObject 类(Objective-C 式内存管理)”(作者:Woong Gyu La)
模板服务器-客户端框架的目的
作为软件开发人员,许多人会同意反复编写相同的代码是非常枯燥的工作。如果代码只是一堆糟糕的初始化代码,那就更糟糕了。这个模板服务器-客户端框架为您处理了所有这些工作,您只需要设计和实现服务器和客户端的数据包结构和数据包处理例程,然后将其余工作交给框架(初始化/等待数据包/创建工作线程/网络互斥等)。
类图
TCP 类
服务器端
BaseServer 类
这是一个基类服务器,负责以下职责:
- 启动服务器
- 停止服务器
- 设置要使用的端口
- 将消息广播给所有已连接的客户端。
- 断开所有已连接客户端的连接(用于紧急情况)
除了上述功能外,它还内部创建一个监听线程,该线程接受客户端连接,并为每个已连接的客户端创建一个工作线程,以与已连接的客户端进行通信。
要创建我自己的服务器,我需要做什么?
这非常简单。
class MyServer: public BaseServer
// MyServer.h
class MyServer:public BaseServer
{
...
virtual BaseServerWorker* createNewWorker();
...
};
// MyServer.cpp
#include "MyServer.h"
#include "MyServerWorker.h"
...
BaseServerWorker* MyServer::createNewWorker()
{
return new MyServerWorker();
}
...
- 创建您的服务器类(例如:
MyServer
) - 使您的类成为
BaseServer
的子类 - 实现“
createNewWorker
”函数(这是“BaseServer
”类中的纯虚函数),该函数返回您实现的服务器工作类。(有关更多详细信息,请参阅“BaseServerWorker
”类部分) - 可选:如果您的服务器需要更多功能,请在您的服务器类(例如:
MyServer
)中实现。 - 完成!
如何启动/停止服务器
您可以非常轻松地如下启动/停止您的服务器:
#include "MyServer.h"
...
MyServer m_server=MyServer(_T("1020")); // 1020 is the port number
if(m_server.StartServer())
{
// server started
}
...
if(m_server.IsServerStarted()) // check if server is started
{
m_server.StopServer(); // Disconnect all clients connected and stop listening
}
...
有关更多详细信息,请参阅 EpServerEngine 的 Wiki 页面,此处。
BaseServerWorker 类
这是一个基类服务器工作者,负责以下职责:
- 启动一个新线程来与客户端通信。
- 向客户端发送/接收消息。
除了上述功能外,它还为每个接收到的数据包创建一个工作线程,以解析数据包并根据接收到的数据包采取行动。
要创建我自己的工作者,我需要做什么?
这与创建服务器一样简单。
class MyServerWorker: public BaseServerWorker
// MyServerWorker.h
class MyServerWorker:public BaseServerWorker
{
...
virtual BasePacketParser* createNewPacketParser();
...
};
// MyServerWorker.cpp
#include "MyServerWorker.h"
#include "MyPacketParser.h"
...
BasePacketParser* MyServerWorker::createNewPacketParser()
{
return new MyPacketParser();
}
...
- 创建您的服务器工作者类(例如:
MyServerWorker
) - 使您的类成为
BaseServerWorker
的子类 - 实现“
createNewPacketParser
”函数(这是“BaseServerWorker
”类中的纯虚函数),该函数返回您实现的数据包解析器类。(有关更多详细信息,请参阅“BasePacketParser
”类部分) - 可选:如果您的服务器工作者需要更多功能,请在您的服务器工作者类(例如:MyServerWorker)中实现。
- 完成!
如何使用这个服务器工作者
基本上,无需任何操作!!!!
您根本不需要做任何事情!!!!
当需要时(例如:当有客户端连接到服务器时),BaseServer
将自动创建您的服务器工作者(例如:MyServerWorker
),并通过调用 createNewPacketParser
函数按需创建/销毁您的数据包解析器。
有关更多详细信息,请参阅 EpServerEngine 的 Wiki 页面,此处。
客户端
BaseClient 类
这是一个基类客户端,负责以下职责:
- 连接到服务器
- 从服务器断开连接
- 向服务器发送/接收消息。
除了上述功能外,它还为每个接收到的数据包创建一个工作线程,以解析数据包并根据接收到的数据包采取行动。
要创建我自己的客户端,我需要做什么?
class MyClient: public BaseClient
// MyClient.h
class MyClient:public BaseClient
{
...
virtual BasePacketParser* createNewPacketParser();
...
};
// MyClient.cpp
#include "MyClient.h"
#include "MyPacketParser.h"
...
BasePacketParser* MyClient::createNewPacketParser()
{
return new MyPacketParser();
}
...
- 创建您的客户端类(例如:
MyClient
) - 使您的类成为
BaseClient
的子类 - 实现
createNewPacketParser
函数(这是BaseClient
类中的纯虚函数),该函数返回您实现的数据包解析器类。(有关更多详细信息,请参阅BasePacketParser
类部分。) - 可选:如果您的客户端需要更多功能,请在您的客户端类(例如:
MyClient
)中实现。 - 完成!
如何连接/断开服务器
您可以非常轻松地如下连接/断开到/从您的服务器:
#include "MyClient.h"
...
MyClient m_client=MyClient(_T("11.222.33.44"),_T("1020")); // 11.222.33.44 is the hostname
// for your server 1020 is the
// port number
if(m_client.Connect())
{
// connected to the server
}
...
if(m_client.IsConnected()) // check if server is started
{
m_client.Disconnect(); // Disconnect from the server
}
...
有关更多详细信息,请参阅 EpServerEngine 的 Wiki 页面,此处。
通用
BasePacketParser 类
这是一个基类数据包解析器,负责以下职责:
- 启动一个新线程来解析接收到的数据包
- 将消息发送给发送了数据包的客户端。
除了上述功能外,它还内部创建一个工作线程来解析数据包并根据接收到的数据包采取行动。
要创建我自己的数据包解析器,我需要做什么?
class MyPacketParser: public BasePacketParser
// MyPacketParser.h
class MyPacketParser:public BasePacketParser
{
...
virtual void ParsePacket(const Packet &packet );
...
};
// MyPacketParser.cpp
#include "MyPacketParser.h"
...
void MyPacketParser::ParsePacket(const Packet &packet )
{
// your parsing routine
}
...
- 创建您的数据包解析器类(例如:
MyPacketParser
) - 使您的类成为
BasePacketParser
的子类 - 实现
ParsePacket
函数(这是BasePacketParser
类中的纯虚函数)。 - 可选:如果您的数据包解析器需要更多功能,请在您的数据包解析器类(例如:
MyPacketParser
)中实现。 - 完成!
如何使用这个数据包解析器
基本上,无需任何操作!!!!
您根本不需要做任何事情!!!!
当需要时(例如:当客户端/服务器工作者接收到数据包时),BaseServerWorker
或 BaseClient
将自动创建您的数据包解析器(例如:MyPacketParser
)。
有关更多详细信息,请参阅 EpServerEngine 的 Wiki 页面,此处。
Packet 类
这是一个数据包类,负责以下职责:
- EpServerEngine 用于服务器和客户端之间通信的基本数据单元。
如何使用这个数据包类?
- 数据包类型声明
typedef enum _sendPacketType{
SEND_PACKET_TYPE_DOSOMETHING=0,
SEND_PACKET_TYPE_DOSOMETHING2,
SEND_PACKET_TYPE_DOSOMETHING3,
}SendPacketType;
struct SendPacket{
SendPacketType packetType;
unsigned int magicNum;
};
例如,如果像上面那样定义了数据包结构,您可以通过如下方式创建数据包对象并将其发送到服务器/客户端。
SendPacket sendPacket;
sendPacket.packetType=SEND_PACKET_TYPE_DOSOMTHING2;
sendPacket.magicNum = 1;
Packet packet(&sendPacket // pointer to the data
, sizeof(SendPacket), // byte size of the data
, true // flag whether to allocate the memory
// for the data within Packet Object or not.
);
- 请注意,由于服务器开发中复制数据包内存对性能至关重要,您可以根据情况选择是为
Packet
对象分配内存,还是仅在Packet
对象中持有数据的指针。
有关更多详细信息,请参阅 EpServerEngine 的 Wiki 页面,此处。
PacketContainer 类
这是一个数据包容器类,负责以下职责:
- 能够容纳具有数据包结构后可变长度数组的可变长度数据包。
如何使用这个数据包容器类?
- 数据包类型声明
typedef enum _sendPacketType{
SEND_PACKET_TYPE_DOSOMETHING=0,
SEND_PACKET_TYPE_DOSOMETHING2,
SEND_PACKET_TYPE_DOSOMETHING3,
}SendPacketType;
struct SendPacket{
SendPacketType packetType;
unsigned int magicNum;
};
例如,如果像上面那样定义了数据包结构,您可以通过如下方式创建数据包容器对象,并将具有可变长度数组的数据包数据传输到数据包对象中,然后将其发送到客户端/服务器。
...
PacketContainer<SendPacket> packetContainer;
Packet sendPacket;
packetContainer=PacketContainer<SendPacket>(0 // default array size addition to SendPacket
, true // flag whether to allocation memory
// within PacketContainer object or not
);
packetContainer.GetPacketPtr()->packetType=SEND_PACKET_TYPE_DIDSOMETHING;
CString someString=_T("Hello");
// Add given array after the SendPacket struct.
packetContainer.SetArray(reinterpret_cast<const char*>(someString.GetString()),(someString.GetLength()+1)*sizeof(TCHAR));
// Transfer to Packet object to send the packet with variadic array to Server/Client.
sendPacket=Packet(reinterpret_cast<const void*>(
packetContainer.GetPacketPtr()) // returns the pointer to the SendPacket object
// followed by array set above.
,packetContainer.GetPacketByteSize() // returns the byte size of SendPacket Struct
// with array.
,false // Since the memory is allocated
// in PacketContainer already,
// don't allocate in Packet Object
// for Performance increase
);
- 请注意,由于服务器开发中复制数据包内存对性能至关重要,您可以根据情况选择是为
PacketContainer
对象分配内存,还是仅在PacketContainer
对象中持有数据的指针。
有关更多详细信息,请参阅 EpServerEngine 的 Wiki 页面,此处。
UDP 类
UDP 类与 TCP 类非常相似,我认为在此处再次解释几乎相同的类是在浪费空间和时间。
如果您需要更多详细信息,请参阅下面的 EpServerEngine wiki 页面,
结论
正如我在引言中所说,这个框架在性能方面可能不是最好的。然而,由于使用这个框架可以让你非常快速地构建服务器和客户端。我相信它对于开发原型服务器/客户端应用程序或测试数据包通信非常有帮助,因为它让你忽略了 Winsock 和线程的所有繁琐初始化,从而能够专注于服务器/客户端通信设计。希望这对您的服务器/客户端开发有所帮助。
还可以查看 EpServerEngine.cs,这是一个用 C# 编写的异步轻量级 IOCP TCP 模板服务器框架。
参考
- EpLibrary 2.0
- EpServerEngine
- MSDN 上的 Winsock
- “如何为 C++ 同步创建一个简单的锁框架”(作者:Woong Gyu La)
- “用于 C++ 的 SmartObject 类(Objective-C 式内存管理)”(作者:Woong Gyu La)
- EpServerEngine 的 wiki 页面
历史
- 2013.08.22:根据 MIT 许可重新分发
- 2013.08.17:添加了示例源代码。
- 2013.01.25:更新了下载链接。
- 2012.11.17:移至正确的部分。
- 2012.11.16:添加了类图。
- 2012.11.15:提交了文章。