目录
一,实现简单结构化传输信息,但是不能分辨是否是结构体。
二,实现通过网络数据报文的格式进行定义传输。
三,将多次发送包文数据升级为一次收发,主要将结构体进行整合,避免出错。(需要进行数据偏移)
四,添加一个接收缓冲区。
五,将服务器端改为select模型,从而实现可以处理多客户端的目标。
六,服务器升级为select处理多客户端模型,并且可以在某个客户端加入时,提醒已经连接的客户端。
今天实现任务:服务器升级为select处理多客户端模型,并且可以在某个客户端加入时,提醒已经连接的客户端。
将信息设置为结构体模式,并进行传输。
出现问题,因为客户端接收服务器回发的消息时,都是结构体模式,所以如果返回数据不是结构体模式,则会出错。
struct DataPackage
{
int age;
char name[32];
};
程序结果:
服务器:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
using namespace std;
struct DataPackage
{
int age;
char name[32];
};
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//---------------------------------
//1,建立一个socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2,bind 绑定用于接受客户端连接的网络接口
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
_sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
{
cout << "错误,绑定网络端口失败" << endl;
}
else
{
cout << "绑定网络端口成功" << endl;
}
//3,listen 监听网络端口
if (SOCKET_ERROR == listen(_sock, 5))
{
cout << "错误,监听网络端口失败" << endl;
}
else
{
cout << "监听网络端口成功" << endl;
}
//4,accept 等待客户端连接
sockaddr_in clientAddr;
int nAddrlen = sizeof(clientAddr);
SOCKET _cSock = INVALID_SOCKET;
_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
if (INVALID_SOCKET == _cSock)
{
cout << "错误,接受到无效的客户端连接" << endl;
}
cout << "新的客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;
char _recvBuf[128] = {};
while (true)
{
int nLen = recv(_cSock, _recvBuf, 128, 0);
//5,接受客户端的请求数据
if (nLen <= 0)
{
cout << "客户端已经退出,任务结束" << endl;
break;
}
cout << "收到消息" << _recvBuf << endl;
//6,处理请求
if (0 == strcmp(_recvBuf, "getInfo"))
{
DataPackage dp = { 20, "小明" };
//7.1,send 向客户端发送一条数据
send(_cSock, (const char*)&dp, sizeof(DataPackage), 0);
memset(_recvBuf, '\n',128);
}
else
{
char msgBuf[] = "???";
//7.3,send 向客户端发送一条数据
send(_cSock, msgBuf, strlen(msgBuf) + 1, 0);
}
}
//8,关闭套接字closesocket
closesocket(_sock);
//-----------------------------------
//清除Windows socket环境
WSACleanup();
system("pause");
return 0;
}
客户端:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
using namespace std;
struct DataPackage
{
int age;
char name[32];
};
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//---------------------------------
//1,用Socket API建立建立TCP客户端
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2,连接服务器 connect
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int ret = connect(_sock, (sockaddr*)&_sin, sizeof(_sin));
if (SOCKET_ERROR == ret)
{
cout << "错误,建立Socket失败" << endl;
}
else
{
cout << "建立Socket成功" << endl;
}
while (true)
{
//3,输入请求
char cmdBuf[128] = {};
cin >> cmdBuf;
//4,处理请求
if (0 == strcmp(cmdBuf, "exit"))
{
break;
}
else
{
//5,向服务器发送请求
send(_sock, cmdBuf, strlen(cmdBuf) + 1, 0);
}
//6,接受服务器消息
char recvBuf[128] = {};
int nlen = recv(_sock, recvBuf, 256, 0);
if (nlen > 0)
{
DataPackage *dp = (DataPackage *)recvBuf;
cout << "接收到数据: " << "年纪:" << dp->age << " 姓名:" << dp->name << endl;
}
}
//7,关闭套接字closesocket
closesocket(_sock);
//-----------------------------------
//清除Windows socket环境
WSACleanup();
system("pause");
return 0;
}
从第一个可以得出,如果仅仅使用结构体传输,则会产生错误,所以需要使用网络数据报文格式
报文有两个部分,包头和包体,是网络消息的基本单元
包头:描述本次消息报的大小,描述数据的作用
包体:数据所以此时定义一个联合体,来描述数据的作用
enum CMD
{
CMD_LOGIN, //登入
CMD_LOGOUT, //登出
CMD_ERROR, //错误
};
运行截图:
服务器端代码:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
using namespace std;
enum CMD
{
CMD_LOGIN, //登入
CMD_LOGOUT, //登出
CMD_ERROR, //错误
};
struct DataHeader
{
short dataLength;
short cmd;
};
//匹配四个消息结构体
struct Login
{
char useName[32];
char PassWord[32];
};
struct LoginResult
{
int result;
};
struct Logout
{
char userName[32];
};
struct LogoutResult
{
int result;
};
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//---------------------------------
//1,建立一个socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2,bind 绑定用于接受客户端连接的网络接口
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
_sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
{
cout << "错误,绑定网络端口失败" << endl;
}
else
{
cout << "绑定网络端口成功" << endl;
}
//3,listen 监听网络端口
if (SOCKET_ERROR == listen(_sock, 5))
{
cout << "错误,监听网络端口失败" << endl;
}
else
{
cout << "监听网络端口成功" << endl;
}
//4,accept 等待客户端连接
sockaddr_in clientAddr;
int nAddrlen = sizeof(clientAddr);
SOCKET _cSock = INVALID_SOCKET;
_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
if (INVALID_SOCKET == _cSock)
{
cout << "错误,接受到无效的客户端连接" << endl;
}
cout << "新的客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;
while (true)
{
DataHeader header = {};
int nLen = recv(_cSock, (char*)&header, sizeof(DataHeader), 0);
//5,接受客户端的请求数据
if (nLen <= 0)
{
cout << "客户端已经退出,任务结束" << endl;
break;
}
cout << "收到命令:" << header.cmd << " 数据长度:" << header.dataLength << endl;
switch (header.cmd)
{
case CMD_LOGIN:
{
Login login = {};
recv(_cSock, (char*)&login, sizeof(Login), 0);
//忽略判断用户名密码是否正确的过程
LoginResult ret = { 1 };
DataHeader hd = { CMD_LOGIN };
send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
send(_cSock, (const char*)&ret, sizeof(LoginResult), 0);
}
break;
case CMD_LOGOUT:
{
Logout logout = {};
recv(_cSock, (char*)&logout, sizeof(Logout), 0);
//忽略判断用户名密码是否正确的过程
LogoutResult ret = { 1 };
send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
send(_cSock, (const char*)&ret, sizeof(Logout), 0);
}
break;
default:
header.cmd = CMD_ERROR;
header.dataLength = 0;
send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
break;
}
}
//8,关闭套接字closesocket
closesocket(_sock);
//-----------------------------------
//清除Windows socket环境
WSACleanup();
system("pause");
return 0;
}
客户端代码:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
using namespace std;
enum CMD
{
CMD_LOGIN, //登入
CMD_LOGOUT, //登出
CMD_ERROR, //错误
};
struct DataHeader
{
short dataLength;
short cmd;
};
//匹配四个消息结构体
struct Login
{
char useName[32];
char PassWord[32];
};
struct LoginResult
{
int result;
};
struct Logout
{
char userName[32];
};
struct LogoutResult
{
int result;
};
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//---------------------------------
//1,用Socket API建立建立TCP客户端
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2,连接服务器 connect
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int ret = connect(_sock, (sockaddr*)&_sin, sizeof(_sin));
if (SOCKET_ERROR == ret)
{
cout << "错误,建立Socket失败" << endl;
}
else
{
cout << "建立Socket成功" << endl;
}
while (true)
{
//3,输入请求
char cmdBuf[128] = {};
cin >> cmdBuf;
//4,处理请求
if (0 == strcmp(cmdBuf, "exit")){
break;
}
else if (0 == strcmp(cmdBuf, "login")){
Login login = { "lyd", "lydmm" };
DataHeader dh = { sizeof(Login), CMD_LOGIN };
//5,向服务器发送请求
send(_sock, (const char *)&dh, sizeof(DataHeader), 0);
send(_sock, (const char *)&login, sizeof(Login), 0);
//接收服务器返回数据
DataHeader retHeader = {};
LoginResult loginRet = {};
recv(_sock, (char *)&retHeader, sizeof(retHeader), 0);
recv(_sock, (char *)&loginRet, sizeof(LoginResult), 0);
cout << "LoginResult:" << loginRet.result << endl;
}
else if (0 == strcmp(cmdBuf, "logout")){
Logout logout = { "lyb" };
DataHeader dh = { sizeof(Login), CMD_LOGOUT };
//5,向服务器发送请求
send(_sock, (const char *)&dh, sizeof(DataHeader), 0);
send(_sock, (const char *)&logout, sizeof(Logout), 0);
//接收服务器返回数据
DataHeader retHeader = {};
LogoutResult logoutRet = {};
recv(_sock, (char *)&retHeader, sizeof(retHeader), 0);
recv(_sock, (char *)&logoutRet, sizeof(LogoutResult), 0);
cout << "LogoutResult:" << logoutRet.result << endl;
}
else{
cout << "不支持的命令,请重新输入" << endl;
}
}
//7,关闭套接字closesocket
closesocket(_sock);
//-----------------------------------
//清除Windows socket环境
WSACleanup();
system("pause");
return 0;
}
上面的方式中,传递的结构体和描述数据的作用(包头和包体)是分开的,容易出错,所以此时将两者结合起来
运行截图:
服务器代码:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
using namespace std;
enum CMD
{
CMD_LOGIN, //登入
CMD_LOGIN_RESULT,
CMD_LOGOUT, //登出
CMD_LOGOUT_RESULT,
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;
};
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//---------------------------------
//1,建立一个socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2,bind 绑定用于接受客户端连接的网络接口
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
_sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
{
cout << "错误,绑定网络端口失败" << endl;
}
else
{
cout << "绑定网络端口成功" << endl;
}
//3,listen 监听网络端口
if (SOCKET_ERROR == listen(_sock, 5))
{
cout << "错误,监听网络端口失败" << endl;
}
else
{
cout << "监听网络端口成功" << endl;
}
//4,accept 等待客户端连接
sockaddr_in clientAddr;
int nAddrlen = sizeof(clientAddr);
SOCKET _cSock = INVALID_SOCKET;
_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
if (INVALID_SOCKET == _cSock)
{
cout << "错误,接受到无效的客户端连接" << endl;
}
cout << "新的客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;
while (true)
{
DataHeader header = {};
int nLen = recv(_cSock, (char*)&header, sizeof(DataHeader), 0);
//5,接受客户端的请求数据
if (nLen <= 0)
{
cout << "客户端已经退出,任务结束" << endl;
break;
}
switch (header.cmd)
{
case CMD_LOGIN:
{
Login login = {};
//做数据偏移
recv(_cSock, (char*)&login+sizeof(DataHeader), sizeof(Login)-sizeof(DataHeader), 0);
cout << "收到命令:CMD_LOGIN, 数据长度:" << login.dataLength;
cout << " UserName:" << login.userName<<" PassWord:"<
客户端代码:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
using namespace std;
enum CMD
{
CMD_LOGIN, //登入
CMD_LOGIN_RESULT,
CMD_LOGOUT, //登出
CMD_LOGOUT_RESULT,
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;
};
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//---------------------------------
//1,用Socket API建立建立TCP客户端
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2,连接服务器 connect
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int ret = connect(_sock, (sockaddr*)&_sin, sizeof(_sin));
if (SOCKET_ERROR == ret)
{
cout << "错误,建立Socket失败" << endl;
}
else
{
cout << "建立Socket成功" << endl;
}
while (true)
{
//3,输入请求
char cmdBuf[128] = {};
cin >> cmdBuf;
//4,处理请求
if (0 == strcmp(cmdBuf, "exit")){
break;
}
else if (0 == strcmp(cmdBuf, "login")){
Login login;
strcpy(login.userName, "lyd");
strcpy(login.PassWord, "lydmima");
//5,向服务器发送请求
send(_sock, (const char *)&login, sizeof(Login), 0);
//接收服务器返回数据
LoginResult loginRet = {};
recv(_sock, (char *)&loginRet, sizeof(LoginResult), 0);
cout << "LoginResult:" << loginRet.result << endl;
}
else if (0 == strcmp(cmdBuf, "logout")){
Logout logout;
strcpy(logout.userName, "lyb");
//5,向服务器发送请求
send(_sock, (const char *)&logout, sizeof(Logout), 0);
//接收服务器返回数据
LogoutResult logoutRet = {};
recv(_sock, (char *)&logoutRet, sizeof(LogoutResult), 0);
cout << "LogoutResult:" << logoutRet.result << endl;
}
else{
cout << "不支持的命令,请重新输入" << endl;
}
}
//7,关闭套接字closesocket
closesocket(_sock);
//-----------------------------------
//清除Windows socket环境
WSACleanup();
system("pause");
return 0;
}
此处添加一个接收缓冲区的作用就是,当出现高并发时,如果发送数据过大时,可能无法全部接收,所以此时使用一个缓冲区来进行接收
客户端不变。
服务器:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
using namespace std;
enum CMD
{
CMD_LOGIN, //登入
CMD_LOGIN_RESULT,
CMD_LOGOUT, //登出
CMD_LOGOUT_RESULT,
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;
};
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//---------------------------------
//1,建立一个socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2,bind 绑定用于接受客户端连接的网络接口
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
_sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
{
cout << "错误,绑定网络端口失败" << endl;
}
else
{
cout << "绑定网络端口成功" << endl;
}
//3,listen 监听网络端口
if (SOCKET_ERROR == listen(_sock, 5))
{
cout << "错误,监听网络端口失败" << endl;
}
else
{
cout << "监听网络端口成功" << endl;
}
//4,accept 等待客户端连接
sockaddr_in clientAddr;
int nAddrlen = sizeof(clientAddr);
SOCKET _cSock = INVALID_SOCKET;
_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
if (INVALID_SOCKET == _cSock)
{
cout << "错误,接受到无效的客户端连接" << endl;
}
cout << "新的客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;
while (true)
{
//缓冲区
char szRecv[1024] = {};
//5,接受客户端的请求数据
int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
DataHeader *header = (DataHeader*)szRecv;
if (nLen <= 0)
{
cout << "客户端已经退出,任务结束" << endl;
break;
}
switch (header->cmd)
{
case CMD_LOGIN:
{
//做数据偏移
recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength-sizeof(DataHeader), 0);
Login *login = (Login*)szRecv;
cout << "收到命令:CMD_LOGIN, 数据长度:" << login->dataLength;
cout << " UserName:" << login->userName<<" PassWord:"<PassWord<< endl;
//忽略判断用户名密码是否正确的过程
LoginResult ret;
send(_cSock, (const char*)&ret, sizeof(LoginResult), 0);
}
break;
case CMD_LOGOUT:
{
recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength-sizeof(DataHeader), 0);
Login *logout = (Login*)szRecv;
cout << "收到命令:CMD_LOGIN, 数据长度:" << logout->dataLength;
cout << " UserName:" << logout->userName <
将以前的模型变为select模型,从而可以实现高并发和跨平台
运行截图:
客户端不变。
服务器端:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#include
using namespace std;
enum CMD
{
CMD_LOGIN, //登入
CMD_LOGIN_RESULT,
CMD_LOGOUT, //登出
CMD_LOGOUT_RESULT,
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;
};
vector g_clients;
int processor(SOCKET _cSock)
{
//缓冲区
char szRecv[1024] = {};
//5,接受客户端的请求数据
int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
DataHeader *header = (DataHeader*)szRecv;
if (nLen <= 0)
{
cout << "客户端已经退出,任务结束" << endl;
return -1;
}
switch (header->cmd)
{
case CMD_LOGIN:
{
//做数据偏移
recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
Login *login = (Login*)szRecv;
cout << "收到命令:CMD_LOGIN, 数据长度:" << login->dataLength;
cout << " UserName:" << login->userName << " PassWord:" << login->PassWord << endl;
//忽略判断用户名密码是否正确的过程
LoginResult ret;
send(_cSock, (const char*)&ret, sizeof(LoginResult), 0);
}
break;
case CMD_LOGOUT:
{
recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
Login *logout = (Login*)szRecv;
cout << "收到命令:CMD_LOGIN, 数据长度:" << logout->dataLength;
cout << " UserName:" << logout->userName << endl;
//忽略判断用户名密码是否正确的过程
LogoutResult ret;
send(_cSock, (const char*)&ret, sizeof(Logout), 0);
}
break;
default:
{
DataHeader header = { 0, CMD_ERROR };
send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
}
break;
}
}
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//---------------------------------
//1,建立一个socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2,bind 绑定用于接受客户端连接的网络接口
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
_sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
{
cout << "错误,绑定网络端口失败" << endl;
}
else
{
cout << "绑定网络端口成功" << endl;
}
//3,listen 监听网络端口
if (SOCKET_ERROR == listen(_sock, 5))
{
cout << "错误,监听网络端口失败" << endl;
}
else
{
cout << "监听网络端口成功" << endl;
}
while (true)
{
//伯克利套接字
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);
for (int n = (int)g_clients.size() - 1; n >= 0; n--)
{
FD_SET(g_clients[n],&fdRead);
}
//nfds 是一个整数值,是指fd_set集合中所有描述符(socket)的范围,而不是数量
//即是所有文件描述符最大值+1,在Windows中这个参数可以写0
int ret = select(_sock + 1, &fdRead, &fdWrite, &fdExp, NULL);
if (ret < 0)
{
cout << "select任务结束" << endl;
break;
}
if (FD_ISSET(_sock, &fdRead))
{
FD_CLR(_sock, &fdRead);
//4,accept 等待客户端连接
sockaddr_in clientAddr = { };
int nAddrlen = sizeof(clientAddr);
SOCKET _cSock = INVALID_SOCKET;
_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
if (INVALID_SOCKET == _cSock)
{
cout << "错误,接受到无效的客户端连接" << endl;
}
g_clients.push_back(_cSock);
cout << "新的客户端加入:"<<(int)_cSock<<" "<< inet_ntoa(clientAddr.sin_addr) << endl;
}
for (size_t n = 0; n < fdRead.fd_count; n++)
{
if (processor(fdRead.fd_array[n]) == -1)
{
auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[n]);
if (iter != g_clients.end())
{
g_clients.erase(iter);
}
}
}
}
for (size_t n = g_clients.size() - 1; n >= 0; n--)
{
closesocket(g_clients[n]);
}
//8,关闭套接字closesocket
closesocket(_sock);
//-----------------------------------
//清除Windows socket环境
WSACleanup();
system("pause");
return 0;
}
添加一个新的功能,当一个客户端连接客户端时,服务器向其发送已经连接的客户端,模拟聊天系统中某个人上线时的提醒功能。
程序运行截图:
如果这个地方仍然使用cin传输的话,cin函数会造成阻塞,结果就是不能及时提醒已经连接的客户端有新的客户端加入
所以这个地方客户端和服务器是自动收发消息,所以会一直发送消息,从而能够模拟出接收新的客户端连接的消息。
服务器端:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#include
using namespace std;
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(NewUserJoin);
cmd = CMD_NEW_USER_JOIN;
sock = 0;
}
int sock;
};
vector g_clients;
int processor(SOCKET _cSock)
{
//缓冲区
char szRecv[1024] = {};
//5,接受客户端的请求数据
int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
DataHeader *header = (DataHeader*)szRecv;
if (nLen <= 0)
{
cout << "客户端已经退出,任务结束" << endl;
return -1;
}
switch (header->cmd)
{
case CMD_LOGIN:
{
//做数据偏移
recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
Login *login = (Login*)szRecv;
cout << "收到命令:CMD_LOGIN, 数据长度:" << login->dataLength;
cout << " UserName:" << login->userName << " PassWord:" << login->PassWord << endl;
//忽略判断用户名密码是否正确的过程
LoginResult ret;
send(_cSock, (const char*)&ret, sizeof(LoginResult), 0);
}
break;
case CMD_LOGOUT:
{
recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
Login *logout = (Login*)szRecv;
cout << "收到命令:CMD_LOGIN, 数据长度:" << logout->dataLength;
cout << " UserName:" << logout->userName << endl;
//忽略判断用户名密码是否正确的过程
LogoutResult ret;
send(_cSock, (const char*)&ret, sizeof(Logout), 0);
}
break;
default:
{
DataHeader header = { 0, CMD_ERROR };
send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
}
break;
}
}
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//---------------------------------
//1,建立一个socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2,bind 绑定用于接受客户端连接的网络接口
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
_sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
{
cout << "错误,绑定网络端口失败" << endl;
}
else
{
cout << "绑定网络端口成功" << endl;
}
//3,listen 监听网络端口
if (SOCKET_ERROR == listen(_sock, 5))
{
cout << "错误,监听网络端口失败" << endl;
}
else
{
cout << "监听网络端口成功" << endl;
}
while (true)
{
//伯克利套接字
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);
for (int n = (int)g_clients.size() - 1; n >= 0; n--)
{
FD_SET(g_clients[n], &fdRead);
}
//nfds 是一个整数值,是指fd_set集合中所有描述符(socket)的范围,而不是数量
//即是所有文件描述符最大值+1,在Windows中这个参数可以写0
//添加非阻塞
//timeval t = { 1, 0 };
int ret = select(_sock + 1, &fdRead, &fdWrite, &fdExp, NULL);
if (ret < 0)
{
cout << "select任务结束" << endl;
break;
}
if (FD_ISSET(_sock, &fdRead))
{
FD_CLR(_sock, &fdRead);
//4,accept 等待客户端连接
sockaddr_in clientAddr = {};
int nAddrlen = sizeof(clientAddr);
SOCKET _cSock = INVALID_SOCKET;
_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
if (INVALID_SOCKET == _cSock)
{
cout << "错误,接受到无效的客户端连接" << endl;
}
for (int n = (int)g_clients.size() - 1; n >= 0; n--)
{
NewUserJoin userjoin;
send(g_clients[n], (const char*)&userjoin, sizeof(NewUserJoin), 0);
}
g_clients.push_back(_cSock);
cout << "新的客户端加入:" << (int)_cSock << " " << inet_ntoa(clientAddr.sin_addr) << endl;
}
for (size_t n = 0; n < fdRead.fd_count; n++)
{
if (processor(fdRead.fd_array[n]) == -1)
{
auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[n]);
if (iter != g_clients.end())
{
g_clients.erase(iter);
}
}
}
}
for (size_t n = g_clients.size() - 1; n >= 0; n--)
{
closesocket(g_clients[n]);
}
//8,关闭套接字closesocket
closesocket(_sock);
//-----------------------------------
//清除Windows socket环境
WSACleanup();
system("pause");
return 0;
}
客户端:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
using namespace std;
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(NewUserJoin);
cmd = CMD_NEW_USER_JOIN;
sock = 0;
}
int sock;
};
int processor(SOCKET _cSock)
{
//缓冲区
char szRecv[1024] = {};
//5,接受客户端的请求数据
int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
DataHeader *header = (DataHeader*)szRecv;
if (nLen <= 0)
{
cout << "与服务器断开连接,任务结束" << endl;
return -1;
}
switch (header->cmd)
{
case CMD_LOGIN_RESULT:
{
recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
LoginResult *login = (LoginResult*)szRecv;
cout << "收到服务端消息:CMD_LOGIN_RESULT " << _cSock << " 数据长度:" << login->dataLength << endl;
}
break;
case CMD_LOGOUT_RESULT:
{
recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
LogoutResult *logout = (LogoutResult*)szRecv;
cout << "收到服务端消息:CMD_LOGOUT_RESULT " << _cSock << " 数据长度:" << logout->dataLength << endl;
}
break;
case CMD_NEW_USER_JOIN:
{
recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
NewUserJoin *userJoin = (NewUserJoin*)szRecv;
cout << "收到服务端消息:CMD_NEW_USER_JOIN " << _cSock << " 数据长度:" << userJoin->dataLength << endl;
}
break;
}
}
int main()
{
//启动Windows socket 2.x环境
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//---------------------------------
//1,用Socket API建立建立TCP客户端
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2,连接服务器 connect
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);
_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int ret = connect(_sock, (sockaddr*)&_sin, sizeof(_sin));
if (SOCKET_ERROR == ret)
{
cout << "错误,建立Socket失败" << endl;
}
else
{
cout << "建立Socket成功" << endl;
}
while (true)
{
fd_set fdReads;
FD_ZERO(&fdReads);
FD_SET(_sock, &fdReads);
//添加非阻塞
//timeval t = { 1, 0 };
int ret = select(_sock, &fdReads, 0, 0, NULL);
if (ret < 0)
{
cout << "select 任务结束1" << endl;
break;
}
if (FD_ISSET(_sock, &fdReads))
{
FD_CLR(_sock, &fdReads);
if (-1 == processor(_sock))
{
cout << "select 任务结束2" << endl;
break;
}
}
Login login;
strcpy(login.userName, "lyd");
strcpy(login.PassWord, "lydmima");
//5,向服务器发送请求
send(_sock, (const char *)&login, sizeof(Login), 0);
Sleep(1000);
}
//7,关闭套接字closesocket
closesocket(_sock);
//-----------------------------------
//清除Windows socket环境
WSACleanup();
cout << "已退出" << endl;
system("pause");
return 0;
}