数据包格式
#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 cmd;
short dataLength;
};
//登录
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 = 1;
}
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 = 1;
}
int result;
char data[1024];
};
//新用户加入
struct NewUserJoin :public DataHeader{
NewUserJoin(){
dataLength = sizeof(NewUserJoin);
cmd = CMD_NEW_USER_JOIN;
scok = 0;
}
int scok;
};
#endif
client端:主要改进在接收数据的函数
#ifndef _easyClient_hpp_
#define _easyClient_hpp_
#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#include<windows.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")//动态链接库
#include<iostream>
#include<stdio.h>
#include<thread>
#include "MH.h"
// 利用當地的消息緩存區來將存储接收的消息,先将消息的头部存储,然后通过对消息长度的提取,提取出消息。
//进而处理消息。处理粘包,将发送的消息分离开来
class easyClient
{
SOCKET sock;
public:
easyClient()
{
sock = INVALID_SOCKET;
}
//虚析构函数
virtual ~easyClient()
{
Close();
}
//初始化socket
void InitSocket(){
//启动winsock2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
if (sock != INVALID_SOCKET){
printf("<SOCKET=%d>关闭旧连接", sock);
Close();
}
//1、建立socket
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
}
int Connect(char* ip, unsigned short port){
if (sock == INVALID_SOCKET){
InitSocket();
}
// 2、连接服务器
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(port);//host to net unsigned short
_sin.sin_addr.S_un.S_addr = inet_addr(ip);
int ret = connect(sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
if (SOCKET_ERROR == ret)
std::printf("绑定网络端口失败。。。");
return ret;
}
//关闭socket
void Close(){
//关闭winsock 2.x环境
if (sock != INVALID_SOCKET){
closesocket(sock);
WSACleanup();
sock = INVALID_SOCKET;
}
}
//处理网络消息
bool OnRun(){
if (isRun()){
fd_set fdReader;
FD_ZERO(&fdReader);
FD_SET(sock, &fdReader);
timeval t = { 1, 0 };
int ret = select(sock, &fdReader, 0, 0, NULL);
if (ret < 0){
std::printf("<SOCKET=%d>select 任务结束1\n", sock);
return false;
}
if (FD_ISSET(sock, &fdReader)){
FD_CLR(sock, &fdReader);
if (-1 == RecvData(sock)){
std::printf("<SOCKET=%d>select 任务结束2\n", sock);
return false;
}
}
return true;
}
return false;
}
//缓冲区最小单元大小
#define RECV_BUFF_SIZE 1024
//接收缓冲区
char _szRecv[RECV_BUFF_SIZE] ;
//第二缓冲区
//char _szMagBuf[RECV_BUFF_SIZE * 10] = {};
char _szMagBuf[10240] ;
//消息缓冲区尾部位置
int _lastPos = 0;
//接收消息,处理粘包
int RecvData(SOCKET cSocket){
//5、接收客户端请求数据
int nLen = recv(cSocket, _szRecv, RECV_BUFF_SIZE, 0);
//当前消息长度
DataHeader* header = (DataHeader*)_szRecv;
if (nLen <= 0){
std::printf("与服务器断开连接,任务结束。\n", cSocket);
return -1;
}
//将接收到的消息拷贝到当地的消息缓冲区
memcpy(_szMagBuf+_lastPos, _szRecv, nLen);
//消息缓冲区的数据尾部位置后移
_lastPos += nLen;
//消息缓冲区是否大于消息头长度
while (_lastPos >= sizeof(DataHeader)){
//消息的长度
DataHeader* header = (DataHeader*)_szRecv;
if (_lastPos > header->dataLength){
//未处理消息缓冲区的长度
int nSize = _lastPos - header->dataLength;
//处理网络消息
OnNetMag(cSocket,header);
//将消息缓冲区剩余未处理数据前移
memcpy(_szMagBuf, _szMagBuf + header->dataLength, nSize);
//消息的尾部位置前移
_lastPos = nSize;
}
else{
//消息缓冲区剩余数据不够一条完整的消息
break;
}
}
//recv(_cSocket, _szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
//OnNetMag(_cSocket, header);
return 0;
}
//响应网络消息
void OnNetMag(SOCKET _cSocket, DataHeader* header){
//6、处理请求
switch (header->cmd){
case CMD_LOGIN_RESULT: {
LoginResult* login = (LoginResult*)header;
std::printf("收到服务器=%d消息:CMD_LOGIN_RESULT,数据长度:%d \n", _cSocket, header->dataLength);
} break;
case CMD_LOGOUT_RESULT:{
LogoutResult* logout = (LogoutResult*)header;
std::printf("收到服务器=%d消息:CMD_LOGOUT_RESULT,数据长度:%d \n", _cSocket, header->dataLength);
}break;
case CMD_NEW_USER_JOIN:{
NewUserJoin* userjoin = (NewUserJoin*)header;
std::printf("收到服务器=%d消息:CMD__NEW_USER_JOIN,新用户加入\n", _cSocket);
} break;
case CMD_ERROR:
{
printf("<Socket=%d>收到服务端消息:CMD_ERROR,数据长度:%d\n", _cSocket, header->dataLength);
}break;
default:{
printf("<Socket=%d>收到服务端未定义消息,数据长度:%d\n", _cSocket, header->dataLength);
}
}
std::printf("发送成功\n");
}
//发送网络消息
int SendData(DataHeader* header){
if (isRun() && header){
return send(sock, (const char*)header, header->dataLength, 0);
}
return INVALID_SOCKET;
}
//是否工作中
bool isRun(){
return sock != INVALID_SOCKET;
}
private:
};
#endif
client端的main.cpp
主函数:
#include<iostream>
#include<stdio.h>
#include<thread>
#include "client1.h"
#include"MH.h"
using namespace std;
// 利用當地的消息緩存區來將存储接收的消息,先将消息的头部存储,然后通过对消息长度的提取,提取出消息。
//进而处理消息。处理粘包,将发送的消息分离开来
bool g_bRun = true;
void cmdThread()
{
while (true){
char cmdBuf[256] = {};
scanf("%s", cmdBuf);
if (0 == strcmp(cmdBuf, "exit")){
std::printf("退出thread线程\n");
break;
}
else{
printf("不支持的命令\n");
}
}
}
int main(){
const int ncount = 10;
easyClient* client[ncount];
for (int n = 0; n < ncount; n++){
client[n] = new easyClient();
client[n]->Connect("127.0.0.1", 4567);
}
//启动线程
thread t1(cmdThread);
t1.detach();
Login login;
strcpy(login.username, "lyd");
strcpy(login.password, "123");
while (g_bRun){
for (int n = 0; n < ncount; n++){
client[n]->SendData(&login);
}
}
for (int n = 0; n < ncount; n++){
client[n]->Close();
delete client[n];
}
std::printf("已退出\n");
std::system("pause");
return 0;
}
server端:
//将接受的client端封装到ClientSocket中,对client的对象进行处理。
//注意:因为栈内存很小,所以在生成对象的时候,最好使用指针来完成。指针指向实际对象存储在堆内存上。
#ifndef _servers_hpp_
#define _servers_hpp_
#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#include<windows.h>
#include<WinSock2.h>
#include<iostream>
#include<vector>
#include"MH.h"
#pragma comment(lib,"ws2_32.lib")//动态链接库
//可以在属性的链接库中添加
//using namespace std;
// 利用當地的消息緩存區來將存储接收的消息,先将消息的头部存储,然后通过对消息长度的提取,提取出消息。
//进而处理消息。处理粘包,将发送的消息分离开来
#define RECV_BUFF_SIZE 1024
//class ClientSocket,纪录每个链接服务器的客户端
class ClientSocket{
public:
ClientSocket(SOCKET sockfd=INVALID_SOCKET){
_sockfd = sockfd;
memset(_szMagBuf, 0, sizeof(_szMagBuf));
_lastPos = 0;
}
SOCKET sockfd(){
return _sockfd;
}
char* msgBuf(){
return _szMagBuf;
}
int getLastPos(){
return _lastPos;
}
void setLastPos(int pos){
_lastPos = pos;
}
private:
SOCKET _sockfd;
//第二缓冲区 消息缓冲区
char _szMagBuf[RECV_BUFF_SIZE * 10];
//char _szMagBuf[2048] = {};
//消息缓冲区尾部位置
int _lastPos ;
};
// new 堆内存
// int a=0; 栈上面
class servers{
private:
std::vector<ClientSocket*> g_clients;
SOCKET sock;
public:
servers(){
sock = INVALID_SOCKET;
}
virtual ~servers(){
Close();
}
//初始化Socket
SOCKET InitSocket(){
//启动winsock2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
if (sock != INVALID_SOCKET){
printf("<SOCKET=%d>关闭旧连接", sock);
Close();
}
//1、建立socket
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
return sock;
}
//绑定端口号
int Bind(const char* ip, unsigned short port){
if (sock == INVALID_SOCKET){
InitSocket();
}
// 2、绑定网络端口
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(port);//host to net unsigned short
_sin.sin_addr.S_un.S_addr = inet_addr(ip);
int ret = bind(sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
if (SOCKET_ERROR == ret)
printf("绑定网络端口失败。。。");
return ret;
}
//监听端口号
int Listen(int n){
//3、listen 监听端口
int ret = listen(sock, n);
if (SOCKET_ERROR == ret)
printf("<SOCKET=%d>监听网络端口失败。。。", sock);
return ret;
}
//接收客户端连接
SOCKET Accept(){
//4、accept 等待客户端连接
sockaddr_in clientAddr = {};
int nAddrlen = sizeof(sockaddr_in);
SOCKET _cSocket = INVALID_SOCKET;
_cSocket = accept(sock, (sockaddr*)&clientAddr, &nAddrlen);
if (INVALID_SOCKET == _cSocket){
printf("<SOCKET=%d>错误,接收到无效客户端。。。\n", (int)_cSocket);
}
else
{
NewUserJoin userjoin;
SendDataToAll(&userjoin);
g_clients.push_back(new ClientSocket(_cSocket));
printf("新客户端加入:socket=%d,IP=%s\n", (int)_cSocket, inet_ntoa(clientAddr.sin_addr));
}
return _cSocket;
}
//关闭socket
void Close(){
//关闭winsock 2.x环境
if (sock != INVALID_SOCKET){
for (int n = (int)g_clients.size() - 1; n >= 0; n--){
closesocket(g_clients[n]->sockfd());
delete g_clients[n];
}
// 7、关闭服务端套接字
closesocket(sock);
WSACleanup();
sock = INVALID_SOCKET;
}
}
//处理网络消息
bool 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);
SOCKET maxSock = sock;
for (int n = (int)g_clients.size() - 1; n >= 0; n--){
//判断集合是否在集合中
FD_SET(g_clients[n]->sockfd(), &fdRead);
if (maxSock < g_clients[n]->sockfd())
maxSock = g_clients[n]->sockfd();
}
timeval t = { 1, 0 };
int ret = select(sock + 1, &fdRead, &fdWrite, &fdExp, &t);
if (ret < 0){
printf("select 客户端已退出。。。\n");
Close();
return false;
}
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]->sockfd(), &fdRead)){
{
if (-1 == RecvData(g_clients[n]))
{
//auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[n]);
auto iter = g_clients.begin() + 1;
if (iter != g_clients.end())
{
delete g_clients[n];
g_clients.erase(iter);
}
}
}
}
}
return true;
}
else{
return false;
}
}
//发送指定socket网络消息
int SendData(SOCKET _cSocket, DataHeader* header){
if (isRun() && header){
return send(_cSocket, (const char*)header, header->dataLength, 0);
}
return INVALID_SOCKET;
}
//发送指定socket网络消息
void SendDataToAll(DataHeader* header){
if (isRun() && header){
for (int n = (int)g_clients.size() - 1; n >= 0; n--){
SendData(g_clients[n]->sockfd(), header);
}
}
}
//是否工作中
bool isRun(){
return sock != INVALID_SOCKET;
}
//消息缓冲区
char _szRecv[RECV_BUFF_SIZE] ;
//接收消息,处理粘包
int RecvData(ClientSocket* pClient){
//5、接收客户端请求数据
int nLen = recv(pClient->sockfd(), _szRecv, RECV_BUFF_SIZE, 0);
if (nLen <= 0){
std::printf("客户端<SOCKET=%d>已退出\n", pClient->sockfd());
return -1;
}
//将接收到的消息拷贝到当地的消息缓冲区
memcpy(pClient->msgBuf() + pClient->getLastPos(), _szRecv, nLen);
//消息缓冲区的数据尾部位置后移
pClient->setLastPos(pClient->getLastPos() + nLen);
//消息缓冲区是否大于消息头长度
while (pClient->getLastPos() >= sizeof(DataHeader)){
//消息的长度
DataHeader* header = (DataHeader*)pClient->msgBuf();
//收到消息大于消息头,开始处理数据
if (pClient->getLastPos() > header->dataLength){
//未处理消息缓冲区的长度
int nSize = pClient->getLastPos() - header->dataLength;
//处理网络消息
OnNetMag(pClient->sockfd(), header);
//将消息缓冲区剩余未处理数据前移
memcpy(pClient->msgBuf(), pClient->msgBuf() + header->dataLength, nSize);
//消息的尾部位置前移
pClient->setLastPos(nSize);
}
else{
//消息缓冲区剩余数据不够一条完整的消息
break;
}
}
return 1;
}
//响应网络消息
virtual void OnNetMag(SOCKET _cSocket, DataHeader* header){
switch (header->cmd){
case CMD_LOGIN: {
Login* login = (Login*)header;
std::printf("收到<SOCKET=%d>命令:CMD_LOGIN,数据长度:%d ,username:%s password:%s \n", _cSocket, login->dataLength, login->username, login->password);
std::printf("姓名为:%s上线了。。。\n", login->username);
LoginResult ret;
SendData(_cSocket, (DataHeader*)&ret);
}break;
case CMD_LOGOUT:{
Logout* logout = (Logout*)header;
std::printf("收到<SOCKET=%d>命令:CMD_LOGOUT,数据长度:%d username:%s \n", _cSocket, logout->dataLength, logout->userName);
std::printf("姓名为:%s 已下线。。。\n", logout->userName);
LogoutResult ret;
SendData(_cSocket, (DataHeader*)&ret);
}
break;
default:
{
printf("<Socket=%d>收到未定义消息,数据长度:%d\n", _cSocket, header->dataLength);
DataHeader header ;
SendData(_cSocket, &header);
}
break;
}
std::printf("发送成功\n");
}
};
#endif
server的main.cpp
主函数
#include<iostream>
#include"server1.h"
#include<thread>
// 利用當地的消息緩存區來將存储接收的消息,先将消息的头部存储,然后通过对消息长度的提取,提取出消息。
//进而处理消息。处理粘包,将发送的消息分离开来
bool g_bRun = true;
void cmdThread(){
while (true){
char cmdBuf[256] = {};
scanf("%s", cmdBuf);
if (0 == strcmp(cmdBuf, "exit")){
g_bRun = false;
printf("退出cmdThread服务器\n");
break;
}
else{
printf("不支持的命令\n");
}
}
}
int main01(){
servers server;
server.InitSocket();
server.Bind("127.0.0.1", 4567);
server.Listen(5);
while (g_bRun){
server.OnRun();
}
server.Close();
printf("已退出,任务结束");
system("pause");
return 0;
}