- 使用的语言为C/C++
- 源码支持的平台为:Windows(本文项目全部使用windows平台下vs2019开发,故本文项目不支持linux平台)
一、思路与准备
之前的客户端虽然可以跑起来,但是声明和实现全写于一个hpp文件中,随着代码日渐增多,增删改变得越发困难。所以我决定尝试将其实现的更加标准。本次我准备的内容如下:
我首先准备将之前服务端源代码的hpp文件进行分离,分离成单独类声明与实现。且服务端本体源码放在一个项目中。随后新建静态库项目存储内存池源码,使服务端源码项目链接内存池静态库。最后新建一个动态库项目,里面存放计时器类代码和报文CMD文件,因为服务端和客户端程序都要用这个库里的文件,为了今后方便改动,我选择使用动态库。
二、服务端本体 项目
1. 思路
首先,服务端源码按功能可分为五个部分:
- 服务端基础接口部分。此部分定义了几个服务端的基本操作,可以通过继承重写这几个基本操作实现不同的功能。
- 服务端主线程部分。此部分调用基础的socket函数,与客户端建立socket连接,仅监控是否有新客户端加入。
- 客户端类部分。每当有新客户端加入,都会新建一个客户端对象,通过该客户端对象(获取socket/使用缓冲区)发送网络报文。
- 子线程类任务处理(发送)部分。此类每一个对象即为一条新的子线程,用来处理服务端与客户端之间的网络报文发送任务。
任务处理接口。通过重写该接口,实现自己的任务处理方式。
- 子线程类接收部分。此类每一个对象即为一条新的子线程,用来处理监控服务端与客户端之间的网络报文接收。
重写任务处理接口方法。实现自己的任务处理方式。
按照五个部分的关系等,可画出如下的关系图:

