Windows下I/O复用模型(select模型)测试

  1. 客户端代码:
#include<WinSock2.h>
#include<WS2tcpip.h>
#include<stdlib.h>
#include<stdio.h>
#include<iostream>
using namespace std;

#pragma comment(lib,"ws2_32.lib")
#define DEF_PORT	27015
#define DEF_SIZE	512

int main()
{
	WSADATA wsaData;
	SOCKET ConnectSocket = INVALID_SOCKET;
	int iResult;
	//初始化Winsock
	iResult = WSAStartup(MAKEWORD(2,2),&wsaData);
	if(iResult != 0){
		cout<<"WSAStartup failed with error: "<<iResult<<endl;
        return 1;
	}

	//创建用于连接的socket
	
	ConnectSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);

	if(ConnectSocket == INVALID_SOCKET){
		cout<<"socket created with error: "<<GetLastError()<<endl;
		WSACleanup();
		return 1;
	}
	
	//连接
	sockaddr_in addserver;
	addserver.sin_family = AF_INET;
	addserver.sin_port = htons(DEF_PORT);
	addserver.sin_addr.S_un.S_addr = inet_addr("10.50.92.211");

	//连接
	iResult  = connect(ConnectSocket,(const sockaddr*)&addserver,sizeof(addserver));
	if(iResult != 0){
		printf("connect failed with error :%d\n",GetLastError());
		closesocket(ConnectSocket);
		WSACleanup();
		return 1;
	}
	char sendbuf[DEF_SIZE];
	printf("conntect successful...\n");
	printf("Please input sendbuf:");
	cin>>sendbuf;
	//发送数据
	iResult = send(ConnectSocket,sendbuf,sizeof(sendbuf),0);
	if(iResult > 0){
		printf("host send: %s\n",sendbuf);
	}
	closesocket(ConnectSocket);
	WSACleanup();
	system("pause");
	return 0;
}
  1. 使用select模型的服务器代码:
    首先说一下select模型是通过fd_set结构和几个系统给定的宏来对多个套接字进行管理的
结构体:
typedef struct fd_set{
	u_int fd_count;					//集合中包含套接字的数量
	SOCKET fd_array[FD_SETSIZE];	//套接字数组集合,FD_SETSIZE默认是64
}fd_set;

宏:
FD_CLR(s,*set):从集合set中删除套接字s
FD_ISSET(s,*set):判断套接字s是否为集合set中的一个,是返回非0,否则返回0
FD_SET(s,*set):将套接字s添加到集合set中
FD_ZERO(*set):将set集合初始化为空集NULL

select()函数
int WSAAPI select(
  int           	nfds,	//是为了和Berkeley套接字兼容保留,执行函数会忽略。
  							//注意:linux一般是最大描述符+1
  fd_set        *readfds,	//指向一个套接字集合,用于检测其可读性
  fd_set        *writefds,	//指向一个套接字集合,用于检测其可写性
  fd_set        *exceptfds,	//指向一个套接字集合,用于检测错误
  const timeval *timeout	//用于指定等待的时间,NULL代表阻塞态,一直等待
);

服务器的代码:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<WinSock2.h>
#include<WS2tcpip.h>
using namespace std;

#pragma comment(lib,"ws2_32.lib")
#define DEF_PORT	27015
#define DEF_SIZE	512

