class ClientSocket
{
public:
ClientSocket(SOCKET sockfd= INVALID_SOCKET) :_sock(sockfd), _lastPos(0) {
memset(_recvMsgBuff, 0, sizeof(_recvMsgBuff));
}
SOCKET sockfd() { return _sock; }
char *msgBuff() { return _recvMsgBuff; }
int getLastPos() { return _lastPos; }
void setLastPos(int pos) { _lastPos = pos; }
private:
SOCKET _sock;
char _recvMsgBuff[RECV_BUFF_SIZE * 10]; //第二缓冲区(消息缓冲区)
int _lastPos;
};
class EasyTcpServer
{
public:
EasyTcpServer() :_sock(INVALID_SOCKET) {
memset(_recvBuff, 0, sizeof(_recvBuff));
}
private:
std::vector _clients;//存放客户端的套接字(重点:此处存储的是指针而不能是对象)
char _recvBuff[RECV_BUFF_SIZE]; //第一缓冲区(接收缓冲区)
};
int EasyTcpServer::RecvData(ClientSocket* pClient)
{
int _nLen = recv(pClient->sockfd(), _recvBuff, RECV_BUFF_SIZE, 0);
if (_nLen < 0) {
std::cout << "recv函数出错!" << std::endl;
return -1;
}
else if (_nLen == 0) {
std::cout << "客户端sockfd() << ">:已退出!" << std::endl;
return -1;
}
memcpy(pClient->msgBuff() + pClient->getLastPos(), _recvBuff, _nLen);
pClient->setLastPos(pClient->getLastPos() + _nLen);
while (pClient->getLastPos() >= sizeof(DataHeader))
{
DataHeader* header = (DataHeader*)pClient->msgBuff();
if (pClient->getLastPos() >= header->dataLength)
{
//剩余未处理消息缓冲区的长度
int nSize = pClient->getLastPos() - header->dataLength;
//处理网络消息
OnNetMessage(pClient->sockfd(), header);
//处理完成之后,将_recvMsgBuff中剩余未处理部分的数据前移
memcpy(pClient->msgBuff(), pClient->msgBuff() + header->dataLength, nSize);
pClient->setLastPos(nSize);
}
else {
//消息缓冲区剩余数据不够一条完整消息
break;
}
}
return 0;
}
#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
{
DataHeader() :cmd(CMD_ERROR), dataLength(sizeof(DataHeader)) {}
short cmd; //命令的类型
short dataLength; //数据的长度
};
//登录消息体
struct Login :public DataHeader
{
Login() {
cmd = CMD_LOGIN;
dataLength = sizeof(Login); //消息长度=消息头(父类)+消息体(子类)
}
char userName[32]; //账号
char PassWord[32]; //密码
char data[932];
};
//登录结果
struct LoginResult :public DataHeader
{
LoginResult() :result(0) {
cmd = CMD_LOGIN_RESULT;
dataLength = sizeof(LoginResult);
}
int result; //登录的结果,0代表正常
char data[992];
};
//退出消息体
struct Logout :public DataHeader
{
Logout() {
cmd = CMD_LOGOUT;
dataLength = sizeof(Logout);
}
char userName[32]; //账号
};
//退出结果
struct LogoutResult :public DataHeader
{
LogoutResult() :result(0) {
cmd = CMD_LOGOUT_RESULT;
dataLength = sizeof(LogoutResult);
}
int result; //退出的结果,0代表正常
};
//新的客户端加入,服务端给其他所有客户端发送此报文
struct NewUserJoin :public DataHeader
{
NewUserJoin(int _cSocket = 0) :sock(_cSocket) {
cmd = CMD_NEW_USER_JOIN;
dataLength = sizeof(LogoutResult);
}
int sock; //新客户端的socket
};
#endif
#ifndef _EasyTcpClient_hpp_
#define _EasyTcpClient_hpp_
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS //for inet_pton()
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#pragma comment(lib, "ws2_32.lib")
#else
#include
#include
#include
#include
#include
#include
//在Unix下没有这些宏,为了兼容,自己定义
#define SOCKET int
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_ERROR (-1)
#endif
#ifndef RECV_BUFF_SIZE
#define RECV_BUFF_SIZE 10240
#endif // !RECV_BUFF_SIZE
#include
#include
#include
#include
#include "MessageHeader.hpp"
using namespace std;
class ClientSocket
{
public:
ClientSocket(SOCKET sockfd= INVALID_SOCKET) :_sock(sockfd), _lastPos(0) {
memset(_recvMsgBuff, 0, sizeof(_recvMsgBuff));
}
SOCKET sockfd() { return _sock; }
char *msgBuff() { return _recvMsgBuff; }
int getLastPos() { return _lastPos; }
void setLastPos(int pos) { _lastPos = pos; }
private:
SOCKET _sock;
char _recvMsgBuff[RECV_BUFF_SIZE * 10];
int _lastPos;
};
class EasyTcpServer
{
public:
EasyTcpServer() :_sock(INVALID_SOCKET) {
memset(_recvBuff, 0, sizeof(_recvBuff));
}
virtual ~EasyTcpServer() { CloseSocket(); }
public:
//判断当前服务端是否在运行
bool isRun() { return _sock != INVALID_SOCKET; }
//初始化socket
void InitSocket();
//绑定端口号
int Bind(const char* ip, unsigned short port);
//监听端口号
int Listen(int n);
//接收客户端连接
SOCKET Accept();
//关闭socket
void CloseSocket();
//处理网络消息
bool Onrun();
/*
使用RecvData接收任何类型的数据,
然后将消息的头部字段传递给OnNetMessage()函数中,让其响应不同类型的消息
*/
//接收数据,参数:客户端的套接字
int RecvData(ClientSocket* pClient);
//响应网络消息
virtual void OnNetMessage(SOCKET _cSock, DataHeader* header);
//发送数据,单发(参数1为指定的客户端的socket)
int SendData(SOCKET _cSock, DataHeader* header);
//群发数据
void SendDataToAll( DataHeader* header);
private:
SOCKET _sock;
std::vector _clients;//存放客户端的套接字
SOCKET maxSock = _sock; //select的参数1要使用,当前最大的文件描述符值
char _recvBuff[RECV_BUFF_SIZE];
};
void EasyTcpServer::InitSocket()
{
#ifdef _WIN32
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
#endif
//建立socket
_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == _sock) {
std::cout << "Server:创建socket成功" << std::endl;
}
else {
std::cout << "Server:创建socket成功" << std::endl;
}
}
int EasyTcpServer::Bind(const char* ip, unsigned short port)
{
if (!isRun())
InitSocket();
//初始化服务端地址
struct sockaddr_in _sin = {};
#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
_sin.sin_family = AF_INET;
_sin.sin_port = htons(port);
//绑定服务端地址
int ret = bind(_sock, (struct sockaddr*)&_sin, sizeof(_sin));
if (SOCKET_ERROR == ret) {
if (ip)
std::cout << "Server:绑定地址(" << ip << "," << port << ")失败!" << std::endl;
else
std::cout << "Server:绑定地址(INADDR_ANY," << port << ")失败!" << std::endl;
}
else {
if (ip)
std::cout << "Server:绑定地址(" << ip << "," << port << ")成功!" << std::endl;
else
std::cout << "Server:绑定地址(INADDR_ANY," << port << ")成功!" << std::endl;
}
return ret;
}
void EasyTcpServer::CloseSocket()
{
if (_sock != INVALID_SOCKET)
{
#ifdef _WIN32
//将所有的客户端套接字关闭
for (int n = (int)_clients.size() - 1; n >= 0; --n)
{
closesocket(_clients[n]->sockfd());
delete _clients[n];
}
//关闭服务端套接字
closesocket(_sock);
WSACleanup();
#else
for (int n = (int)_clients.size() - 1; n >= 0; --n)
{
close(_clients[n]->sockfd());
delete _clients[n];
}
close(_sock);
#endif
_clients.clear();
_sock = INVALID_SOCKET;
}
}
int EasyTcpServer::Listen(int n)
{
//监听网络端口
int ret = listen(_sock, n);
if (SOCKET_ERROR == ret)
std::cout << "Server:监听网络端口失败!" << std::endl;
else
std::cout << "Server:监听网络端口成功!" << std::endl;
return ret;
}
SOCKET EasyTcpServer::Accept()
{
//用来保存客户端地址
struct sockaddr_in _clientAddr = {};
int nAddrLen = sizeof(_clientAddr);
SOCKET _cSock = INVALID_SOCKET;
//接收客户端连接
#ifdef _WIN32
_cSock = accept(_sock, (struct sockaddr*)&_clientAddr, &nAddrLen);
#else
_cSock = accept(_sock, (struct sockaddr*)&_clientAddr, (socklen_t*)&nAddrLen);
#endif
if (INVALID_SOCKET == _cSock) {
std::cout << "Server:接收到无效客户端!" << std::endl;
}
else {
//通知其他已存在的所有客户端,有新的客户端加入
NewUserJoin newUserInfo(static_cast(_cSock));
SendDataToAll(&newUserInfo);
//将客户端的套接字存入vector内
_clients.push_back(new ClientSocket(_cSock));
std::cout << "Server:接受到新的客户端连接,IP=" << inet_ntoa(_clientAddr.sin_addr)
<< ",Socket=" << static_cast(_cSock) << std::endl;
}
return _cSock;
}
bool EasyTcpServer::Onrun()
{
if (isRun())
{
fd_set fdRead;
fd_set fdWrite;
fd_set fdExp;
FD_ZERO(&fdRead);
FD_ZERO(&fdWrite);
FD_ZERO(&fdExp);
FD_SET(_sock, &fdRead);
FD_SET(_sock, &fdWrite);
FD_SET(_sock, &fdExp);
//每次select之前,将所有客户端加入到读集中(此处为了演示,只介绍客户端读的情况)
for (int n = (int)_clients.size() - 1; n >= 0; --n)
{
FD_SET(_clients[n]->sockfd(), &fdRead);
if (maxSock < _clients[n]->sockfd())
maxSock = _clients[n]->sockfd();
}
struct timeval t = { 3,0 };
int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t);
if (ret < 0)
{
std::cout << "Server:select出错!" << std::endl;
return false;
}
if (FD_ISSET(_sock, &fdRead))//如果一个客户端连接进来,那么服务端的socket就会变为可读的,此时我们使用accept来接收这个客户端
{
FD_CLR(_sock, &fdRead);
Accept();
}
//遍历vector数组中所有的客户端套接字,如果某个客户端的套接字在读集中,
//那么说明相应的客户端有数据来,那么就执行processor()函数
for (int n = (int)_clients.size() - 1; n >= 0; --n)
{
if (FD_ISSET(_clients[n]->sockfd(), &fdRead))
{
if (-1 == RecvData(_clients[n]))
{
//如果processor出错,那么就将该客户端从全局vector中移除
//首先获取该套接字在vector中的迭代器位置,然后通过erase()删除
auto iter = _clients.begin() + n;
if (iter != _clients.end())
{
delete _clients[n];
_clients.erase(iter);
}
}
}
}
return true;
}
return false;
}
int EasyTcpServer::RecvData(ClientSocket* pClient)
{
int _nLen = recv(pClient->sockfd(), _recvBuff, RECV_BUFF_SIZE, 0);
if (_nLen < 0) {
std::cout << "recv函数出错!" << std::endl;
return -1;
}
else if (_nLen == 0) {
std::cout << "客户端sockfd() << ">:已退出!" << std::endl;
return -1;
}
memcpy(pClient->msgBuff() + pClient->getLastPos(), _recvBuff, _nLen);
pClient->setLastPos(pClient->getLastPos() + _nLen);
while (pClient->getLastPos() >= sizeof(DataHeader))
{
DataHeader* header = (DataHeader*)pClient->msgBuff();
if (pClient->getLastPos() >= header->dataLength)
{
//剩余未处理消息缓冲区的长度
int nSize = pClient->getLastPos() - header->dataLength;
//处理网络消息
OnNetMessage(pClient->sockfd(), header);
//处理完成之后,将_recvMsgBuff中剩余未处理部分的数据前移
memcpy(pClient->msgBuff(), pClient->msgBuff() + header->dataLength, nSize);
pClient->setLastPos(nSize);
}
else {
//消息缓冲区剩余数据不够一条完整消息
break;
}
}
return 0;
}
void EasyTcpServer::OnNetMessage(SOCKET _cSock, DataHeader* header)
{
switch (header->cmd)
{
case CMD_LOGIN: //如果是登录
{
Login *login = (Login*)header;
std::cout << "服务端:收到客户端的消息CMD_LOGIN,用户名:" << login->userName << ",密码:" << login->PassWord << std::endl;
//此处可以判断用户账户和密码是否正确等等(省略)
//返回登录的结果给客户端
LoginResult ret;
SendData(_cSock, &ret);
}
break;
case CMD_LOGOUT: //如果是退出
{
Logout *logout = (Logout*)header;
std::cout << "服务端:收到客户端的消息CMD_LOGOUT,用户名:" << logout->userName << std::endl;
//返回退出的结果给客户端
LogoutResult ret;
SendData(_cSock, &ret);
}
break;
default: //如果有错误
{
std::cout << "服务端:收到客户端的未知消息消息" << std::endl;
//DataHeader默认为错误消息
DataHeader header;
SendData(_cSock, &header);
}
break;
}
}
int EasyTcpServer::SendData(SOCKET _cSock, DataHeader* header)
{
if (isRun() && header)
{
return send(_cSock, (const char*)header, header->dataLength, 0);
}
return SOCKET_ERROR;
}
void EasyTcpServer::SendDataToAll(DataHeader* header)
{
//通知其他已存在的所有客户端,有新的客户端加入
for (int n = 0; n < _clients.size(); ++n)
{
SendData(_clients[n]->sockfd(), header);
}
}
#endif
#include "EasyTcpClient.hpp"
int main()
{
EasyTcpClient client1;
client1.ConnectServer("192.168.0.106", 4567); //IP地址随服务端IP地址而变
Login login;
strcpy(login.userName, "dongshao");
strcpy(login.PassWord, "123456");
while (client1.isRun())
{
client1.Onrun();
client1.SendData(&login);
}
client1.CloseSocket();
std::cout << "客户端停止工作!" << std::endl;
getchar(); //防止程序一闪而过
return 0;
}
#include "EasyTcpServer.hpp"
#include "MessageHeader.hpp"
int main()
{
EasyTcpServer server1;
server1.Bind("192.168.0.105", 4567);
server1.Listen(5);
while (server1.isRun())
{
server1.Onrun();
}
server1.CloseSocket();
std::cout << "服务端停止工作!" << std::endl;
getchar(); //防止程序一闪而过
return 0;
}