服务器端使用select函数
#define WIN32_LEAN_AND_MEAN //windows.h和WinSock2.h有宏定义冲突,导致WinSock2.h必须要在windows.h前引用,定义该宏后可不用强制位置
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include<windows.h>
#include<WinSock2.h>
#include<iostream>
#include<vector>
#pragma comment(lib, "ws2_32.lib") //method_1
enum CMD
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGOUT,
CMD_LOGOUT_RESULT,
CMD_NEW_USER_JOIN,
CMD_EXIT
};
struct DataHeader
{
short data_length;
short cmd;
};
struct Login : public DataHeader
{
Login()
{
data_length = sizeof(Login);
cmd = CMD_LOGIN;
}
char username[32];
char password[32];
};
struct LoginResult : public DataHeader
{
LoginResult()
{
data_length = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
}
int result;
};
struct Logout : public DataHeader
{
Logout()
{
data_length = sizeof(Logout);
cmd = CMD_LOGOUT;
}
char username[32];
};
struct LogoutResult : public DataHeader
{
LogoutResult()
{
data_length = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
}
int result;
};
struct NewUserJOIN : public DataHeader
{
NewUserJOIN()
{
data_length = sizeof(NewUserJOIN);
cmd = CMD_NEW_USER_JOIN;
}
int sock;
};
std::vector<SOCKET> client_socks;
bool return_value_error(char* function_name, int ret)
{
if (SOCKET_ERROR == ret)
{
std::cout << function_name << "错误" << std::endl;
return TRUE;
}
return FALSE;
}
int Communicate(SOCKET client_sock)
{
//6.接收数据
DataHeader header = {};
int buf_len = recv(client_sock, (char*)&header, sizeof(DataHeader), 0);
//7.处理数据
if (return_value_error("recv", buf_len))
{
return -1;
}
else
{
if (header.cmd == CMD_EXIT)
{
std::cout << "客户端连接退出" << std::endl;
return -1;
}
else if (header.cmd == CMD_LOGIN)
{
Login data = {};
buf_len = recv(client_sock, (char *)&data + sizeof(DataHeader), sizeof(Login) - sizeof(DataHeader), 0); //如果未发生错误, send 将返回发送的总字节数,该字节数可能小于 len 参数中请求发送的数量
return_value_error("recv", buf_len);
std::cout << "接收请求:LOGIN" << ",数据长度:" << data.data_length << ",用户名:" << data.username << std::endl;
LoginResult res = {};
if (!strncmp(data.username, "aaron", sizeof("aaron") && !strncmp(data.password, "123456", sizeof("123456"))))
{
res.result = 0; //网络通信一般返回0为正常
}
else
{
res.result = 1;
}
buf_len = send(client_sock, (char *)&res, sizeof(LoginResult), 0);
return_value_error("send", buf_len);
}
else if (header.cmd == CMD_LOGOUT)
{
Logout data = {};
buf_len = recv(client_sock, (char *)&data + sizeof(DataHeader), sizeof(Logout) - sizeof(DataHeader), 0);
return_value_error("recv", buf_len);
std::cout << "接收请求:LOGOUT" << ",数据长度:" << data.data_length << ",用户名:" << data.username << std::endl;
LogoutResult res = {};
res.result = 0;
buf_len = send(client_sock, (char *)&res, sizeof(LogoutResult), 0);
return_value_error("send", buf_len);
}
}
return 0;
}
int main()
{
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat); //Windows socket网络环境的启动函数,需要引入ws2_32库文件,可以使用method_1,也可以在属性界面链接器的输入选项添加依赖项
//1.创建socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //地址系列规范,套接字规范,使用的协议
//2.绑定地址
sockaddr_in server_addr = {};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666);
server_addr.sin_addr.S_un.S_addr = INADDR_ANY; //代表本机所有地址
int ret = bind(sock, (sockaddr*)&server_addr, sizeof(sockaddr_in)); //若成功返回0
return_value_error("bind", ret);
//3.监听端口
ret = listen(sock, 5); //第二个参数为挂起的连接队列的最大长度,若成功返回0
return_value_error("listen", ret);
while (true)
{
fd_set readfds;
fd_set writefds;
fd_set exceptfds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&exceptfds);
FD_SET(sock, &readfds);
FD_SET(sock, &writefds);
FD_SET(sock, &exceptfds);
for (size_t i = 0; i < client_socks.size(); ++i)
{
FD_SET(client_socks[i], &readfds);
}
//select 函数确定一个或多个套接字的状态,并在必要时等待执行同步 I/O
//nfds是指fd_set中所有描述符的范围,而不是数量
//即是所有文件描述符最大值加1,windows中这个参数不重要
timeval time = { 0, 0 }; //设置等待时间为0,即非阻塞模式
int ret = select(sock + 1, &readfds, &writefds, &exceptfds, &time); //readfds:一个可选指针,指向要检查的一组套接字的可读性。
if (return_value_error("select", ret))
{
break;
}
if(FD_ISSET(sock, &readfds))
{
FD_CLR(sock, &readfds);
//4.接收客户端连接,
sockaddr_in client_addr = {};
int len = sizeof(sockaddr_in);
//std::cout << typeid(recv_buf).name() << std::endl;
SOCKET client_sock = accept(sock, (sockaddr*)&client_addr, &len); //accept函数返回一个新的套接字来和客户端通信,sockaddr和sockaddr_in所代表的意义一样,字节大小也一样,只不过sockaddr_in是后面创建的,之前的接口没有更改
if (INVALID_SOCKET == client_sock)
{
std::cout << "连接客户端错误" << std::endl;
break;
}
else
{
std::cout << "客户端:" << inet_ntoa(client_addr.sin_addr) << "连接成功" << std::endl;
client_socks.push_back(client_sock);
char buf[] = "hello, i'm server";
//5.发送数据
int buf_len = send(client_sock, buf, sizeof(buf), 0); //如果未发生错误, send 将返回发送的总字节数,该字节数可能小于 len 参数中请求发送的数量
return_value_error("send", buf_len);
}
}
//std::cout << "空闲时间处理其他事务" << std::endl;
for (u_int i = 0; i < readfds.fd_count; ++i)
{
if (-1 == Communicate(readfds.fd_array[i]))
{
std::vector<SOCKET>::iterator iter = std::find(client_socks.begin(), client_socks.end(), readfds.fd_array[i]);
if (iter != client_socks.end())
{
client_socks.erase(iter);
}
}
}
}
//
for (size_t i = 0; i < client_socks.size(); ++i)
{
closesocket(client_socks[i]);
}
std::cout << "服务器退出" << std::endl;
getchar();
//8.关闭套接字
closesocket(sock);
WSACleanup();
return 0;
}
客户端
#define WIN32_LEAN_AND_MEAN //windows.h和WinSock2.h有宏定义冲突,导致WinSock2.h必须要在windows.h前引用,定义该宏后可不用强制位置
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include<windows.h>
#include<WinSock2.h>
#include<iostream>
#pragma comment(lib, "ws2_32.lib") //method_1
bool judge_return_value(char* function_name, int ret)
{
if (SOCKET_ERROR == ret)
{
std::cout << function_name << "错误" << std::endl;
return FALSE;
}
return TRUE;
}
enum CMD
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGOUT,
CMD_LOGOUT_RESULT,
CMD_EXIT
};
struct DataHeader
{
short data_length;
short cmd;
};
struct Login : public DataHeader
{
Login()
{
data_length = sizeof(Login);
cmd = CMD_LOGIN;
}
char username[32];
char password[32];
};
struct LoginResult : public DataHeader
{
LoginResult()
{
data_length = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
}
int result;
};
struct Logout : public DataHeader
{
Logout()
{
data_length = sizeof(Logout);
cmd = CMD_LOGOUT;
}
char username[32];
};
struct LogoutResult : public DataHeader
{
LogoutResult()
{
data_length = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
}
int result;
};
int main()
{
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat); //Windows socket网络环境的启动函数,需要引入ws2_32库文件,可以使用method_1,也可以在属性界面链接器的输入选项添加依赖项
//创建socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //客户端的地址由系统自动分配,不需要绑定
//连接服务器
sockaddr_in server_addr = {};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666);
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int ret = connect(sock, (sockaddr*)&server_addr, sizeof(sockaddr_in)); //如果未发生错误,则返回零
judge_return_value("connect", ret);
//接受消息
char buf[128] = {};
int buf_len = recv(sock, buf, 128, 0);
judge_return_value("recv", buf_len);
std::cout << buf << std::endl;
char recv_buf[128] = {};
char send_buf[128] = {};
while (true)
{
//输入数据
std::cin >> send_buf;
if (strncmp(send_buf, "exit", sizeof("exit")) == 0)
{
DataHeader header = { 0, CMD_EXIT };
buf_len = send(sock, (char*)&header, sizeof(DataHeader), 0);
judge_return_value("send", buf_len);
break;
}
else if (strncmp(send_buf, "login", sizeof("login")) == 0)
{
Login info = {};
strcpy(info.username, "aaron");
strcpy(info.password, "123456");
buf_len = send(sock, (char*)&info, sizeof(Login), 0);
judge_return_value("send", buf_len);
LoginResult res = {};
buf_len = recv(sock, (char*)&res, sizeof(LoginResult), 0);
judge_return_value("recv", buf_len);
std::cout << "数据指令:" << res.cmd << ",数据长度:" << res.data_length << ",返回:"<<res.result << std::endl;
}
else if (strncmp(send_buf, "logout", sizeof("logout")) == 0)
{
Logout info = { };
strcpy(info.username, "aaron");
buf_len = send(sock, (char*)&info, sizeof(Logout), 0);
judge_return_value("send", buf_len);
LogoutResult res = {};
buf_len = recv(sock, (char*)&res, sizeof(LoginResult), 0);
judge_return_value("recv", buf_len);
std::cout << "数据指令:" << res.cmd << ",数据长度:" << res.data_length << ",返回:" << res.result << std::endl;
}
}
std::cout << "连接退出" << std::endl;
getchar();
//关闭套接字
closesocket(sock);
WSACleanup();
return 0;
}
客户端加入线程
#define WIN32_LEAN_AND_MEAN //windows.h和WinSock2.h有宏定义冲突,导致WinSock2.h必须要在windows.h前引用,定义该宏后可不用强制位置
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include<windows.h>
#include<WinSock2.h>
#include<iostream>
#include<thread>
#pragma comment(lib, "ws2_32.lib") //method_1
enum CMD
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGOUT,
CMD_LOGOUT_RESULT,
CMD_NEW_USER_JOIN,
CMD_EXIT
};
struct DataHeader
{
short data_length;
short cmd;
};
struct Login : public DataHeader
{
Login()
{
data_length = sizeof(Login);
cmd = CMD_LOGIN;
}
char username[32];
char password[32];
};
struct LoginResult : public DataHeader
{
LoginResult()
{
data_length = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
}
int result;
};
struct Logout : public DataHeader
{
Logout()
{
data_length = sizeof(Logout);
cmd = CMD_LOGOUT;
}
char username[32];
};
struct LogoutResult : public DataHeader
{
LogoutResult()
{
data_length = sizeof(LogoutResult);
cmd = CMD_LOGOUT_RESULT;
}
int result;
};
struct NewUserJOIN : public DataHeader
{
NewUserJOIN()
{
data_length = sizeof(NewUserJOIN);
cmd = CMD_NEW_USER_JOIN;
}
int sock;
};
bool judge_return_value(char* function_name, int ret)
{
if (SOCKET_ERROR == ret)
{
std::cout << function_name << "错误" << std::endl;
return FALSE;
}
return TRUE;
}
bool return_value_error(char* function_name, int ret)
{
if (SOCKET_ERROR == ret)
{
std::cout << function_name << "错误" << std::endl;
return TRUE;
}
return FALSE;
}
bool flag = true;
void cmdThread(SOCKET sock)
{
char send_buf[128] = {};
while (true)
{
//输入数据
std::cin >> send_buf;
if (strncmp(send_buf, "exit", sizeof("exit")) == 0)
{
DataHeader header = { 0, CMD_EXIT };
int buf_len = send(sock, (char*)&header, sizeof(DataHeader), 0);
flag = false;
judge_return_value("send", buf_len);
break;
}
else if (strncmp(send_buf, "login", sizeof("login")) == 0)
{
Login info = {};
strcpy(info.username, "aaron");
strcpy(info.password, "123456");
int buf_len = send(sock, (char*)&info, sizeof(Login), 0);
judge_return_value("send", buf_len);
LoginResult res = {};
buf_len = recv(sock, (char*)&res, sizeof(LoginResult), 0);
judge_return_value("recv", buf_len);
std::cout << "数据指令:" << res.cmd << ",数据长度:" << res.data_length << ",返回:" << res.result << std::endl;
}
else if (strncmp(send_buf, "logout", sizeof("logout")) == 0)
{
Logout info = {};
strcpy(info.username, "aaron");
int buf_len = send(sock, (char*)&info, sizeof(Logout), 0);
judge_return_value("send", buf_len);
LogoutResult res = {};
buf_len = recv(sock, (char*)&res, sizeof(LoginResult), 0);
judge_return_value("recv", buf_len);
std::cout << "数据指令:" << res.cmd << ",数据长度:" << res.data_length << ",返回:" << res.result << std::endl;
}
}
}
int main()
{
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat); //Windows socket网络环境的启动函数,需要引入ws2_32库文件,可以使用method_1,也可以在属性界面链接器的输入选项添加依赖项
//创建socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //客户端的地址由系统自动分配,不需要绑定
//连接服务器
sockaddr_in server_addr = {};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666);
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int ret = connect(sock, (sockaddr*)&server_addr, sizeof(sockaddr_in)); //如果未发生错误,则返回零
judge_return_value("connect", ret);
//接受消息
char buf[128] = {};
int buf_len = recv(sock, buf, 128, 0);
judge_return_value("recv", buf_len);
std::cout << buf << std::endl;
char recv_buf[128] = {};
char send_buf[128] = {};
std::thread t(cmdThread, sock); //创建线程输入cmd
t.detach();
while (flag)
{
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
timeval time = { 0, 0 }; //设置等待时间为0,即非阻塞模式
int ret = select(sock + 1, &readfds, 0, 0, &time);
if (FD_ISSET(sock, &readfds))
{
DataHeader header = {};
int buf_len = recv(sock, (char*)&header, sizeof(DataHeader), 0);
NewUserJOIN new_user = {};
buf_len = recv(sock, (char *)&new_user + sizeof(DataHeader), sizeof(NewUserJOIN) - sizeof(DataHeader), 0); //如果未发生错误, send 将返回发送的总字节数,该字节数可能小于 len 参数中请求发送的数量
return_value_error("recv", buf_len);
std::cout << "新客户:" << new_user.sock << "加入" << std::endl;
}
}
std::cout << "连接退出" << std::endl;
getchar();
//关闭套接字
closesocket(sock);
WSACleanup();
return 0;
}