由此,我令 ⑤部分 include ①③④部分,再令 ②部分 include ⑤部分,即可实现项目各文件间的关联。
2. 头文件源码
①服务端基础接口部分
INetEvent.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
#ifndef _INET_EVENT_H_ #define _INET_EVENT_H_
class ClientSocket; class CellServer; struct DataHeader;
class INetEvent { public: virtual void OnNetJoin(ClientSocket* pClient) = 0; virtual void OnNetLeave(ClientSocket* pClient) = 0; virtual void OnNetMsg(CellServer* pCellServer, ClientSocket* pClient, DataHeader* pHead) = 0; virtual void OnNetRecv(ClientSocket* pClient) = 0; };
#endif
|
②服务端主线程部分
TcpServer.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
#ifndef _TCP_SERVER_H_ #define _TCP_SERVER_H_
#include"CellServer.h" #include<stdio.h> #include<atomic>
class TcpServer : INetEvent { public: TcpServer(); virtual ~TcpServer(); int InitSocket(); int Bind(const char* ip, unsigned short port); int Listen(int n); int Accept(); void CloseSocket(); void AddClientToServer(ClientSocket* pClient); void Start(int nCellServer); inline bool IsRun(); bool OnRun(); void time4msg(); virtual void OnNetJoin(ClientSocket* pClient); virtual void OnNetLeave(ClientSocket* pClient); virtual void OnNetMsg(CellServer* pCellServer, ClientSocket* pClient, DataHeader* pHead); virtual void OnNetRecv(ClientSocket* pClient);
private: SOCKET _sock; std::vector<CellServer*> _cellServers; mytimer _time; std::atomic_int _msgCount; std::atomic_int _recvCount; std::atomic_int _clientCount; };
#endif
|
③客户端类部分
ClientSocket.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
#ifndef _CLIENT_SOCKET_H_ #define _CLIENT_SOCKET_H_
#ifdef _WIN32 #define FD_SETSIZE 1024 #define WIN32_LEAN_AND_MEAN #include<winSock2.h> #include<WS2tcpip.h> #include<windows.h> #pragma comment(lib, "ws2_32.lib") #include "pch.h" #pragma comment(lib, "guguDll.lib") #pragma comment(lib, "guguAlloc.lib") #else #include<sys/socket.h> #include<arpa/inet.h> #include<unistd.h> #include<string.h>
#define SOCKET int #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) #endif
#ifndef RECV_BUFFER_SIZE #define RECV_BUFFER_SIZE 4096 #define SEND_BUFFER_SIZE 40 #endif
class ClientSocket { public: ClientSocket(SOCKET sockfd = INVALID_SOCKET); virtual ~ClientSocket(); SOCKET GetSockfd(); char* MsgBuf(); int GetLen(); void SetLen(int len); int SendData(DataHeader* head);
private: SOCKET _sockfd; char* _Msg_Recv_buf; int _Len_Recv_buf;
char* _Msg_Send_buf; int _Len_Send_buf; };
#endif
|
④子线程类任务处理(发送)部分
CellTask.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
#ifndef _CELL_Task_hpp_ #define _CELL_Task_hpp_
#include<thread> #include<mutex> #include<list> #include <functional>
class CellTask { public: virtual void DoTask() = 0; };
class CellTaskServer { public: CellTaskServer(); virtual ~CellTaskServer(); void addTask(CellTask* ptask); void Start();
protected: void OnRun();
private: std::list<CellTask*>_tasks; std::list<CellTask*>_tasksBuf; std::mutex _mutex; };
#endif
|
⑤子线程类接收部分
CellServer.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
|
#ifndef _CELL_SERVER_H_ #define _CELL_SERVER_H_
#include"INetEvent.h" #include"CellTask.h" #include"ClientSocket.h"
#include <vector>
class CellSendMsgTask : public CellTask { public: CellSendMsgTask(ClientSocket* pClient, DataHeader* pHead) { _pClient = pClient; _pHeader = pHead; }
virtual void DoTask() { _pClient->SendData(_pHeader); delete _pHeader; }
private: ClientSocket* _pClient; DataHeader* _pHeader;
};
class CellServer { public: CellServer(SOCKET sock = INVALID_SOCKET); virtual ~CellServer(); void setEventObj(INetEvent* event); void CloseSocket(); bool IsRun(); bool OnRun(); int RecvData(ClientSocket* t_client); void NetMsg(DataHeader* pHead, ClientSocket* pClient); void addClient(ClientSocket* client); void Start(); int GetClientCount() const; void AddSendTask(ClientSocket* pClient, DataHeader* pHead);
private: SOCKET _maxSock; fd_set _fd_read_bak; bool _client_change;
char* _Recv_buf; SOCKET _sock; std::vector<ClientSocket*> _clients; std::vector<ClientSocket*> _clientsBuf; std::mutex _mutex; std::thread* _pThread; INetEvent* _pNetEvent; CellTaskServer _taskServer;
};
#endif
|
三、内存池静态库 项目
1. 思路
在服务端源码基本完成后,我开始为内存池的连接进行准备。我打算在VS2019上尝试动态库和静态库的生成与链接。内存池我打算首先用静态库来链接,在之后可能我会改为动态库链接。
在网上查阅资料后,我按如下步骤进行静态库生成与链接:
- 更改该项目配置类型为静态库类型

- 关闭预编译头(我是关掉了,也可以把内存池放在预编译头中)

- 在解决方案属性中,使得应用程序项目(即服务端项目)依赖于内存池静态库项目。这样在编译服务端项目时,会自动编译更新静态库

- 在应用程序项目(即服务端项目)属性中,在链接器选项中的附加依赖项中,添加上lib静态库文件

- 由于两个项目(服务端和静态库)在同一个解决方案,所以可以不用用代码链接静态库(我个人实验得出结论,不一定对)
1 2
| #pragma comment(lib, "guguAlloc.lib")
|

