zoukankan      html  css  js  c++  java
  • C++Socket编程—socket网络模型之select模型

    一、select模型是什么

    select模型是Windows sockets中最常见的IO模型。它利用select函数实现IO 管理。通过对select函数的调用,应用程序可以判断套接字是否存在数据、能否向该套接字写入据。

    二、为什么要使用select模型?

    解决基本C/S模型中,accept()、recv()、send()阻塞的问题,以及C/S模型需要创建大量线程,客户端过多就会增加服务器运行压力

    三、select模型与C/S模型的不同点    

        • C/S模型中accept()会阻塞一直傻等socket来链接
        • select模型只解决accept()傻等的问题,不解决recv(),send()执行阻塞问题
        其实select模型解决了实现多个客户端链接,与多个客户端分别通信
        两个模型都存在recv(),send()执行阻塞问题
        • 由于服务器端,客户端不需要(客户端只有一个socket,可以通过加线
        程解决同时recv和send)


    select函数决定一个或者多个套接字(socket)的状态,如果需要的话,等待执行异步I/O。

    四、select模型的API函数

    int select(
                  __in     int    nfds,
                  __inout    fd_set *readfds,
                  __inout  fd_set *writefds,
                  __inout  fd_set *exceptfds,
                  __int       const struct timeval *timeout
                  );


    参数:
     nfds:忽略。
     readnfds: 指向检查可读性的套接字集合的可选的指针。
     writefds: 指向检查可写性的套接字集合的可选的指针。
     exceptfds: 指向检查错误的套接字集合的可选的指针。
     timeout: select函数需要等待的最长时间,需要以TIMEVAL结构体格式提供此参数,对于阻塞操作,此参数为null。
     
    返回值:
           select函数返回那些准备好并且包含在fd_set结构体的套接字的总数,如果超时,则返回0;如果错误发生,返回SOCKET_ERROR。如果返回值为SOCKET_ERROR,可以通过WSAGetLastError函数检索指定的错误码。

    五、代码示例

    select模型下的客户端服务器简单收发数据通信:

    客户端:

    int main()
    {
    // 	1) 创建socket
    	SOCKET sockServer = socket(
    		AF_INET,
    		SOCK_STREAM, //流式 
    		IPPROTO_TCP);//tcp协议
    
    
    	// 	2) 绑定端口
    	sockaddr_in siServer;
    	siServer.sin_family = AF_INET;
    	siServer.sin_port = htons(9527);
    	siServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    	int nRet = bind(sockServer, (sockaddr*)&siServer, sizeof(siServer));
    	if (nRet == SOCKET_ERROR)
    	{
    		printf("绑定失败 
    ");
    		return 0;
    	}
    
    	// 	3) 监听
    	nRet = listen(sockServer, SOMAXCONN);
    	if (nRet == SOCKET_ERROR)
    	{
    		printf("监听失败 
    ");
    		return 0;
    	}
    	
    	//创建线程, 检测socket是否有数据可读, 并且处理
    	vector<SOCKET> vctClients;
    	HANDLE hTread = CreateThread(NULL, 0, HandleClientsThread, (LPVOID)&vctClients, 0, NULL);
    	CloseHandle(hTread);
    		
    	while (true)
    	{
    		// 	4) 接受连接
    		sockaddr_in siClient;
    		int nSize = sizeof(siClient);
    		SOCKET sockClient = accept(sockServer, (sockaddr*)&siClient, &nSize);
    		if (sockClient == SOCKET_ERROR)
    		{
    			printf("接受连接失败 
    ");
    			return 0;
    		}
    		printf("IP:%s port:%d 连接到服务器. 
    ",
    			inet_ntoa(siClient.sin_addr),
    			ntohs(siClient.sin_port));
    
    		g_lock.Lock();  //加上线程锁
    		vctClients.push_back(sockClient);
    		g_lock.UnLock();
    	}
        return 0;
    }

     服务器:

    bool HandleData(SOCKET sockClient)
    {
    	// 	5) 收发数据
    	char aryBuff[MAXWORD] = { 0 };
    	int nRet = recv(sockClient, aryBuff, sizeof(aryBuff), 0);
    	if (nRet == 0 || nRet == SOCKET_ERROR)
    	{
    		printf("接受数据失败 
    ");
    		return false;
    	}
    	printf("收到数据: %s 
    ", aryBuff);
    
    	char szBuff[] = { "recv OK " };
    	nRet = send(sockClient, szBuff, sizeof(szBuff), 0);
    	if (nRet == SOCKET_ERROR)
    	{
    		printf("数据发送失败 
    ");
    		return false;
    	}
    
    	return true;
    }
    
    //线程, 用来处理客户端, 和客户端进行数据的收发
    DWORD WINAPI HandleClientsThread(LPVOID pParam)
    {
    	vector<SOCKET>& vctClients = *(vector<SOCKET>*)pParam;
    
    	while (TRUE)
    	{
    		fd_set fdRead;
    		FD_ZERO(&fdRead); //初始化
    
    		//将所有的socket放入数组
    		g_lock.Lock();
    		for (auto sock:vctClients)
    		{
    			FD_SET(sock, &fdRead);
    		}
    		g_lock.UnLock();
    
    		//检测指定的socket
    		timeval tv = {1, };
    		int nRet = select(fdRead.fd_count, &fdRead, NULL, NULL, &tv);
    		if (nRet == 0 || nRet == SOCKET_ERROR) 
    		{
    			continue;
    		}
    
    		//处理数据
    		g_lock.Lock();
    		for (auto itr = vctClients.begin(); itr != vctClients.end(); ++itr)
    		{
    			//判断socket是否是可以读数据了
    			if (FD_ISSET(*itr, &fdRead))
    			{
    				if (!HandleData(*itr))
    				{
    					//连接断开
    					vctClients.erase(itr);
    					break;
    				}
    			}
    		}
    		g_lock.UnLock();
    	}
    
    	return 0;
    }
    
  • 相关阅读:
    一个靠谱的技术方案文档是怎样的
    代码可复用性问题兼谈团队协作
    碎碎念五四
    碎碎念五五
    cmd命令查看本机的端口占用情况
    JS字符串里字符串嵌套和转义字符
    cef内嵌浏览器提示clodop未安装或未启动
    ADD_PRINT_IMAGE直接输出图片URL方式
    lodop缩放图片到完全适合纸张
    部署Kubernetes Cluster
  • 原文地址:https://www.cnblogs.com/zhaoyixiang/p/12961406.html
Copyright © 2011-2022 走看看