socket编程学过很久,真的不好意思说自己不是很懂。不过确实懂得不是很多。

    初学socket是在linux,后来转作windows的,也只涉及winsock1.1的。

    今天只想谈一谈关于阻塞和非阻塞的socket。

    调用socket函数得到的socket的文件标示符,默认情况下是一个阻塞的socket,从应用角度,就是每当调用accept,recv,send等函数的时候,如果对方没有相应,那么进程会阻塞在那里,直到对方相应。这在应用中有很多不便,尤其是在windows环境中的编程,如果进程被阻塞那么看上去有一点像死机的感觉。

     其实把一个socket设置为非阻塞的也很简单。但是,应用select函数,阻塞socket也可以到达类似的效果。一会详细讨论。

    先考虑一个TCP的连接,当服务器程序listen以后,如果直接调用accept,那么进程会阻塞在那里,一直到被client端connect,然后开启一个新的线程处理客户的服务,主线程继续监听请求。这样可以实现同时处理多个客户端连接,但是这样是会阻塞主线程,不是一个好的办法。其实,我当然也可添加一个线程,在“后台”accept,但是我觉得这里面有一个多线程操作同一个文件描述符socket的问题。也不是很好。也就是说,如果只用到线程机制,并不能很好的解决阻塞的问题。

    select函数可以对一套文件描述符操作(windows里叫handle,我觉得没有什么本质的区别。都是进程空间里面的一个整数而已),观察这套文件描述符的可读或可写的情况。这样就给我们提供这样一个思路,只有当一个socket是可读的,也就是有远程连接请求connect或是对套接口write,那么才去调用accept或是read,这样就可以避免阻塞了。

    以准备accept的套接口为例,如果调用socket函数得到的文件描述符为s=3,那么,我可以调用select监视所有4以下的文件描述符的读写特性。select(s+1,&fd_readset,&fd_writeset,&fd_errorset,&tv_time).这个函数是内核等待特定事件的函数,并不是只有套接口可以用。原型是

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

    第一个参数表示观察的最大的文件描述符,一般是你想要观察的socket+1,第二个参数是可读文件描述符set,第三个是可写set,第四个参数是error set,第五个是超时的时间。如果在超时的时间内没有观察过任何改动,那么函数会清空这几个set(我个人理解),并且返回0;如果出错,会返回-1。否则返回>0,并且只把可读(可写)的文件描述符保留在对应的set内。所以只要在调用select以前,把等待连接的套接口放到可读set中,如果发生connect,select会返回>0,这时候可以观察一下套接口是否还在这个set中,如果是,则说明有请求。这个方法可以用于多个套接口同时监听多个端口的情况。也可以用于一个已经连接的套接口等待对方发送的消息,比如write。

    对于set的操作,有如下几个宏:FD_ZERO,FD_SET,FD_ISSET,FD_CLR.

    fd_set myset; //定义描述字集数据类型

    FD_ZERO (&myset); //对描述字集初始化

    FD_SET(s, &myset); //打开描述字的第s位,也即把套接口加入set中

   FD_ISSET(s, &myest) //测试描述字的第s位,这个宏一般是在select之后才会用到。

   FD_CLR(s, &myset) // //关闭描述字的第s位,就是在set中清除s.

    这个函数的使用方法,可以参见下面的程序。就不再细诉了。

    FD_ZERO(&fs_ReadSet);
 FD_SET(iSockets, &fs_ReadSet);
 iSockNum = iSockets+1;

    while(1)
 {
  FD_ZERO(&fs_WriteSet);
  for (iSocketz = 0; iSocketz<iSockNum; iSocketz++)
  {
   if (FD_ISSET(iSocketz, &fs_ReadSet))
    FD_SET(iSocketz, &fs_WriteSet);
  }

  tv_time.tv_sec = 2;
  tv_time.tv_usec = 500000;
  iRtn = select(iSockNum, &fs_WriteSet, NULL, NULL, &tv_time);
  if (iRtn == -1)
  {
   printf("function select error\n");
   goto error;
  }
  else if(iRtn == 0)
  {
   continue;
  }

  if (FD_ISSET(iSockets, &fs_WriteSet))
  {
   iSockLen = sizeof(sa_client);
   iSocketz = accept(iSockets, (struct sockaddr*)&sa_client, &iSockLen);
   if (iSocketz == -1)
   {
    printf("function accept error \n");
    goto error;
   }
   printf("z : %d\n", iSocketz); 
   
   if (iSocketz >=MAX_CLIENT)
   {
               close(iSocketz);
    continue;
   }
   if (iSockNum < iSocketz+1)
    iSockNum = iSocketz+1;
   FD_SET(iSocketz, &fs_ReadSet);
   
  }

  for (iSocketz=iSockets+1; iSocketz < iSockNum; iSocketz++)
  {
          
   if (FD_ISSET(iSocketz, &fs_WriteSet))
   { 
    memset(pRcvBuf, 0, BUFFERSIZE);
    memset(pSndBuf, 0, BUFFERSIZE);
    iRtn = read(iSocketz, pRcvBuf, BUFFERSIZE);
    if (iRtn == -1)
    {
     printf("function read error \n");
     goto error;
    }
 
    printf("%s\n", pRcvBuf);

    pSndBuf[0] = 0;
    strcpy(pSndBuf, "fuck");
    iBufLen = sizeof("fuck");

    iRtn = write(iSocketz, pSndBuf, iBufLen);
    if (iRtn == -1)
    {
     printf("function write error \n");
     goto error;
    }
    
    if(strcmp(pRcvBuf,"exit")==0)
    {
        FD_CLR(iSocketz, &fs_ReadSet);
        close(iSocketz);
    }
   }
   
  }
  for (iSocketz = iSockNum-1; (iSocketz >iSockets&&!FD_ISSET(iSocketz, &fs_ReadSet)); iSocketz = iSockNum-1)
  {
   iSockNum = iSocketz;
  }
  //printf("isockNum: %d\n",iSockNum);
 }

    至于套接口的通信,我还想说一点,套接口和文件管道差不多,一方读一方写,read会阻塞到对方write,而write不会阻塞。应该是这样了。如果我个人对这个部分的理解有错误。会在以后改正。



Trackback: http://tb.donews.net/TrackBack.aspx?PostId=692041


[点击此处收藏本文]  发表于2006年01月12日 1:33 PM




正在读取评论……
添加评论
大名
网址


验证码
评论