windows网络编程------select模型

select模型与一般模型的区别是selete模型使用fd_set数据类型和select函数在内核里维护一张存有连接上的socket表,使用select轮循处理这些socket,实现同时与多个socket连接。

基本工作原理:创建socket,绑定,监听,开个死循环,当发现有socket请求连接后把该socket写入全局变量的fd_set数据类型中,给另一个线程处理,总监听数-1,处理线程会接受fd_set全局变量,(开死循环)使用select函数堵塞监听表中的socket(轮循该表(fd-set)),当发现有表中的某个或多个socket有信息传过来时,再轮循一次该表,逐个处理传过来的信息。如果某个socket断开连接(recv发送消息过来,返回值是0),则把该socket从表中移除。总监听数量+1.

使用的函数:

int select(
    int nfds,//忽略,只是为了保持与早期的Berkeley套接字应用程序的兼容

    fd_set FAR* readfds,//可读性检查(有数据可读入,连接关闭,重设,终止),为空则不检查可读性
    fd_set FAR* writefds,//可写性检查(有数据可发出),为空则不检查可写性
    fd+set FAR* exceptfds,//带外数据检查(带外数据),为空则不检查
    const struct timeval FAR* timeout//超时
    );

struct timeval
{
    long tv_sec;        //秒数
    long tv_usec;       //微秒数
};
void FD_SET(int fd, fd_set *set);   //在set中设置文件描述符fd
void FD_CLR(int fd, fd_set *set);   //清除set中的fd位
int  FD_ISSET(int fd, fd_set *set); //判断set中是否设置了文件描述符fd
void FD_ZERO(fd_set *set);          //清空set中的所有位(在使用文件描述符集前,应该先清空一下)
    //(注意FD_CLR和FD_ZERO的区别,一个是清除某一位,一个是清除所有位)

实例:

服务器:

#include <stdio.h>
#include <WinSock2.h>//必须放在windows.h前面
#include <Windows.h>
#include <stdlib.h>
#pragma comment(lib, "ws2_32.lib")


fd_set g_fdClientSock; //可以理解为一张存了需要等待的socket的数组。
int clientNum = 0;

DWORD WINAPI ListenThreadProc(LPARAM lparam)
{

	fd_set fdRead;
	FD_ZERO(&fdRead);
	int nRet = 0;
	char *recvBuffer = (char*)malloc(1024);
	if (!recvBuffer)
	{
		return -1;
	}
	memset(recvBuffer, 0, 1024);
	
	while (1)
	{
		fdRead = g_fdClientSock;
		timeval vt;

		vt.tv_sec = 0; //秒
		vt.tv_usec = 100;//毫秒
		nRet = select(0, &fdRead, 0, 0, &vt);//会阻塞检查集合中所有socket是否有信号
		if (nRet != SOCKET_ERROR)
		{
			for (int i = 0; i<g_fdClientSock.fd_count; i++)
			{
				if (FD_ISSET(g_fdClientSock.fd_array[i], &fdRead))
				{
					memset(recvBuffer, 0, 1024);
					nRet = recv(g_fdClientSock.fd_array[i], recvBuffer, 1024, 0);//返回字节数
					if (nRet >0)
					{
						//todo:
						printf("接收到数据:%s", recvBuffer);
						send(g_fdClientSock.fd_array[i], recvBuffer, strlen(recvBuffer), 0);
					
					}
					else//如果接收失败,则从集合中清除响应socket,并把客户端数量减1??为什么要这个操作,不删除,FD表的负担太大了么?
					{	//因为当recv返回值是0时,表示断开连接了,所以删掉,不让他站位置
						closesocket(g_fdClientSock.fd_array[i]);
						clientNum--;
						FD_CLR(g_fdClientSock.fd_array[i], &g_fdClientSock);//在FD_SET表格中删除该socket
					}
				}
			}
		}
	}

	if (recvBuffer)
	{
		free(recvBuffer);
		recvBuffer = nullptr;
	}
	return 0;
}

int main()
{
	int port = 5099;
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		printf("Failed to load Winsock");
		return 0;
	}

	//创建用于监听的套接字  AF_INET:IPV4版本
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
	if (sockSrv == INVALID_SOCKET)
	{
		return 0;
	}

	//地址绑定-告诉操作系统是在哪一个地址及端口
	SOCKADDR_IN addrSrv;
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(port); //1024以上的端口号,htons本地转换为网络数据
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//电脑上所有的网络ip

	int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
	if (retVal == SOCKET_ERROR)
	{
		printf("绑定bind失败:%d\n", WSAGetLastError());
		return 0;
	}

	if (listen(sockSrv, 5/*SOMAXCONN*/) == SOCKET_ERROR)
	{
		printf("监听listen失败:%d", WSAGetLastError());
		return 0;
	}

	SOCKADDR_IN addrClient;//用于获取连接上来的人的地址信息
	int len = sizeof(SOCKADDR);
	//创建线程
	CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ListenThreadProc, NULL, NULL, NULL);
	//绑定好之后就在这里监听,每次监听到sockket就把他写入内核的FD_SET(g_fdClientSock是全局,另一个线程能看到),之后的就交给另一个线程,总共就需要2个线程
	while (clientNum < FD_SETSIZE)
	{
		//等待客户请求到来	
		SOCKET clientSock = accept(sockSrv, (SOCKADDR *)&addrClient, &len);
		if (clientSock == SOCKET_ERROR)
		{
			printf("接收Accept失败:%d", WSAGetLastError());
			break;
		}
		else
		{
			printf("接收Accept到客户端IP:[%s]\n", inet_ntoa(addrClient.sin_addr));
		}
		FD_SET(clientSock, &g_fdClientSock);//添加到集合中去
		clientNum++;//每次接收到一个人就+1,目前最多接收64个客户,可以改到最大值的1024
	}

	closesocket(sockSrv);
	WSACleanup();

	system("pause");
	return 0;
}

客户端:(客户端和一般模型的客户端一样)


#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")


//不要安全检测
#define _CRT_SECURE_NO_WARNINGS

int main()
{
	//加载套接字
	WSADATA wsaData;
	char buff[1024];
	memset(buff, 0, sizeof(buff));

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		printf("Failed to load Winsock");
		return 0;
	}

	SOCKADDR_IN addrSrv;
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(5099);//http默认端口
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	//addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//htonl(INADDR_ANY);         //服务器地址为INADDR_ANY,即为(0.0.0.0)上监听(任意ip),监听端口为9990

	//创建套接字
	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
	if (SOCKET_ERROR == sockClient) {
		printf("Socket() error:%d", WSAGetLastError());
		return 0;
	}

	//向服务器发出连接请求
	if (connect(sockClient, (struct  sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET) {
		printf("Connect failed:%d", WSAGetLastError());
		return 0;
	}

	int iRecvLen = 0;

	//发送数据
	char* buffSend = "hello, this is a Client....";
	iRecvLen = send(sockClient, buffSend, strlen(buffSend), 0);


	//接收数据
	iRecvLen = recv(sockClient, buff, sizeof(buff), 0);
	printf("%s\n", buff);

	//关闭套接字
	closesocket(sockClient);
	WSACleanup();
	system("pause");
	return 0;
}

参考文章及书籍

windows下的IO模型之选择(select)模型

select模型的原理、优点、缺点

TCP之 select模型

select与阻塞和非阻塞

《windows网络编程(第二版)》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值