之前我们都是在同一份cpp中编写代码与测试
本篇开始,我们需要将服务端和客户端的代码封装成class
这样我们就可以使用对象的方式创建多个server或多个client
一、封装class概述 1、头文件包含的处理
在Windows下我们可以使用#pragma once来防止重复包含
但是为了跨平台的通用性,我们需要使用如下的形式来防止重复包含
#ifndef _MessageHeader_hpp #define _MessageHeader_hpp2、封装项目结构
3、server的select注意事项消息头文件 -> MessageHeader.hpp
实现类 -> EasyTcpClient.hpp/EasyTcpServer.hpp
测试类 -> client.cpp/server.cpp
由于我们会创建多个server对象来进行测试
所以select的timeout我们需要设置成非阻塞模式
否则server开启多端口的时候,会被阻塞
timeval t = { 1,0 }; int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t);
二、测试结论
三、完整源码 1、通用消息头: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; };2、EasyTcpServer.hpp
#ifndef _EasyTcpServer_hpp #define _EasyTcpServer_hpp #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #include3、server.cpp#include #pragma comment(lib,"ws2_32.lib") #else #include //Unix标准库头文件 #include #include #define SOCKET int #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) #endif #include #include #include"MessageHeader.hpp" class EasyTcpServer { private: SOCKET _sock; std::vector g_clients; public: EasyTcpServer() { _sock = INVALID_SOCKET; } virtual ~EasyTcpServer() { Close(); } //初始化Socket SOCKET InitSocket() { #ifdef _WIN32 //启动Windows socket 2.x环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); #endif if (INVALID_SOCKET != _sock) { printf(" 关闭旧连接...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 _WIN32 if (ip) { _sin.sin_addr.S_un.S_addr = inet_addr(ip); } else { _sin.sin_addr.S_un.S_addr = INADDR_ANY; } #else if (ip) { _sin.sin_addr.s_addr = inet_addr(ip); } else { _sin.sin_addr.s_addr = INADDR_ANY; } #endif int 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); #endif if (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; } //关闭Socket void Close() { if (_sock != INVALID_SOCKET) { #ifdef _WIN32 for (int n = (int)g_clients.size() - 1; n >= 0; n--) { closesocket(g_clients[n]); } // 8 关闭套节字closesocket closesocket(_sock); //------------ //清除Windows socket环境 WSACleanup(); #else for (int n = (int)g_clients.size() - 1; n >= 0; n--) { close(g_clients[n]); } // 8 关闭套节字closesocket close(_sock); #endif } } bool OnRun() { if (isRun()) { //伯克利套接字 BSD socket fd_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中这个参数可以写0 timeval t = { 1,0 }; int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t); //&t //printf("select ret=%d count=%dn", ret, _nCount++); 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 ::iterator if (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] = {}; int nLen = (int)recv(_cSock, szRecv, sizeof(DataHeader), 0); DataHeader* header = (DataHeader*)szRecv; if (nLen <= 0) { printf("客户端 已退出,任务结束。n", (int)_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("收到客户端 请求:CMD_LOGIN,数据长度:%d,userName=%s PassWord=%sn", (int)_cSock, login->dataLength, login->userName, login->PassWord); //忽略判断用户密码是否正确的过程 LoginResult ret; SendData(_cSock, &ret); } break; case CMD_LOGOUT: { Logout* logout = (Logout*)header; printf("收到客户端 请求:CMD_LOGOUT,数据长度:%d,userName=%s n", (int)_cSock, logout->dataLength, logout->userName); //忽略判断用户密码是否正确的过程 LogoutResult ret; SendData(_cSock, &ret); } 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
#include "EasyTcpServer.hpp" int main() { EasyTcpServer server1; server1.InitSocket(); server1.Bind(nullptr, 4567); server1.Listen(5); EasyTcpServer server2; server2.InitSocket(); server2.Bind(nullptr, 4568); server2.Listen(5); while (server1.isRun() || server2.isRun()) { server1.OnRun(); server2.OnRun(); } server1.Close(); server2.Close(); printf("已退出,任务结束。n"); getchar(); return 0; }4、EasyTcpClient.hpp
#ifndef _EasyTcpClient_hpp_ #define _EasyTcpClient_hpp_ #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include5、client.cpp#include #pragma comment(lib,"ws2_32.lib") #else #include //uni std #include #include #define SOCKET int #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) #endif #include #include "MessageHeader.hpp" class EasyTcpClient { SOCKET _sock; public: EasyTcpClient() { _sock = INVALID_SOCKET; } virtual ~EasyTcpClient() { Close(); } //初始化socket void InitSocket() { #ifdef _WIN32 //启动Windows socket 2.x环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); #endif if (INVALID_SOCKET != _sock) { printf(" 关闭旧连接...n", _sock); Close(); } _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == _sock) { printf("错误,建立Socket失败...n"); } else { printf("建立Socket成功...n"); } } //连接服务器 int Connect(const char* ip, unsigned short port) { if (INVALID_SOCKET == _sock) { InitSocket(); } // 2 连接服务器 connect sockaddr_in _sin = {}; _sin.sin_family = AF_INET; _sin.sin_port = htons(port); #ifdef _WIN32 _sin.sin_addr.S_un.S_addr = inet_addr(ip); #else _sin.sin_addr.s_addr = inet_addr(ip); #endif int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in)); if (SOCKET_ERROR == ret) { printf("错误,连接服务器失败...n"); } else { printf("连接服务器成功...n"); } return ret; } //关闭套节字closesocket void Close() { if (_sock != INVALID_SOCKET) { #ifdef _WIN32 closesocket(_sock); //清除Windows socket环境 WSACleanup(); #else close(_sock); #endif _sock = INVALID_SOCKET; } } //处理网络消息 bool OnRun() { if (isRun()) { fd_set fdReads; FD_ZERO(&fdReads); FD_SET(_sock, &fdReads); timeval t = { 1,0 }; int ret = select(_sock + 1, &fdReads, 0, 0, &t); if (ret < 0) { printf(" select任务结束1n", _sock); return false; } if (FD_ISSET(_sock, &fdReads)) { FD_CLR(_sock, &fdReads); if (-1 == RecvData(_sock)) { printf(" select任务结束2n", _sock); return false; } } 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("与服务器断开连接,任务结束。n"); return -1; } recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0); OnNetMsg(header); return 0; } //响应网络消息 void OnNetMsg(DataHeader* header) { switch (header->cmd) { case CMD_LOGIN_RESULT: { LoginResult* login = (LoginResult*)header; printf("收到服务端消息:CMD_LOGIN_RESULT,数据长度:%dn", login->dataLength); } break; case CMD_LOGOUT_RESULT: { LogoutResult* logout = (LogoutResult*)header; printf("收到服务端消息:CMD_LOGOUT_RESULT,数据长度:%dn", logout->dataLength); } break; case CMD_NEW_USER_JOIN: { NewUserJoin* userJoin = (NewUserJoin*)header; printf("收到服务端消息:CMD_NEW_USER_JOIN,数据长度:%dn", userJoin->dataLength); } break; } } //发送数据 int SendData(DataHeader* header) { if (isRun() && header) { return send(_sock, (const char*)header, header->dataLength, 0); } return SOCKET_ERROR; } private: }; #endif
#include "EasyTcpClient.hpp" #includevoid cmdThread(EasyTcpClient* client) { while (true) { char cmdBuf[256] = {}; scanf("%s", cmdBuf); if (0 == strcmp(cmdBuf, "exit")) { client->Close(); printf("退出cmdThread线程n"); break; } else if (0 == strcmp(cmdBuf, "login")) { Login login; strcpy(login.userName, "lyd"); strcpy(login.PassWord, "lydmm"); client->SendData(&login); } else if (0 == strcmp(cmdBuf, "logout")) { Logout logout; strcpy(logout.userName, "lyd"); client->SendData(&logout); } else { printf("不支持的命令。n"); } } } int main() { EasyTcpClient client; client.Connect("127.0.0.1", 4567); //启动UI线程 std::thread t1(cmdThread, &client); t1.detach(); while (client.isRun()) { client.OnRun(); //printf("空闲时间处理其它业务..n"); //Sleep(1000); } client.Close(); printf("已退出。n"); getchar(); return 0; }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)