C++ Windows Socket五种I/O模型之Select模型

概述

Windows操作系统提供了选择(Select)、异步选择 (WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口 (Completion Port)共五种I/O模型。每一种模型均适用于一种特定的应用场景。今天主要讲一下Select 模型。

涉及的API

int WSAAPI select(
int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
const timeval timeout
);
参数:nfds:忽略。

readnfds: 指向检查可读性的套接字集合的可选的指针。
writefds: 指向检查可写性的套接字集合的可选的指针。
exceptfds: 指向检查错误的套接字集合的可选的指针。
timeout: select函数需要等待的最长时间,需要以TIMEVAL结构体格式提供此参数,对于阻塞操作,此参数为null
返回值:成功返回 fd_set集合体已经准备好的socket句柄,失败返回SOCKET_ERROR,返回值为 0 表示是等待超时

示例:

int numb=select(0, &readset, NULL, NULL, &vt);
if (numb==0 ||numb==SOCKET_ERROR)
{
	continue;
}

fd_set

fd_set结构由各种Windows套接字函数和服务提供者(如select函数)使用,用于将套接字放入“set”中,以实现各种目的,如使用select函数的readfds参数测试给定套接字的可读性,结构体原型为:

typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} FD_SET, *PFD_SET, *LPFD_SET;
参数:fd_count 集合中socket的个数
fd_array[FD_SETSIZE] 指套接字数组

主要利用4个宏定义操作fd_set 集合:
FD_ZERO(&set); 将set清零使集合中不含任何fd
FD_SET(fd, &set); 将fd加入set集合
FD_CLR(fd, &set); 将fd从set集合中清除
FD_ISSET(fd, &set); 在调用select()函数后,用FD_ISSET来检测fd是否在set集合中,当检测到fd在set中则返回真,否则,返回假(0)

timeval
时间间隔结构体,使用也比较简单,原型为:

typedef struct timeval {
long tv_sec;
long tv_usec;
} TIMEVAL, *PTIMEVAL, *LPTIMEVAL;
备注:tv_sec 时间间隔,单位秒
tv_usec 时间间隔 ,单位毫秒

示例:

timeval val={1,100};//表示1秒零100毫秒

关于TCP基础的知识,这里就不在过多的讲解,如果不太清楚,可以查看TCP通信相关知识点。

详细代码

由于客户端代码是通用的,这里就不再添加,如果需要可以直接调用TCP通信中的客户端代码即可。

服务端:

// TCP_Select.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <thread>

using namespace std;
#pragma comment(lib,"ws2_32.lib")

fd_set pSet;
//SOCKET sockArray[64];
//int ntotal = 0;

void  recvThread()
{
	char recvBuf[1024];
	while (1)
	{

		fd_set readset;
		FD_ZERO(&readset);
		readset=pSet;
		/*for (int j=0;j!=ntotal;j++)
		{
			FD_SET(sockArray[j], &readset);
		}*/
		timeval vt = { 1,0 };// 前面是秒,后面是毫秒

		int numb=select(0, &readset, NULL, NULL, &vt);
		if (numb==SOCKET_ERROR ||numb==0)
		{
			continue;
		}
		for (int i=0;i!= readset.fd_count;i++)
		{

			if (FD_ISSET(readset.fd_array[i],&readset))
			{
				ZeroMemory(recvBuf,1024);
				int len=recv(readset.fd_array[i],recvBuf,1024,0);

				if (len>0)
				{
					cout << readset.fd_array[i] <<recvBuf<< endl;
				}
				if (len==0)//说明连接中断
				{

					cout << readset.fd_array[i] <<"断开连接"<< endl;
					closesocket(readset.fd_array[i]);//关闭socket
					FD_CLR(readset.fd_array[i], &pSet);//清空集合体

														
				}

			}

		}

	}
}
int main()
{

	WSADATA  wsData;

	int nret = WSAStartup(MAKEWORD(2, 2), &wsData);

	if (nret!=0)
	{
		return nret;
	}

	sockaddr_in sa, recvSa;

	int nlen = sizeof(sa);

	sa.sin_addr.S_un.S_addr = INADDR_ANY;

	sa.sin_port = htons(9999);
	sa.sin_family = AF_INET;

	SOCKET  asock = socket(AF_INET, SOCK_STREAM, 0);

	if (asock==INVALID_SOCKET)
	{
		return WSAGetLastError();
	}

	if (bind(asock,(sockaddr*)&sa,nlen)!=0)
	{
		return WSAGetLastError();
		
	}

	if (listen(asock, SOMAXCONN)!=0)
	{
		return WSAGetLastError();
	}

	FD_ZERO(&pSet);//初始化

	thread t(recvThread);
	t.detach();

	while (1)
	{

		SOCKET  sock = accept(asock, (sockaddr*)&recvSa, &nlen);

		if (sock != INVALID_SOCKET)
		{
			FD_SET(sock, &pSet);//加人集合体
			//sockArray[ntotal] = sock;
			//ntotal++;
		}
	}
 
}

