服务器端:
EasyTcpServer.hpp
#ifndef _EasyTcpServer_hpp_ #define _EasyTcpServer_hpp_ #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARNINGS #include<WinSock2.h> #include<Windows.h> #pragma comment(lib,"ws2_32.lib") #else #include<unistd.h> #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_client; //容器里装的是所有的客户端 public : EasyTcpServer() { _sock = INVALID_SOCKET;//将Socket设置为一个无效的套接字 } virtual ~EasyTcpServer() { Close(); } //初始化Socket SOCKET InitSock() { //启动Win Socket2.x环境 #ifdef _WIN32 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); #endif //1.建立一个socket if (INVALID_SOCKET != _sock) // 如果当前的socket并不是无效的,则将其关闭 { printf("<socket=%d>关闭旧连接......\n", _sock); Close(); } _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 新建一个socket,此时的Socket为有效 if (INVALID_SOCKET == _sock) { printf("ERROR:建立失败!\n"); } else{ printf("客户端绑定成功......\n"); } return _sock; } //绑定端口号 int Bind(const char* ip,unsigned short port) { /*if (_sock == INVALID_SOCKET) { InitSock(); }*/ sockaddr_in _sin = {}; //创建网络地址 _sin.sin_family = AF_INET; //IPV4 _sin.sin_port = htons(port); //Host to Network Short #ifdef _WIN32 if (ip) //验证IP是否存在 { _sin.sin_addr.S_un.S_addr = inet_addr(ip); // IP地址 } else{ _sin.sin_addr.S_un.S_addr = INADDR_ANY; } #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.监听网络端口 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.等待接收客户端连接 sockaddr_in clientAddr = {}; //创建客户端网络地址 int nAddrLen = sizeof(sockaddr_in); SOCKET _cSOCK = INVALID_SOCKET; //将SOCKET的对象_cSOCK初始化无效的Socket #ifdef _WIN32 _cSOCK = accept(_sock, (sockaddr *)&clientAddr, &nAddrLen);//接收来自客户端传来的SOCKET #else _cSOCK = accept(_sock, (sockaddr *)&clientAddr, (socklen_t *)&nAddrLen); #endif if (_cSOCK == INVALID_SOCKET) { printf("<Socket=%d>错误,接收到无效客户端SOCKET!\n",_sock); } else { //向客户端发送新来的用户 NewUserJoin userJoin; SendDataToAll(&userJoin); //将服务端收到的socket存入容器中 g_client.push_back(_cSOCK); printf("<Socket=%d>新客户端加入:Socket=%d,IP = %s\n", _sock,(int)_cSOCK, inet_ntoa(clientAddr.sin_addr));//inet_ntoa(clientAddr.sin_addr)将接收到的IP地址转化为字符串 } return _cSOCK; } //关闭socket void Close() { if (_sock != INVALID_SOCKET) { #ifdef _WIN32 for (int n = (int)g_client.size(); n >= 0; n--) { //8.关闭自身的socket closesocket(g_client[n]); } //8.关闭自身的socket closesocket(_sock); //清除Windows socket环境 WSACleanup(); #else for (int n = (int)g_client.size(); n >= 0; n--) { //8.关闭自身的socket closesocket(g_client[n]); } close(_sock); #endif } } //处理网络消息 bool onRun() { if (isRun()) { fd_set fd_Read; fd_set fd_Write; fd_set fd_Exp; FD_ZERO(&fd_Read);//FD_ZERO 清空集合里的数据 FD_ZERO(&fd_Write); FD_ZERO(&fd_Exp); FD_SET(_sock, &fd_Read);//FD_SET 可以进行操作的宏 FD_SET(_sock, &fd_Write); FD_SET(_sock, &fd_Exp); SOCKET maxSock = _sock; for (int n = g_client.size() - 1; n >= 0; n--) { FD_SET(g_client[n], &fd_Read); if (maxSock < g_client[n]) { maxSock = g_client[n]; } } /* select( _In_ int nfds, _Inout_opt_ fd_set FAR * readfds, _Inout_opt_ fd_set FAR * writefds, _Inout_opt_ fd_set FAR * exceptfds, _In_opt_ const struct timeval FAR * timeout ); */ //nfds是一个整数值,是指fd_set集合所有的描述符(select里的第一个参数)的范围(而不是数量) //既是所有文件描述符最大值+1 timeval t = { 1, 0 }; int ret = select(_sock + 1, &fd_Read, &fd_Write, &fd_Exp, &t); //系统提供select函数来实现多路复用输入/输出模型 if (ret < 0) { printf("select任务结束!\n"); Close(); return false; } if (FD_ISSET(_sock, &fd_Read)) { FD_CLR(_sock, &fd_Read); Accept(); } for (int n = (int)g_client.size() - 1; n >= 0; n--) { if (FD_ISSET(g_client[n], &fd_Read)) { if (RecvData(g_client[n]) == -1) { auto iter = g_client.begin() + n; if (iter != g_client.end()) { g_client.erase(iter); } } } } return true; } return false; } //是否工作中 bool isRun() { return _sock != INVALID_SOCKET; } //接收数据 处理粘包和拆包 int RecvData(SOCKET _cSOCK) { //增加一个缓冲区 char szRecv[1024] = {}; //5.接收客户端新数据 int nLen = recv(_cSOCK, szRecv, sizeof(DataHeader), 0); DataHeader *header = (DataHeader*)szRecv; if (nLen <= 0) { printf("客户端已退出!任务结束!"); 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\nUserName:%s\nPassWord:%s\n", _cSOCK, login->dataLength, login->username, login->password); //忽略判断用户密码是否正确的过程 LoginResult ret; send(_cSOCK, (char *)&ret, sizeof(LoginResult), 0); //再发消息体 } case CMD_Logout: { Logout* logout = (Logout*)header; printf("收到命令:CMD_Logout,数据长度:%d\nUserName:%s\n", logout->dataLength, logout->username); //忽略判断用户密码是否正确的过程 LogoutResult let; send(_cSOCK, (char *)&let, sizeof(let), 0); //再发消息体 } break; case CMD_New_User_Join: { NewUserJoin* UserJoin = (NewUserJoin*)header; printf("收到命令:CMD_Logout,数据长度:%d\nUserName:%s\n", UserJoin->dataLength); //忽略判断用户密码是否正确的过程 NewUserJoin let; send(_cSOCK, (char *)&let, sizeof(let), 0); //再发消息体 } break; default: { DataHeader header = { 0 }; send(_cSOCK, (char *)&header.cmd, 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; } //群发指定socket数据 void SendDataToAll(DataHeader* header) { if (isRun() && header) { for (int n = (int)g_client.size() - 1; n >= 0; n--) { SendData(g_client[n], header); } } } }; #endif
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; }; //包体 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(LogoutResult); cmd = CMD_New_User_Join; sock = 0; } int sock; };
Server.cpp
#include "EasyTcpServer.hpp" int main() { EasyTcpServer server; server.InitSock(); server.Bind(nullptr,4567); server.Listen(5); while (server.isRun()) { server.onRun(); //printf("空闲时间处理其他业务.......\n"); } server.Close(); system("pause"); return 0; }
客户端:
EasyTcpClient.hpp
#ifndef _EasyTcpClient_hpp_ #define _EasyTcpClient_hpp_ #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include<WinSock2.h> #include<Windows.h> #pragma comment(lib,"ws2_32.lib") #else #include<unistd.h> #include<arpa/inet.h> #include<string.h> #define SOCKET int #define INVALID_SOCKET (SOCKET)(-0) #define SOCKET_ERROR (-1) #endif #include<stdio.h> #include "MessageHeader.hpp" class EasyTcpClient { SOCKET _sock; public: EasyTcpClient() { _sock = INVALID_SOCKET;//将Socket设置为一个无效的套接字 } //虚构函数 virtual ~EasyTcpClient() { Close(); } //初始化socket void initSocket() { //启动Win Socket2.x环境 #ifdef _WIN32 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); #endif //1.建立一个socket if (INVALID_SOCKET != _sock) // 如果当前的socket并不是无效的,则将其关闭 { printf("<socket=%d>关闭旧连接......\n",_sock); Close(); } _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 新建一个socket if (INVALID_SOCKET == _sock) { printf("ERROR:建立失败!\n"); } else{ printf("客户端绑定成功......\n"); } } //连接服务器 int Connet(char* ip, unsigned short port) { if (INVALID_SOCKET == _sock) { initSocket(); } sockaddr_in _sin = {}; //创建网络地址 _sin.sin_family = AF_INET; _sin.sin_port = htons(port); //Host to Network Short #ifdef _WIN32 _sin.sin_addr.S_un.S_addr = inet_addr(ip);//inet_addr("127.0.0.1"); // 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("ERROR:连接失败!\n"); } else { printf("客户端连接成功......\n"); } return ret; } //关闭服务器 void Close() { if (_sock != INVALID_SOCKET) { //关闭Win Socket2.x环境 #ifdef _WIN32 closesocket(_sock); //WinSocket关闭 WSACleanup(); #else close(_sock); #endif _sock = INVALID_SOCKET; } } //查询网络消息 bool onRun() { if (isRun()) { //伯克利 socket fd_set fd_Read; FD_ZERO(&fd_Read);//FD_ZERO 清空集合里的数据 FD_SET(_sock, &fd_Read);//FD_SET 可以进行操作的宏 timeval t = { 1, 0 }; int ret = select(_sock, &fd_Read, 0, 0, &t); if (ret < 0) { printf("<socket=%d>select任务结束1!", _sock); return false; } if (FD_ISSET(_sock, &fd_Read)) { FD_CLR(_sock, &fd_Read); if (RecvData(_sock) == -1) { printf("<socket = %d>select任务结束2!", _sock); return false; } } return true; } return false; } //是否工作中 bool isRun() { return _sock != INVALID_SOCKET; } //接收数据 处理粘包和拆包 int RecvData(SOCKET _cSOCK) { //增加一个缓冲区 char szRecv[1024] = {}; //5.接收客户端新数据 int nLen = recv(_cSOCK, szRecv, sizeof(DataHeader), 0); DataHeader *header = (DataHeader*)szRecv; if (nLen <= 0) { printf("与服务器断开连接!任务结束!"); 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 *loginresult = (LoginResult*)header; printf("收到服务端消息请求:CMD_Login_Result,数据长度:%d\n", loginresult->dataLength); } break; case CMD_Logout_Result: { LogoutResult* logoutresult = (LogoutResult*)header; printf("收到服务端消息请求:CMD_Logout_Result,数据长度:%d\nUserName:%s\n", logoutresult->dataLength); } break; case CMD_New_User_Join: { NewUserJoin* newuserjoin = (NewUserJoin*)header; printf("收到服务端消息请求:CMD_New_User_Join,数据长度:%d\nUserName:%s\n", newuserjoin->dataLength); } break; } } //发送数据 int SendData(DataHeader* header) { if (isRun() && header) { return send(_sock, (const char*)header, header->dataLength, 0); } return SOCKET_ERROR; } private: }; #endif
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; }; //包体 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(LogoutResult); cmd = CMD_New_User_Join; sock = 0; } int sock; };
client.cpp
#include "EasyTcpClient.hpp" //线程库头文件 #include<thread> void cmdThread(EasyTcpClient* client) { while (true) { char cmdBuf[256] = {}; scanf("%s", cmdBuf); if (0 == strcmp(cmdBuf, "exit")) { client->Close(); printf("退出!\n"); break; } else if (0 == strcmp(cmdBuf, "login")) { Login login; strcpy(login.username, "sutaoyu"); strcpy(login.password, "sutaoyu01"); client->SendData(&login); } else if (0 == strcmp(cmdBuf, "logout")) { Logout logout; strcpy(logout.username, "sutaoyu"); client->SendData(&logout); } else{ printf("不支持的命令!"); } } } int main() { EasyTcpClient client; client.initSocket(); client.Connet("127.0.0.1", 4567); //启动线程 std::thread t1(cmdThread, &client); t1.detach(); while (client.isRun()) { client.onRun(); } client.Close(); printf("已退出!\n"); getchar(); return 0; }