winsock-select模型总结

1 select模型介绍[1]

  • select模型的名字来源于select函数,当线程需要调用一个I/O函数的时候,比如recv,此时如果套接字处于阻塞模式,则直接调用recv有可能会使线程阻塞;如果套接字是非阻塞模式,则直接调用recv,又有可能返回WSAEWOULDBOLCK,也就是说,无论套接字是阻塞模式还是非阻塞模式,直接调用recv都有可能出现问题;
  • 有了select模型后,当线程需要调用recv时,线程不直接调用它,相反,线程先调用selec函数,由于select函数本身是阻塞的,所以线程将会被挂起.当select函数检测到系统的接受缓冲区有可用数据的时候,也就是能够确保recv成功返回的时候,select将返回,此时线程将被唤醒,进而可以调用recv,此时调用recv就会成功,而不会阻塞和返回WSAEWOULDBOLCK,
  • 问题:select函数本身是阻塞的,这和使用阻塞模式的套接字直接调用recv有什么区别呢?
    区别在于,如果对阻塞模式的套接字直接调用I/O函数,线程每次只能等待一个套接字的一种I/O操作,而如果线程调用的是select的话,因为selec可以同时检测多个套接字的多种操作,所以只要任一个套接字或者多个套接字上的任意一种或者多种操作就绪,select都会返回,也就是说线程具备了 等待多个套接字上多种操作的能力;由于一个套接字对应一个客户端,也就是线程可以同时服务多个客户连接;

2 server端(以下代码直接粘贴到vs中就可以运行)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <process.h>
#include <winsock.h>
#include <iostream>

using namespace std;

#pragma comment(lib, "ws2_32.lib")

const char SERVER[] = "127.0.0.1";
const int PORT = 5555;
const int MSGSIZE = 1024;

int cnt = 0;
SOCKET sockets[FD_SETSIZE];

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
	fd_set fdread;
	int ret;
	struct timeval tv = { 1, 0 };
	char szMessage[MSGSIZE];
	while (TRUE)
	{
		FD_ZERO(&fdread);
		for (int i = 0; i < cnt; i++)
		{
			FD_SET(sockets[i], &fdread);//将套接口加入集合中
		}
		//检查可读性的套接口
		ret = select(0, &fdread, NULL, NULL, &tv);

		if (ret == 0)
		{
			cout << "ret == 0?" << endl;
			continue;
		}

		for (int i = 0; i < cnt; i++)
		{
			if (FD_ISSET(sockets[i], &fdread)) //如果可读, 奶蛋的,写错了。
			{
				ret = recv(sockets[i], szMessage, MSGSIZE, NULL);
				if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
				{
					printf("Client socket %d closed.\n", sockets[i]);
					closesocket(sockets[i]);
					if (i < cnt - 1)
					{
						sockets[i--] = sockets[--cnt];
					}
				}
				else
				{
					szMessage[ret] = '\0';
					cout << "The message that client send is :" << szMessage << endl;
					char szBuffer[1001];
					sprintf(szBuffer, "I have bean received your messages, %s", szMessage);
					send(sockets[i], szBuffer, strlen(szBuffer), NULL);
				}
			}
		}

	}
	return 0;
}