2. 头文件源码
①重载new/delete部分
Alloctor.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
#ifndef _Alloctor_h_ #define _Alloctor_h_
void* operator new(size_t size); void operator delete(void* p); void* operator new[](size_t size); void operator delete[](void* p); void* mem_alloc(size_t size); void mem_free(void* p);
#endif
|
②内存池类部分
MemoryAlloc.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
#ifndef _Memory_Alloc_h_ #define _Memory_Alloc_h_
#include"MemoryBlock.h"
class MemoryAlloc { public: MemoryAlloc(); virtual ~MemoryAlloc(); void setInit(size_t nSize, size_t nBlockSize); void initMemory(); void* allocMem(size_t nSize); void freeMem(void* p);
protected: char* _pBuf; MemoryBlock* _pHeader; size_t _nSize; size_t _nBlockSize; std::mutex _mutex; };
#endif
|
③内存块类部分
MemoryBlock.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
#ifndef _Memory_Block_h_ #define _Memory_Block_h_
class MemoryAlloc;
#include<stdlib.h> #include<assert.h> #include<mutex>
#ifdef _DEBUG #include<stdio.h> #define xPrintf(...) printf(__VA_ARGS__) #else #define xPrintf(...) #endif
class MemoryBlock { public: int _nID; int _nRef; MemoryAlloc* _pAlloc; MemoryBlock* _pNext; bool _bPool;
private:
};
#endif
|
④内存管理工具类
MemoryMgr.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
#ifndef _Memory_Mgr_h_ #define _Memory_Mgr_h_
#define MAX_MEMORY_SIZE 128
#include"MemoryAlloc.h"
class MemoryMgr { public: static MemoryMgr* Instance(); void* allocMem(size_t nSize); void freeMem(void* p); void addRef(void* p);
private: MemoryMgr(); virtual ~MemoryMgr(); void init_szAlloc(int begin, int end, MemoryAlloc* pMem);
private: MemoryAlloc* _szAlloc[MAX_MEMORY_SIZE + 1]; MemoryAlloc _mem64; MemoryAlloc _mem128; };
#endif
|
四、计时/报文动态库 项目
1. 思路
内存池静态库链接完成后,我开始准备新建动态库项目,存放自实现计时器类和报文命令类型。
在网上查阅资料后,我按如下步骤进行静态库生成与链接:
- 新建动态库项目,配置类型为动态库

- 此项目中,我使用了预编译头文件

- 添加自实现计时器类和报文命令类型文件
(下图中guguTimer.h/guguTimer.cpp
为计时器类声明/定义、CMD.h
为报文命令类型文件)

- 添加库导出关键字
__declspec(dllexport)

- 将计时类的成员变量改为全局变量,保证生命周期
(cpp文件需要include"pch.h"
来保证预编译正常进行)

- 编译动态库,得到dll文件和lib文件

- 将dll文件、lib文件、动态库中所有的头文件复制到服务端项目的源码文件夹下
(如下图所示,复制的文件有:guguDll.dll
、guguDll.lib
、pch.h
、framework.h
、guguTimer.h
、CMD.h
)

- 链接动态库、include预编译文件,此时动态库链接完成
1 2 3
| #include "pch.h" #pragma comment(lib, "guguDll.lib")
|
2. 头文件源码
①pch.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
#ifndef PCH_H #define PCH_H
#include "framework.h" #include "guguTimer.h" #include "CMD.h"
#endif
|
②framework
1 2 3 4 5 6
| #pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
|
③guguTimer.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
#ifndef MY_TIMER_H_ #define MY_TIMER_H_
#include<chrono>
class __declspec(dllexport) mytimer { private: public: mytimer();
virtual ~mytimer();
void UpDate();
double GetSecond();
};
#endif
|
④CMD.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
|
#ifndef _CMD_H_ #define _CMD_H_
enum cmd { CMD_LOGIN, CMD_LOGINRESULT, CMD_LOGOUT, CMD_LOGOUTRESULT, CMD_NEW_USER_JOIN, CMD_ERROR };
struct DataHeader { short cmd; short date_length; };
struct Login : public DataHeader { Login() { this->cmd = CMD_LOGIN; this->date_length = sizeof(Login); } char UserName[32]; char PassWord[32]; };
struct LoginResult : public DataHeader { LoginResult() { this->cmd = CMD_LOGINRESULT; this->date_length = sizeof(LoginResult); } int Result; };
struct Logout : public DataHeader { Logout() { this->cmd = CMD_LOGOUT; this->date_length = sizeof(Logout); } char UserName[32]; };
struct LogoutResult : public DataHeader { LogoutResult() { this->cmd = CMD_LOGOUTRESULT; this->date_length = sizeof(LogoutResult); } int Result; };
struct NewUserJoin : public DataHeader { NewUserJoin() { this->cmd = CMD_NEW_USER_JOIN; this->date_length = sizeof(NewUserJoin); } char UserName[32]; };
#endif
|
五、项目完整源码(github)
github链接
如下图:guguServer为服务端程序、guguAlloc为内存池静态库、guguDll为动态库
