zoukankan      html  css  js  c++  java
  • 【网络编程】之六、选择select

      select模型在五中模型中是最简单,最容易实现的,当然他的效率当然不如其他四种;

    select可以去监视一个套接字,看哪个socket有消息到来;

    int select(
      _In_     int nfds,//忽略
      _Inout_  fd_set *readfds,//一个用于检测可读性的参数
      _Inout_  fd_set *writefds,//检查可写性
      _Inout_  fd_set *exceptfds,//用于例外数据
      _In_     const struct timeval *timeout//最大等待时间
    );

    来看一下fd_set:

    typedef struct fd_set { 
     u_int fd_count;//指定可放套接字的数目
     SOCKET fd_array[FD_SETSIZE];//一个套接字的数组
    } fd_set;
    
    其实fd_set就是一个socket的集合。

    其中#define FD_SETSIZE 64

    所以fd_set最多可以放64个套接字。

    fdset 代表着一系列特定套接字的集合。其中, readfds 集合包括符合下述任何一个条件的套接字:
    ● 有数据可以读入。
    ● 连接已经关闭、重设或中止。
    ● 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。

    writefds 集合包括符合下述任何一个条件的套接字:
    ● 有数据可以发出。
    ● 如果已完成了对一个非锁定连接调用的处理,连接就会成功。

    exceptfds 集合包括符合下述任何一个条件的套接字:
    ● 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
    ● 有带外(Out-of-band,OOB)数据可供读取。


    在select函数中,中间三个参数(readfds, writefds, exceptfds)中,你至少有一个不是NULL,不为空了也必须要至少包含一个套接字句柄。如果不满足这些要求,select函数就没有任何东西等待了。

    最后一个参数timeout是一个指向timeval结构的指针,

    typedef struct timeval {
      long tv_sec;//秒
      long tv_usec;//毫秒
    } timeval;
    
    他指定用于select函数等待io操作的最长时间。  如果你传一个空进去,那么select函数调用就会无限的锁定或者停止下去,直到有一个描述符符合指定的条件。

    如果你把最后一个参数设置为(0,0),那么就是说select会立即返回,处于性能的考虑,要避免这样的设置。



    ok,现在我们的select函数成功返回了,她会在fd_set结构中返回刚好有未完成的io操作的所有套接字句柄的总量。如果超时了,那么就会返回0。 如果失败了那么就返回SOCKET_ERROR,我们可以调用WSAGetLastError函数来获取错误码。

    当我们调用select函数之前,我们必须要分配一个fd_set结构集合。然后才可以调用select函数。

    ok,winsock提供给我们一些宏,我们来看一下:

    sock s;

    fd_set set;

    FD_CLR(s, *set); 从set中删除套接字s。

    FD_ISSET(s, *set);检查s是否在set中,如果在就返回TRUE,否则就是FALSE;

    FD_SET(s, *set);将套接字s放入结合set中。

    FDZERO(*set);将set初始化为空集合。



    最后看实例之前,我们先来看一下select的使用步骤:

    1、使用FDZERO初始化一个fd_set对象。

    2、调用FD_SET,把套接字加入到fd_set集合中。

    3、调用select函数,等待返回。

    4、完成后返回所有的fd_set集合中设置的套接字句柄的总数,对每个集合进行了相应的更新。

    5、根据select函数的返回值,联合FD_ISSET对fd_set进行检查。

    6、知道了集合中待解决的IO操作后,对IO进行处理。  返回1,继续进行select的调用处理。


    贴上服务器端代码:

    /**************************************************
    文件名server.cpp
    windows下socket网络编程实例  -- 服务器端基于TCP
    服务器地址:'127.0.0.1'
    端口号 8888
    作者:peter
    ***************************************************/
    #include<WinSock2.h>
    #include<stdio.h>
    #pragma comment(lib,"WS2_32.lib")
    
    bool select_server(SOCKET sock, int nTime = 100, bool bRead = true);
    
    int main(int argc, char* argv[])
    {
    	WSADATA wsaData;
    	WORD sockVersion = MAKEWORD(2,0);//指定版本号
    	::WSAStartup(sockVersion, &wsaData);//载入winsock的dll
    	//创建套接字基于TCP
    	SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    	if(s == INVALID_SOCKET)
    	{
    		printf("error");
    		::WSACleanup();//清理,释放资源
    		return 0;
    	}
    
    	sockaddr_in sin;
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(8888);//端口号8888
    	sin.sin_addr.S_un.S_addr = INADDR_ANY;//地址全是0,也就是所有的地址
    	//绑定socket
    	if(::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    	{
    		printf("error");
    		::WSACleanup();//清理释放资源
    		return 0;
    	}
    	//监听socket
    	if(::listen(s, 2) == SOCKET_ERROR)
    	{
    		printf("error");
    		::WSACleanup();//释放资源
    		return 0;
    	}
    
    	sockaddr_in remoteAddr;
    	int nAddrLen = sizeof(remoteAddr);
    	SOCKET client;
    	char szText[] = "peter\n";//缓冲区数据
    	char szBuf[1024] = {0};
    
    	while(1)
    	{
    		fd_set fdset;
    		FD_ZERO(&fdset);
    		FD_SET(s, &fdset);
    		timeval tv;
    		tv.tv_sec  = 3;
    		tv.tv_usec = 0;
    		int n;
    		if (n = select(0, &fdset,NULL, NULL, &tv) == SOCKET_ERROR)
    		{
    			continue;
    		}
    		if(FD_ISSET(s,&fdset))
    		{
    			/*accept服务器端使用,调用函数进入阻塞状态,等待用户连接,如果没有客户端进行连接,程序就在这个地方。
    				不会看到后面。如果有客户端连接,那么程序就执行一次然后继续循环到这里等待。*/
    			client = ::accept(s, (SOCKADDR*)&remoteAddr, &nAddrLen);
    			if(client == INVALID_SOCKET)
    			{
    				printf("error");
    				continue;
    			}
    
    			printf("接收到一个连接:%s\r\n",inet_ntoa(remoteAddr.sin_addr));
    
    			::send(client, szText, strlen(szText), 0); //发送数据
    
    			int re = ::recv(client, szBuf, 1024, 0);
    			if(re > 0)
    			{
    				szBuf[re] = '\n';
    				printf(szBuf);
    			}
    		
    			::closesocket(client);//关闭套接字
    		}
    	}
    
    	::closesocket(s);
    	::WSACleanup();
    	return 0;
    }
    
    
    客户端见前面的【网络编程】之四、socket网络编程例解  ;

    稍后会写一个简易的聊天程序,将源码贴出,稍后!




    2012/8/22

    jofranks 于南昌

  • 相关阅读:
    RootMotionComputer 根运动计算机
    tar压缩解压缩命令详解
    解决有关flask-socketio中服务端和客户端回调函数callback参数的问题
    flask-sqlalchemy中Datetime的创建时间、修改时间,default,server_default,onupdate
    sqlalchemy和flask-sqlalchemy的几种分页方法
    Flask路由报错:raise FormDataRoutingRedirect(request)
    解决Python自带的json不能序列化data,datetime类型数据问题
    Python中将字典转换为有序列表、无序列表的方法
    flask-sqlalchemy 一对一,一对多,多对多操作
    python2 UnicodeDecodeError: 'ascii' codec can't decode byte 0xce in position 7: ordinal not in range(128)
  • 原文地址:https://www.cnblogs.com/java20130723/p/3211407.html
Copyright © 2011-2022 走看看