int main()
{
	WSADATA wsaData; //WSADATA framework 用于存储 Windows 套接字调用返回的初始化信息
	SOCKET sListen, sClient;
	SOCKADDR_IN local, client;
	char szMessage[MSGSIZE];
	int ret;
	DWORD dwThreadId;
	int iaddrSize = sizeof(SOCKADDR_IN);

	WSAStartup(0x0202, &wsaData); //初始化ws2_32.lib

	//create listening socket
	sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//bind
	local.sin_family = AF_INET;
	local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	local.sin_port = htons(PORT);
	if (bind(sListen, reinterpret_cast<sockaddr*>(&local), sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
	{
		cout << "bind failed!" << endl;
	}
	//listen
	if (listen(sListen, 3) == SOCKET_ERROR)
	{
		cout << "listen failed!" << endl;
	}
	//create WorkerThread
	CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
	cout << "PID = " << dwThreadId << endl;

	while (TRUE)
	{
		//accept a connection
		//这里的accept也是个阻塞函数,如果有必要可以将其修改为非阻塞,这样可以不影响自己的主线程的程序;
		/*
		修改为非阻塞的代码
		u_long nNoBlock = 1;
		ioctlsocket(sListen, FIONBIO, &nNoBlock);
	*/
		sClient = accept(sListen, reinterpret_cast<sockaddr*>(&client), &iaddrSize);
		if (sClient == INVALID_SOCKET)
		{
			cout << "accept error!" << endl;
			break;
		}
		
		else
		{
			printf("Accept client: %s: %d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
			//加入到socket的数组中
			sockets[cnt++] = sClient;
		}

	}
	closesocket(sListen);
	WSACleanup();
	return 0;
}

3 client端

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <winsock2.h>
#include <iostream>

using namespace std;

#pragma comment(lib, "ws2_32.lib")


const char SERVER[] = "127.0.0.1";
const int PORT = 5555;
const int MSGSIZE = 1024;


int main()
{
	WSADATA wsaData;
	SOCKET sClient;
	SOCKADDR_IN server;
	char szMessage[MSGSIZE];
	int ret;

	WSAStartup(0x0202, &wsaData);

	sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	memset(&server, 0, sizeof(SOCKADDR_IN));
	server.sin_family = AF_INET;
	server.sin_addr.S_un.S_addr = inet_addr(SERVER);
	server.sin_port = htons(PORT);

	cout << "Waiting for connect!" << endl;
	if (connect(sClient, (struct sockaddr*)(&server), sizeof(SOCKADDR_IN)) == 0)
	{
		cout << "connect!!~" << endl;
		while (TRUE)
		{
			cout << "send:" << endl;
			cin >> szMessage;

			send(sClient, szMessage, strlen(szMessage), 0);

			ret = recv(sClient, szMessage, MSGSIZE, 0);
			szMessage[ret] = '\0';

			cout << "receive: " << szMessage << endl;
		}
	}

	closesocket(sClient);
	WSACleanup();
	return 0;
}

4 运行结果

在这里插入图片描述

参考文献

[1]唐文超编著,Visual studio C++网络编程2013[M].清华大学出版社
[2]https://blog.csdn.net/aledavvv/article/details/23791179

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用winsock库的select函数的示例代码: ```c++ #include <winsock2.h> #include <stdio.h> #pragma comment(lib,"ws2_32.lib") //链接ws2_32.lib库文件 int main() { WSADATA wsaData; int iRet = 0; iRet = WSAStartup(MAKEWORD(2,2), &wsaData); //初始化winsock库 if(iRet != NO_ERROR) { printf("WSAStartup() failed with error: %d\n", iRet); return 1; } SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建TCP socket if(sockServer == INVALID_SOCKET) { printf("socket() failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } sockaddr_in addrSrv; addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(8888); addrSrv.sin_addr.s_addr = htonl(INADDR_ANY); iRet = bind(sockServer, (const sockaddr *)&addrSrv, sizeof(addrSrv)); //绑定socket到指定IP和端口号 if(iRet == SOCKET_ERROR) { printf("bind() failed with error: %ld\n", WSAGetLastError()); closesocket(sockServer); WSACleanup(); return 1; } iRet = listen(sockServer, 5); //开始监听 if(iRet == SOCKET_ERROR) { printf("listen() failed with error: %ld\n", WSAGetLastError()); closesocket(sockServer); WSACleanup(); return 1; } fd_set fdRead; //定义读文件描述符集合 FD_ZERO(&fdRead); //清空读文件描述符集合 FD_SET(sockServer, &fdRead); //将监听socket加入读文件描述符集合 while(true) { fd_set fdReadBackup = fdRead; //备份读文件描述符集合,因为select会改变集合内容 iRet = select(0, &fdReadBackup, NULL, NULL, NULL); //等待读文件描述符集合有数据可读 if(iRet == SOCKET_ERROR) { printf("select() failed with error: %ld\n", WSAGetLastError()); break; } else if(iRet > 0) { if(FD_ISSET(sockServer, &fdReadBackup)) //监听socket有数据可读,表示有新连接请求 { sockaddr_in addrClient; int addrClientLen = sizeof(addrClient); SOCKET sockConn = accept(sockServer, (sockaddr *)&addrClient, &addrClientLen); //接受连接请求 if(sockConn == INVALID_SOCKET) { printf("accept() failed with error: %ld\n", WSAGetLastError()); break; } printf("New client connected: %s:%d\n", inet_ntoa(addrClient.sin_addr), ntohs(addrClient.sin_port)); FD_SET(sockConn, &fdRead); //将连接socket加入读文件描述符集合 } else //连接socket有数据可读,表示客户端发送了数据 { for(int i = 0; i < fdReadBackup.fd_count; i++) //遍历读文件描述符集合中的所有连接socket { SOCKET sockConn = fdReadBackup.fd_array[i]; if(sockConn != sockServer) //排除监听socket { char szBuf[1024] = {0}; iRet = recv(sockConn, szBuf, sizeof(szBuf), 0); //接收数据 if(iRet == SOCKET_ERROR) { printf("recv() failed with error: %ld\n", WSAGetLastError()); break; } else if(iRet == 0) //对方关闭连接 { printf("Client disconnected.\n"); closesocket(sockConn); FD_CLR(sockConn, &fdRead); //从读文件描述符集合中移除连接socket } else //正常接收到数据 { printf("Received data from client: %s\n", szBuf); send(sockConn, szBuf, iRet, 0); //回复相同的数据 } } } } } } closesocket(sockServer); WSACleanup(); return 0; } ``` 该代码实现了一个简单的TCP服务器,通过select函数实现了多路复用,可以同时处理多个客户端连接。在主循环中,使用select函数等待读文件描述符集合中的socket有数据可读,当有新连接请求时,将连接socket加入读文件描述符集合,当连接socket有数据可读时,接收数据并回复相同的数据。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值