C socket编程之select模型

陌路散爱 提交于 2019-12-02 06:15:25

        在做Socket编程时,当要处理一个server对应多个client,这种可以每个客户端用一个线程来处理,但是客户端太多,程序的性能会降低。Windows提供了select模型,很好的处理了一对多的模型。select的申明如下:

Int WSAAPI select(
_In_ int nfds,   //0,无意义
_Inout_opt_ fd_set FAR * readfds,  //检查可读性集合
_Inout_opt_ fd_set FAR * writefds, //检查可写性集合
_Inout_opt_ fd_set FAR * exceptfds,//进行异常检测的Socket
_In_opt_ const struct timeval FAR * timeout //非阻塞模式中设置最大等待时间;

         从msdn可查看参数的详细解释:

         Any two of the parameters, readfdswritefds, or exceptfds, can be given as null. At least one must be non-null, and any non-nulldescriptor set must contain at least one handle to a socket.

        In summary, a socket will be identified in a particular set when select returns if:

       其中任何两个参数readfds、writefds或exceptfds都可以被指定为null。至少一个必须是非空的,任何非空描述符集必须包含一个套接字的至少一个句柄。

        总之,当select返回以下情况时,套接字将在特定集合中标识:

readfds:

  • If listen has been called and a connection is pending, accept will succeed.

       如果监听已被调用,且连接正在挂起此时调用accept函数会成功

  • Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).

       有数据可读,如果启用SO_OOBINLINE,则包含OOB数据

  • Connection has been closed/reset/terminated.

       连接已经关闭,重置或终止

writefds:

  • If processing a connect call (nonblocking), connection has succeeded.
  • Data can be sent.

exceptfds:

  • If processing a connect call (nonblocking), connection attempt failed.
  • OOB data is available for reading (only if SO_OOBINLINE is disabled).

         fd_set的定义如下

typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

#define FD_SETSIZE      64

            可知select模型最多可以处理64个套接字

       Return Value

           The select function returns the total number of socket handles that are ready and contained in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, WSAGetLastError can be used to retrieve a specific error code.

          调用成功时select函数返回准备好并包含在fd_set结构中的套接字句柄的总数,如果时间限制过期,则返回零,如果发生错误,则返回SOCKET_ERROR。如果返回值是SOCKET_ERROR,则可以使用WSAGetLastError来检索特定的错误代码。

         select模型紧密结合的四个宏来简化对socket的操作:

                   FD_CLR( s,*set) 从队列set删除句柄s。

                   FD_ISSET( s, *set) 检查句柄s是否存在与队列set中。

                   FD_SET( s,*set )把句柄s添加到队列set中。

                   FD_ZERO( *set ) 把set队列初始化成空队列。

 

      select判断sockets是否刻度的步骤:

  1. 将该套接字加入到readfds集合;
  2. 一readfds作为第二个参数调用select函数;
  3. 当select函数返回时,应用程序判断该套接字是否任然存在于readfds集合;
  4. 如果该套接字存在于readfds集合,则表明该套接字刻度,此时可以调用recv函数接受数据,否则,

 

          下面的代码是一个server对应多个client, 仅做测试demo, 如果是实际项目,需实际处理。

          服务端代码

#include<winsock2.h>
#include<iostream>

using  namespace  std;

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