int main()
{
	
	WSADATA wsaData;
	int iResult;
	
	SOCKET ListenSocket = INVALID_SOCKET;
	SOCKET AcceptSocket = INVALID_SOCKET;
	char recvbuf[DEF_SIZE] = {0};

	//1.初始化
	iResult = WSAStartup(MAKEWORD(2,2),&wsaData);
	if(iResult != 0)
	{
		printf("WSAStartup failed with error :%d\n",iResult);
		return 1;
	}

	//2.创建用于监听的套接字
	ListenSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
	if(ListenSocket == INVALID_SOCKET){
		printf("create ListenSocket failed with error:%d\n",GetLastError());
		WSACleanup();
		return 1;
	}
	//3.为监听套接字绑定地址和端口号
	sockaddr_in addServer;

	addServer.sin_family = AF_INET;
	addServer.sin_port = htons(DEF_PORT);
	addServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

	iResult = bind(ListenSocket,(const sockaddr*)&addServer,sizeof(addServer));
	if(iResult == SOCKET_ERROR){
		printf("bind failed with error:%d",GetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}

	//4.监听客户端的连接
	iResult = listen(ListenSocket,SOMAXCONN);
	if(iResult == SOCKET_ERROR){
		printf("Listen failed with error:%d\n",GetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	printf("TCP Server is starting...\n");

	//5.Select模型
	/*	使用以下结构来管理多个套接字
		typedef struct fd_set{
			u_int fd_count;					//集合中包含套接字的数量
			SOCKET fd_array[FD_SETSIZE];	//套接字数组集合
		}fd_set;
	*/
	sockaddr_in addrClient;
	int addrClientSize = sizeof(addrClient);
	fd_set fdRead,fdSocket;
	FD_ZERO(&fdSocket);				//初始化fdSocket集合
	FD_SET(ListenSocket,&fdSocket);	//将服务器的用于监听的ListenSocket加入到集合中

	while (1)
	{
		//通过select(等待数据到达事件)
		//如果有事件到达,移除fdRead集合中没有解决的IO操作的套接字句柄,然后返回
		fdRead = fdSocket;
		iResult = select(0,&fdRead,NULL,NULL,NULL);
		//有网络时间发生
		//确定有哪些套接字有未解决的I/O,并进一步处理这些I/O
		if(iResult > 0){
			for(int i = 0; i < (int)fdSocket.fd_count;++i)
			{
				if(FD_ISSET(fdSocket.fd_array[i],&fdRead))
				{	
					if(fdSocket.fd_array[i] == ListenSocket)
					{
						if(fdSocket.fd_count < FD_SETSIZE)
						{
							//同时复用的套接字数量不能大于64
							//有新的连接请求
							AcceptSocket  = accept(ListenSocket,(sockaddr*)&addrClient,&addrClientSize);
							if(AcceptSocket == INVALID_SOCKET)
							{
								printf("accpet failed!\n");
								closesocket(ListenSocket);
								WSACleanup();
								return 1;
							}
							//增加新的连接套接字进行复用等待
							FD_SET(AcceptSocket,&fdSocket);
							printf("收到新的连接:%s\n",inet_ntoa(addrClient.sin_addr));
					
						}
						else 
						{
							printf("连接个数受限\n");
							continue;
						}
					}

					else
					{
						//有数据到达
						memset(recvbuf,0,sizeof(recvbuf));
						iResult = recv(fdSocket.fd_array[i],recvbuf,sizeof(recvbuf),0);
						if(iResult > 0){
							//case1:数据到达
							printf("数据内容: %s\n",recvbuf);
							continue;
						}
						else if(iResult == 0){
							//case2:连接关闭
							printf("连接正常关闭\n");
							closesocket(fdSocket.fd_array[i]);
							FD_CLR(fdSocket.fd_array[i],&fdSocket);
						}
						else{
							//case3:接收失败
							printf("recv failed with error: %d\n",GetLastError());
							closesocket(fdSocket.fd_array[i]);
							FD_CLR(fdSocket.fd_array[i],&fdSocket);
							WSACleanup();
							return 1;
						}
					}
				}
			}
		}
		else {
			printf("select failed with error\n");
			break;
		}
	}
	closesocket(ListenSocket);
	WSACleanup();
	system("pause");
	return 0;
}

  1. 测试结果:
    在这里插入图片描述

  2. 模型评价:
    实现了单线程处理多个socket的I/O事件,避免了阻塞模式下线程膨胀的问题
    但是可以管理的套接字数量有限,默认是64个,最大也是1024个。不过足以解决小型局域网中的I/O问题。另外用集合的方式管理套接字比较麻烦,每次在使用套接字进行网络操作前都需要调用select函数来判断套接字的状态,给cpu带来了额外的系统调用开销。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值