封装server
文章目錄
- 1 封裝server
- 1.1 概述
- 1.2 代碼實現
1 封裝server
1.1 概述
對server的分裝和對client的封裝很類似。
1.2 代碼實現
MessageHeader.hpp:
#ifndef _MessageHeader_hpp_ #define _MessageHeader_hpp_enum CMD {CMD_LOGIN,CMD_LOGIN_RESULT,CMD_LOGOUT,CMD_LOGOUT_RESULT,CMD_NEW_USER_JOIN,CMD_ERROR };struct DataHeader {short dataLength;short cmd; };//DataPackage struct Login : public DataHeader {Login(){dataLength = sizeof(Login);cmd = CMD_LOGIN;}char userName[32];char PassWord[32]; };struct LoginResult : public DataHeader {LoginResult(){dataLength = sizeof(LoginResult);cmd = CMD_LOGIN_RESULT;result = 0;}int result; };struct Logout : public DataHeader {Logout(){dataLength = sizeof(Logout);cmd = CMD_LOGOUT;}char userName[32]; };struct LogoutResult : public DataHeader {LogoutResult(){dataLength = sizeof(LogoutResult);cmd = CMD_LOGOUT_RESULT;result = 0;}int result; };struct NewUserJoin : public DataHeader {NewUserJoin(){dataLength = sizeof(NewUserJoin);cmd = CMD_NEW_USER_JOIN;scok = 0;}int scok; };#endif // !_MessageHeader_hpp_EasyTcpServer.hpp:
#ifndef _EasyTcpServer_hpp_ #define _EasyTcpServer_hpp_#ifdef _WIN32#define WIN32_LEAN_AND_MEAN#define _WINSOCK_DEPRECATED_NO_WARNINGS#include<windows.h>#include<WinSock2.h>#pragma comment(lib,"ws2_32.lib") #else#include<unistd.h> //uni std#include<arpa/inet.h>#include<string.h>#define SOCKET int#define INVALID_SOCKET (SOCKET)(~0)#define SOCKET_ERROR (-1) #endif#include<stdio.h> #include<vector> #include"MessageHeader.hpp"class EasyTcpServer { private:SOCKET _sock;std::vector<SOCKET> g_clients; public:EasyTcpServer(){_sock = INVALID_SOCKET;}virtual ~EasyTcpServer(){Close();}//初始化SocketSOCKET InitSocket(){ #ifdef _WIN32//啟動Windows socket 2.x環境WORD ver = MAKEWORD(2, 2);WSADATA dat;WSAStartup(ver, &dat); #endifif (INVALID_SOCKET != _sock){printf("<socket=%d>關閉舊連接...\n", (int)_sock);Close();}_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (INVALID_SOCKET == _sock){printf("錯誤,建立socket失敗...\n");}else {printf("建立socket=<%d>成功...\n", (int)_sock);}return _sock;}//綁定IP和端口號int Bind(const char* ip, unsigned short port){//if (INVALID_SOCKET == _sock)//{// InitSocket();//}// 2 bind 綁定用于接受客戶端連接的網絡端口sockaddr_in _sin = {};_sin.sin_family = AF_INET;_sin.sin_port = htons(port);//host to net unsigned short#ifdef _WIN32if (ip){_sin.sin_addr.S_un.S_addr = inet_addr(ip);}else {_sin.sin_addr.S_un.S_addr = INADDR_ANY;} #elseif (ip) {_sin.sin_addr.s_addr = inet_addr(ip);}else {_sin.sin_addr.s_addr = INADDR_ANY;} #endifint ret = bind(_sock, (sockaddr*)&_sin, sizeof(_sin));if (SOCKET_ERROR == ret){printf("錯誤,綁定網絡端口<%d>失敗...\n", port);}else {printf("綁定網絡端口<%d>成功...\n", port);}return ret;}//監聽端口號int Listen(int n){// 3 listen 監聽網絡端口int ret = listen(_sock, n);if (SOCKET_ERROR == ret){printf("socket=<%d>錯誤,監聽網絡端口失敗...\n",_sock);}else {printf("socket=<%d>監聽網絡端口成功...\n", _sock);}return ret;}//接受客戶端連接SOCKET Accept(){// 4 accept 等待接受客戶端連接sockaddr_in clientAddr = {};int nAddrLen = sizeof(sockaddr_in);SOCKET _cSock = INVALID_SOCKET; #ifdef _WIN32_cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen); #else_cSock = accept(_sock, (sockaddr*)&clientAddr, (socklen_t *)&nAddrLen); #endifif (INVALID_SOCKET == _cSock){printf("socket=<%d>錯誤,接受到無效客戶端SOCKET...\n", (int)_sock);}else{NewUserJoin userJoin;SendDataToAll(&userJoin);g_clients.push_back(_cSock);printf("socket=<%d>新客戶端加入:socket = %d,IP = %s \n", (int)_sock, (int)_cSock, inet_ntoa(clientAddr.sin_addr));}return _cSock;}//關閉Socketvoid Close(){if (_sock != INVALID_SOCKET){ #ifdef _WIN32for (int n = (int)g_clients.size() - 1; n >= 0; n--){closesocket(g_clients[n]);}// 8 關閉套節字closesocketclosesocket(_sock);//------------//清除Windows socket環境WSACleanup(); #elsefor (int n = (int)g_clients.size() - 1; n >= 0; n--){close(g_clients[n]);}// 8 關閉套節字closesocketclose(_sock); #endif}}//處理網絡消息bool OnRun(){if (isRun()){//伯克利套接字 BSD socketfd_set fdRead;//描述符(socket) 集合fd_set fdWrite;fd_set fdExp;//清理集合FD_ZERO(&fdRead);FD_ZERO(&fdWrite);FD_ZERO(&fdExp);//將描述符(socket)加入集合FD_SET(_sock, &fdRead);FD_SET(_sock, &fdWrite);FD_SET(_sock, &fdExp);SOCKET maxSock = _sock;for (int n = (int)g_clients.size() - 1; n >= 0; n--){FD_SET(g_clients[n], &fdRead);if (maxSock < g_clients[n]){maxSock = g_clients[n];}}///nfds 是一個整數值 是指fd_set集合中所有描述符(socket)的范圍,而不是數量///既是所有文件描述符最大值+1 在Windows中這個參數可以寫0timeval t = { 1,0 };int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t);if (ret < 0){printf("select任務結束。\n");Close();return false;}//判斷描述符(socket)是否在集合中if (FD_ISSET(_sock, &fdRead)){FD_CLR(_sock, &fdRead);Accept();}for (int n = (int)g_clients.size() - 1; n >= 0; n--){if (FD_ISSET(g_clients[n], &fdRead)){if (-1 == RecvData(g_clients[n])){auto iter = g_clients.begin() + n;//std::vector<SOCKET>::iteratorif (iter != g_clients.end()){g_clients.erase(iter);}}}}return true;}return false;}//是否工作中bool isRun(){return _sock != INVALID_SOCKET;}//接收數據 處理粘包 拆分包int RecvData(SOCKET _cSock){//緩沖區char szRecv[4096] = {};// 5 接收客戶端數據int nLen = (int)recv(_cSock, szRecv, sizeof(DataHeader), 0);DataHeader* header = (DataHeader*)szRecv;if (nLen <= 0){printf("客戶端<Socket=%d>已退出,任務結束。\n", _cSock);return -1;}recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);OnNetMsg(_cSock, header);return 0;}//響應網絡消息virtual void OnNetMsg(SOCKET _cSock, DataHeader* header){switch (header->cmd){case CMD_LOGIN:{Login* login = (Login*)header;printf("收到客戶端<Socket=%d>請求:CMD_LOGIN,數據長度:%d,userName=%s PassWord=%s\n", _cSock, login->dataLength, login->userName, login->PassWord);//忽略判斷用戶密碼是否正確的過程LoginResult ret;send(_cSock, (char*)&ret, sizeof(LoginResult), 0);}break;case CMD_LOGOUT:{Logout* logout = (Logout*)header;printf("收到客戶端<Socket=%d>請求:CMD_LOGOUT,數據長度:%d,userName=%s \n", _cSock, logout->dataLength, logout->userName);//忽略判斷用戶密碼是否正確的過程LogoutResult ret;send(_cSock, (char*)&ret, sizeof(ret), 0);}break;default:{DataHeader header = { 0,CMD_ERROR };send(_cSock, (char*)&header, sizeof(header), 0);}break;}}//發送指定Socket數據int SendData(SOCKET _cSock, DataHeader* header){if (isRun() && header){return send(_cSock, (const char*)header, header->dataLength, 0);}return SOCKET_ERROR;}void SendDataToAll(DataHeader* header){for (int n = (int)g_clients.size() - 1; n >= 0; n--){SendData(g_clients[n], header);}}};#endif // !_EasyTcpServer_hpp_server.cpp:
#include "EasyTcpServer.hpp"int main() {EasyTcpServer server;server.InitSocket();server.Bind(nullptr, 4567);server.Listen(5);while (server.isRun()){server.OnRun();//printf("空閑時間處理其它業務..\n");}server.Close();printf("已退出。\n");getchar();return 0; }參考資料:
總結
- 上一篇: 生化危机6雇佣兵a级多少分
- 下一篇: 粘包的原因分析及解决