int  main()
{
	//初始化winsock的环境
	WSADATA  wd;
	if (WSAStartup(MAKEWORD(2, 2, ), &wd) == SOCKET_ERROR)
	{
		cout << "WSAStartup  error:" << GetLastError() << endl;
		return 0;
	}

	//1.创建监听套接字
	SOCKET  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sListen == INVALID_SOCKET)
	{
		cout << "socket  error:" << GetLastError() << endl;
		return 0;
	}

	//2.绑定到ip与端口
	sockaddr_in  addr;
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addr.sin_port = htons(8000);
	addr.sin_family = AF_INET;
	int len = sizeof(sockaddr_in);
	if (bind(sListen, (SOCKADDR*)&addr, len) == SOCKET_ERROR)
	{
		cout << "bind  error:" << GetLastError() << endl;
		return 0;
	}

	//3.监听套接字
	if (listen(sListen, 5) == SOCKET_ERROR)
	{
		cout << "listen  error:" << GetLastError() << endl;
		return 0;
	}

	//4. select开始了
	fd_set  readSet;//定义一个读(接受消息)的集合
	FD_ZERO(&readSet);//初始化集合
	FD_SET(sListen, &readSet);

	//不停的select才可以读取套接字的状态改变
	while (true)
	{
		fd_set tmpSet;//定义一个临时的集合
		FD_ZERO(&tmpSet);//初始化集合
		tmpSet = readSet;// 每次循环都是所有的套接字

		//利用select选择出集合中可以读写的多个套接字,有点像筛选
		int ret = select(0, &tmpSet, NULL, NULL, NULL);//最后一个参数为NULL,一直等待,直到有数据过来
		if (ret == SOCKET_ERROR)
		{
			continue;
		}

		//成功筛选出来的tmpSet可以发送或者接收的socket
		for (int i = 0; i < tmpSet.fd_count; ++i)
		{
			//获取到套接字
			SOCKET  s = tmpSet.fd_array[i];

			//接收到客户端的链接
			if (s == sListen)
			{
				SOCKET  c = accept(s, NULL, NULL);
				//fd_set集合最大值为64
				if (readSet.fd_count < FD_SETSIZE)
				{
					//往集合中添加客户端套接字
					FD_SET(c, &readSet);
					cout << "欢迎" << c << "进入聊天室!" << endl;

					//给客户端发送欢迎
					char buf[100] = { 0 };
					sprintf(buf, "欢迎%d进入聊天室!", c);
					send(c, buf, 100, 0);
				}
				else
				{
					cout << "达到客户端容量上线!" << endl;
				}

			}
			else//一定是客户端
			{
				//接收客户端的数据
				char buf[100] = { 0 };
				ret = recv(s, buf, 100, 0);
				if (ret == SOCKET_ERROR || ret == 0)
				{
					closesocket(s);
					FD_CLR(s, &readSet);
					cout << s << "离开聊天室!" << endl;
				}
				else
				{
					cout << s << "说:" << buf << endl;
				}
			}
		}
	}

	//关闭监听套接字
	closesocket(sListen);

	//清理winsock环境
	WSACleanup();

	return 0;
}

            编译出现C4996错误,可以看这里的解决办法

            客户端代码

#include<winsock2.h>//winsock2的头文件
#include<iostream>
using  namespace std;

//勿忘,链接dll的lib
#pragma comment(lib, "ws2_32.lib")

int  main()
{
	//加载winsock2的环境
	WSADATA  wd;
	if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
	{
		cout << "WSAStartup  error:" << GetLastError() << endl;
		return 0;
	}

	//1.创建流式套接字
	SOCKET  s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s == INVALID_SOCKET)
	{
		cout << "socket  error:" << GetLastError() << endl;
		return 0;
	}

	//2.连接服务器
	sockaddr_in   addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8000);
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	int len = sizeof(sockaddr_in);
	if (connect(s, (SOCKADDR*)&addr, len) == SOCKET_ERROR)
	{
		cout << "connect  error:" << GetLastError() << endl;
		return 0;
	}

	//3接收服务端的消息
	char buf[100] = { 0 };
	recv(s, buf, 100, 0);
	cout << buf << endl;

	//3随时给服务端发消息
	int  ret = 0;
	do
	{
		char buf[100] = { 0 };
		cout << "请输入聊天内容:";
		cin >> buf;
		ret = send(s, buf, 100, 0);
	} while (ret != SOCKET_ERROR && ret != 0);

	//4.关闭监听套接字
	closesocket(s);

	//清理winsock2的环境
	WSACleanup();

	return 0;
}

                 例如,测试结果如下,在服务端窗口可以看到客户端的连接情况。

        select模型只处理了64个socket, 如果多于64个如何处理呢,Hp-SocketIOCP这两种可以试试, 暂时没有项目用到,以后再研究研究。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!