注意事项:

  1. 这个小的Demo用的是全局变量 pSet 集合,那recvThread() 接收线程为什么还要重新定义一个临时变量 fd_set readset ,并赋值呢?
    这是因为select() 返回后会把以前加入的但并无事件发生的fd清空,所以要每次循环都要重新对临时变量赋值,不能用直接使用全局变量。
  2. 这个Demo 是直接使用全局 fd_set集合体,也可以使用SOCKET 数组的方式,这里我做了屏蔽,后面的连接断开处理也没写完,有兴趣可以自己试试。
  3. 使用全局变量涉及到线程同步,示例中没有给出,如果想了解可以再在使用全局变量的时候添加:
#include <mutex>//头文件
mutex mtx;//全局变量
........
unique_lock <mutex> lock(mtx);
FD_SET(sock, &pSet);

如有错误,欢迎各位大神指正,共勉!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用c++编写的wsaeventselect i/o模型实现的联网多人在线五子棋程序的基本代码框架,仅供参考: ``` #include <iostream> #include <winsock2.h> #include <ws2tcpip.h> #pragma comment(lib, "ws2_32.lib") #define MAX_CLIENTS 10 #define PORT 8080 SOCKET clients[MAX_CLIENTS]; WSAEVENT events[MAX_CLIENTS]; int num_clients = 0; char buffer[1024]; void process_read_event(int idx); void process_write_event(int idx); void broadcast_game_state(); int main() { // 初始化Winsock库 WSADATA wsa_data; int result = WSAStartup(MAKEWORD(2, 2), &wsa_data); if (result != 0) { std::cerr << "WSAStartup failed: " << result << std::endl; return 1; } // 创建Socket对象 SOCKET server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (server_socket == INVALID_SOCKET) { std::cerr << "Failed to create socket: " << WSAGetLastError() << std::endl; WSACleanup(); return 1; } // 绑定IP地址和端口号 sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); result = bind(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)); if (result == SOCKET_ERROR) { std::cerr << "Failed to bind socket: " << WSAGetLastError() << std::endl; closesocket(server_socket); WSACleanup(); return 1; } // 开始监听连接请求 result = listen(server_socket, SOMAXCONN); if (result == SOCKET_ERROR) { std::cerr << "Failed to listen on socket: " << WSAGetLastError() << std::endl; closesocket(server_socket); WSACleanup(); return 1; } // 创建事件对象并关联到Socket WSAEVENT accept_event = WSACreateEvent(); WSAEventSelect(server_socket, accept_event, FD_ACCEPT); // 主循环 while (true) { // 等待事件的发生 DWORD num_events = WSAWaitForMultipleEvents(num_clients + 1, events, FALSE, WSA_INFINITE, FALSE); if (num_events == WSA_WAIT_FAILED) { std::cerr << "WSAWaitForMultipleEvents failed: " << WSAGetLastError() << std::endl; break; } // 处理事件 for (int i = 0; i < num_clients; i++) { if (events[i] != WSA_INVALID_EVENT && WSAWaitForMultipleEvents(1, &events[i], TRUE, 0, FALSE) == WSA_WAIT_EVENT_0) { WSANETWORKEVENTS net_events; WSAEnumNetworkEvents(clients[i], events[i], &net_events); if (net_events.lNetworkEvents & FD_READ) { process_read_event(i); } if (net_events.lNetworkEvents & FD_WRITE) { process_write_event(i); } if (net_events.lNetworkEvents & FD_CLOSE) { closesocket(clients[i]); WSACloseEvent(events[i]); for (int j = i; j < num_clients - 1; j++) { clients[j] = clients[j + 1]; events[j] = events[j + 1]; } num_clients--; } } } if (num_events == WSA_WAIT_EVENT_0 + num_clients) { // 处理新的连接请求 sockaddr_in client_addr; int addr_len = sizeof(client_addr); SOCKET client_socket = accept(server_socket, (sockaddr*)&client_addr, &addr_len); if (client_socket == INVALID_SOCKET) { std::cerr << "Failed to accept connection: " << WSAGetLastError() << std::endl; } else if (num_clients == MAX_CLIENTS) { std::cerr << "Max number of clients reached, connection refused" << std::endl; closesocket(client_socket); } else { clients[num_clients] = client_socket; events[num_clients] = WSACreateEvent(); WSAEventSelect(client_socket, events[num_clients], FD_READ | FD_WRITE | FD_CLOSE); num_clients++; } } // 广播游戏状态 broadcast_game_state(); } // 清理资源 closesocket(server_socket); for (int i = 0; i < num_clients; i++) { closesocket(clients[i]); WSACloseEvent(events[i]); } WSACleanup(); return 0; } void process_read_event(int idx) { // 读取客户端发送的数据 int num_bytes = recv(clients[idx], buffer, sizeof(buffer), 0); if (num_bytes == SOCKET_ERROR) { std::cerr << "Failed to read data from client: " << WSAGetLastError() << std::endl; return; } // 解析数据并更新游戏状态 // TODO: 解析数据并更新游戏状态 } void process_write_event(int idx) { // 发送当前游戏状态给客户端 // TODO: 发送当前游戏状态给客户端 } void broadcast_game_state() { // 广播当前游戏状态给所有客户端 // TODO: 广播当前游戏状态给所有客户端 } ``` 需要注意的是,该代码仅为基本框架,具体实现还需要根据具体需求进行完善和修改。同时,还需要注意多线程编程的相关问题,如线程同步和资源竞争等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值