目錄
三,將多次發送包文數據升級爲一次收發,主要將結構體進行整合,避免出錯。(需要進行數據偏移)
五,將服務器端改爲select模型,從而實現可以處理多客戶端的目標。
六,服務器升級爲select處理多客戶端模型,並且可以在某個客戶端加入時,提醒已經連接的客戶端。
今天實現任務:服務器升級爲select處理多客戶端模型,並且可以在某個客戶端加入時,提醒已經連接的客戶端。
- 實現簡單結構化傳輸信息,但是不能分辨是否是結構體。
- 實現通過網絡數據報文的格式進行定義傳輸。
- 將多次發送包文數據升級爲一次收發,主要將結構體進行整合,避免出錯。(需要進行數據偏移)
- 添加一個接收緩衝區。
- 將服務器端改爲select模型,從而實現可以處理多客戶端的目標。
- 服務器升級爲select處理多客戶端模型,並且可以在某個客戶端加入時,提醒已經連接的客戶端。
一,實現簡單結構化傳輸信息,但是不能分辨是否是結構體。
將信息設置爲結構體模式,並進行傳輸。
出現問題,因爲客戶端接收服務器回發的消息時,都是結構體模式,所以如果返回數據不是結構體模式,則會出錯。
struct DataPackage
{
int age;
char name[32];
};
程序結果:
服務器:
#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>
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<iostream>
#include<windows.h>
#include<Winsock2.h>
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<iostream>
#include<windows.h>
#include<Winsock2.h>
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<iostream>
#include<windows.h>
#include<Winsock2.h>
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<iostream>
#include<windows.h>
#include<Winsock2.h>
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:"<<login.PassWord<< endl;
//忽略判斷用戶名密碼是否正確的過程
LoginResult ret;
send(_cSock, (const char*)&ret, sizeof(LoginResult), 0);
}
break;
case CMD_LOGOUT:
{
Logout logout = {};
recv(_cSock, (char*)&logout + sizeof(DataHeader), sizeof(Logout)-sizeof(DataHeader), 0);
cout << "收到命令:CMD_LOGIN, 數據長度:" << logout.dataLength;
cout << " UserName:" << logout.userName <<endl;
//忽略判斷用戶名密碼是否正確的過程
LogoutResult ret;
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<iostream>
#include<windows.h>
#include<Winsock2.h>
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<iostream>
#include<windows.h>
#include<Winsock2.h>
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:"<<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;
}
}
//8,關閉套接字closesocket
closesocket(_sock);
//-----------------------------------
//清除Windows socket環境
WSACleanup();
system("pause");
return 0;
}
五,將服務器端改爲select模型,從而實現可以處理多客戶端的目標。
將以前的模型變爲select模型,從而可以實現高併發和跨平臺
運行截圖:
客戶端不變。
服務器端:
#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>
#include<vector>
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<SOCKET> 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;
}
六,服務器升級爲select處理多客戶端模型,並且可以在某個客戶端加入時,提醒已經連接的客戶端。
添加一個新的功能,當一個客戶端連接客戶端時,服務器向其發送已經連接的客戶端,模擬聊天系統中某個人上線時的提醒功能。
程序運行截圖:
如果這個地方仍然使用cin傳輸的話,cin函數會造成阻塞,結果就是不能及時提醒已經連接的客戶端有新的客戶端加入
所以這個地方客戶端和服務器是自動收發消息,所以會一直髮送消息,從而能夠模擬出接收新的客戶端連接的消息。
服務器端:
#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>
#include<vector>
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<SOCKET> 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<iostream>
#include<windows.h>
#include<Winsock2.h>
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;
}