简易TCP服务端升级为Select网络模型
这篇文章简单介绍了如何建立服务端与客户端1对1的阻塞模式网络程序,链接如下
服务端与客户端1对1网络程序
今天,我们来把这个TCP服务端升级为select模型非阻塞模式来处理多客户端,客户端不变。
具体的流程可以看下面这张图
服务端程序代码:
// Server.cpp : Defines the entry point for the console application.
//
#include <iostream>
#include <WinSock2.h>
#include <vector>
using namespace std;
#pragma comment(lib, "WS2_32.lib")
//表示链接WS2_32.lib这个库。
#define PORT 1024
vector<SOCKET> g_clients;
//发送和接收都封装成函数
int dealWrite(SOCKET s)
{
char msg[] = { "Hello client" };
int size = send(s, msg, sizeof(msg), 0);
if (size == SOCKET_ERROR)
{
cout << "发送信息失败! 错误代码:" << WSAGetLastError() << endl;
return -1;
}
else if (size == 0)
{
cout << "对方已关闭连接" << endl;
return -1;
}
else
{
cout << "信息发送成功" << endl;
}
return 0;
}
int dealRead(SOCKET s)
{
char recvMsg[128] = {};
int size = recv(s, recvMsg, sizeof(recvMsg), 0);
if (size == SOCKET_ERROR)
{
cout << "接收信息失败! 错误代码:" << WSAGetLastError() << endl;;
return -1;
}
else if (size == 0)
{
cout << "对方已关闭连接" << endl;
return -1;
}
else
{
cout << "The message from Client:" << recvMsg << endl;
}
return 0;
}
int main()
{
SOCKET sock_server, newsock;
struct sockaddr_in server_addr, client_addr;
// 初始化 winsock2.dll[12/27/2017 MagicScaring]
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2); //生成版本号
if (WSAStartup(wVersionRequested, &wsaData) != 0)
{
cout << "加载 winsock.dll失败" << endl;
return 0;
}
// 创建套接字 [12/27/2017 MagicScaring]
if ((sock_server = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
{
cout << "创建套接字失败! 错误代码:" << WSAGetLastError() << endl;
WSACleanup(); //注销WinSock动态链接库
return 0;
}
// 填写需要绑定的本地地址 [12/27/2017 MagicScaring]
int addr_len = sizeof(struct sockaddr_in);
memset((void*)&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sock_server, (struct sockaddr*)&server_addr, addr_len) != 0)
{
cout << "绑定失败!错误代码:" << WSAGetLastError() << endl;
closesocket(sock_server); //关闭已连接套接字
WSACleanup(); //注销WinSock动态链接库
return 0;
}
// 开始监听 [12/27/2017 MagicScaring]
if (listen(sock_server, 0) != 0)
{
cout << "listen调用失败!错误代码:" << WSAGetLastError() << endl;
closesocket(sock_server);
WSACleanup();
return 0;
}
else
{
cout << "listening...." << endl;
}
// 循环:接收连接请求并收发数据 [12/27/2017 MagicScaring]
while (true)
{
fd_set fdRead;
fd_set fdWrite;
fd_set fdExp;
//FD_ZERO:清空一个文件描述符集合
FD_ZERO(&fdRead);
FD_ZERO(&fdWrite);
FD_ZERO(&fdExp);
//FD_SET:将监听的文件描述符,添加到监听集合中
FD_SET(sock_server, &fdRead);
FD_SET(sock_server, &fdWrite);
FD_SET(sock_server, &fdExp);
for (size_t i = 0; i < g_clients.size(); ++i)
{
FD_SET(g_clients[i], &fdRead);
}
/*
select函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数列表:
nfds: 监听的所有文件描述符的最大描述符 + 1(内核采取轮询的方式)
readfds: 读文件描述监听集合
writefds: 写文件描述符集合
exceptfds: 异常文件描述符集合
timeout: 大于0:设置监听超时时长;NULL:阻塞监听;0:非阻塞监听
返回值:
大于0:所欲监听集合中,满足对应时间的总数
0:没有满足的
-1:出错error
*/
timeval t = { 0,500 };
int ret = select(sock_server + 1, &fdRead, &fdWrite, &fdExp, NULL);
if (ret < 0)
{
cout << "select is over" << endl;
break;
}
//FD_ISSET:判断一个文件描述符是否在一个集合中,返回值:在1,不在0
if (FD_ISSET(sock_server, &fdRead))
{
//FD_CLR:将一个文件描述符从集合中移除
FD_CLR(sock_server, &fdRead);
if ((newsock = accept(sock_server, (struct sockaddr*)&client_addr, &addr_len)) == INVALID_SOCKET)
{
cout << "accept 函数调用失败! 错误代码:" << WSAGetLastError() << endl;
break;
}
cout << "成功接收到一个连接请求!" << endl;
cout << "新客户端加入, socket:" << newsock << ",IP:" << inet_ntoa(client_addr.sin_addr)<< endl;
if (!dealWrite(newsock))
{
g_clients.push_back(newsock);
}
}
for (size_t i = 0; i < fdRead.fd_count; ++i)
{
if (-1 == dealRead(fdRead.fd_array[i]))
{
auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[i]);
if (iter != g_clients.end())
{
g_clients.erase(iter);
}
}
}
}
//关闭套接字
for (size_t i = 0; i < g_clients.size(); ++i)
{
closesocket(g_clients[i]);
}
//清除winsock环境
WSACleanup();
return 0;
}
客户端程序代码:
// Client.cpp : Defines the entry point for the console application.
//
#include <iostream>
#include <WinSock2.h>
using namespace std;
#pragma comment(lib, "WS2_32.lib")
//表示链接WS2_32.lib这个库。
#define PORT 1024
int main()
{
SOCKET sock_client;
struct sockaddr_in server_addr;
int addr_len = sizeof(struct sockaddr_in);
char msgbuffer[128];
memset(msgbuffer, 0, sizeof(msgbuffer));
// 初始化 winsock2.dll[12/27/2017 MagicScaring]
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2); //生成版本号
if (WSAStartup(wVersionRequested, &wsaData) != 0)
{
cout << "加载 winsock.dll失败" << endl;
return 0;
}
// 创建套接字 [12/27/2017 MagicScaring]
if ((sock_client = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
{
cout << "创建套接字失败! 错误代码:" << WSAGetLastError() << endl;
WSACleanup(); //注销WinSock动态链接库
return 0;
}
// 填写服务器地址 [12/27/2017 MagicScaring]
char IP[20] = { "127.0.0.1" };
memset((void*)&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr(IP);
// 与服务器建立连接 [12/27/2017 MagicScaring]
if (connect(sock_client, (struct sockaddr*)&server_addr, addr_len) == SOCKET_ERROR)
{
cout << "连接失败! 错误代码:" << WSAGetLastError() << endl;
closesocket(sock_client);
WSACleanup();
return 0;
}
while (true)
{
int size;
if ((size = recv(sock_client, msgbuffer, sizeof(msgbuffer), 0)) == SOCKET_ERROR)
{
cout << "接收信息失败! 错误代码:" << WSAGetLastError() << endl;
closesocket(sock_client);
WSACleanup();
return 0;
}
else if (size == 0)
{
cout << "对方已关闭连接" << endl;
closesocket(sock_client);
WSACleanup();
return 0;
}
else
{
cout << "The message from Server:" << msgbuffer << endl;
}
char send_msg[128] = { "Hello server" };
if ((size = send(sock_client, send_msg, sizeof(send_msg), 0)) == SOCKET_ERROR)
{
cout << "发送信息失败! 错误代码:" << WSAGetLastError() << endl;
}
else if (size == 0)
{
cout << "对方已关闭连接" << endl;
}
else
{
cout << "信息发送成功" << endl;
}
}
closesocket(sock_client);
WSACleanup();
return 0;
}
服务器程序运行结果:
客户端程序1运行结果:
客户端程序2运行结果: