海闊天空

          I'm on my way!
          隨筆 - 17, 文章 - 69, 評(píng)論 - 21, 引用 - 0
          數(shù)據(jù)加載中……

          socket編程


          今天只想談一談關(guān)于阻塞和非阻塞的socket。

          調(diào)用socket函數(shù)得到的socket的文件標(biāo)示符,默認(rèn)情況下是一個(gè)阻塞的socket,從應(yīng)用角度,就是每當(dāng)調(diào)用accept,recv,send 等函數(shù)的時(shí)候,如果對(duì)方?jīng)]有相應(yīng),那么進(jìn)程會(huì)阻塞在那里,直到對(duì)方相應(yīng)。這在應(yīng)用中有很多不便,尤其是在windows環(huán)境中的編程,如果進(jìn)程被阻塞那么 看上去有一點(diǎn)像死機(jī)的感覺(jué)。

          其實(shí)把一個(gè)socket設(shè)置為非阻塞的也很簡(jiǎn)單。但是,應(yīng)用select函數(shù),阻塞socket也可以到達(dá)類似的效果。一會(huì)詳細(xì)討論。

          先考慮一個(gè)TCP的連接,當(dāng)服務(wù)器程序listen以后,如果直接調(diào)用accept,那么進(jìn)程會(huì)阻塞在那里,一直到被client端connect,然后 開(kāi)啟一個(gè)新的線程處理客戶的服務(wù),主線程繼續(xù)監(jiān)聽(tīng)請(qǐng)求。這樣可以實(shí)現(xiàn)同時(shí)處理多個(gè)客戶端連接,但是這樣是會(huì)阻塞主線程,不是一個(gè)好的辦法。其實(shí),我當(dāng)然也 可添加一個(gè)線程,在“后臺(tái)”accept,但是我覺(jué)得這里面有一個(gè)多線程操作同一個(gè)文件描述符socket的問(wèn)題。也不是很好。也就是說(shuō),如果只用到線程 機(jī)制,并不能很好的解決阻塞的問(wèn)題。

          select函數(shù)可以對(duì)一套文件描述符操作(windows里叫handle,我覺(jué)得沒(méi)有什么本 質(zhì)的區(qū)別。都是進(jìn)程空間里面的一個(gè)整數(shù)而已),觀察這套文件描述符的可讀或可寫(xiě)的情況。這樣就給我們提供這樣一個(gè)思路,只有當(dāng)一個(gè)socket是可讀的, 也就是有遠(yuǎn)程連接請(qǐng)求connect或是對(duì)套接口write,那么才去調(diào)用accept或是read,這樣就可以避免阻塞了。

          以準(zhǔn)備accept的套接口為例,如果調(diào)用socket函數(shù)得到的文件描述符為s=3,那么,我可以調(diào)用select監(jiān)視所有4以下的文件描述符的讀寫(xiě)特 性。 select(s+1,&fd_readset,&fd_writeset,&fd_errorset,&tv_time). 這個(gè)函數(shù)是內(nèi)核等待特定事件的函數(shù),并不是只有套接口可以用。原型是

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

          第一個(gè)參數(shù)表示觀察的最大的文件描述符,一般是你想要觀察的socket+1, 第二個(gè)參數(shù)是可讀文件描述符set,第三個(gè)是可寫(xiě)set,第四個(gè)參數(shù)是error set,第五個(gè)是超時(shí)的時(shí)間。如果在超時(shí)的時(shí)間內(nèi)沒(méi)有觀察過(guò)任何改動(dòng),那么函數(shù)會(huì)清空這幾個(gè)set(我個(gè)人理解),并且返回0;如果出錯(cuò),會(huì)返回-1。否 則返回>0,并且只把可讀(可寫(xiě))的文件描述符保留在對(duì)應(yīng)的set內(nèi)。所以只要在調(diào)用select以前,把等待連接的套接口放到可讀set中,如果 發(fā)生connect,select會(huì)返回>0,這時(shí)候可以觀察一下套接口是否還在這個(gè)set中,如果是,則說(shuō)明有請(qǐng)求。這個(gè)方法可以用于多個(gè)套接口 同時(shí)監(jiān)聽(tīng)多個(gè)端口的情況。也可以用于一個(gè)已經(jīng)連接的套接口等待對(duì)方發(fā)送的消息,比如write。

          對(duì)于set的操作,有如下幾個(gè)宏:FD_ZERO,FD_SET,FD_ISSET,FD_CLR.

          fd_set myset; //定義描述字集數(shù)據(jù)類型

          FD_ZERO (&myset); //對(duì)描述字集初始化

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

          FD_ISSET(s, &myest) //測(cè)試描述字的第s位,這個(gè)宏一般是在select之后才會(huì)用到。

          FD_CLR(s, &myset) // //關(guān)閉描述字的第s位,就是在set中清除s.

          這個(gè)函數(shù)的使用方法,可以參見(jiàn)下面的程序。就不再細(xì)訴了。

          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);
          }

          至于套接口的通信,我還想說(shuō)一點(diǎn),套接口和文件管道差不多,一方讀一方寫(xiě),read會(huì)阻塞到對(duì)方write,而write不會(huì)阻塞。應(yīng)該是這樣了。如果我個(gè)人對(duì)這個(gè)部分的理解有錯(cuò)誤。會(huì)在以后改正。

          posted on 2009-12-14 11:35 石頭@ 閱讀(478) 評(píng)論(0)  編輯  收藏 所屬分類: Tcp/Ip

          主站蜘蛛池模板: 屯昌县| 无为县| 宝清县| 玉树县| 长泰县| 虎林市| 伊川县| 聊城市| 财经| 满洲里市| 黎川县| 青阳县| 皋兰县| 黑山县| 鄂尔多斯市| 镇江市| 铜陵市| 抚顺县| 光泽县| 怀安县| 临洮县| 宝兴县| 西畴县| 禹州市| 章丘市| 黑山县| 八宿县| 海南省| 安庆市| 叶城县| 永新县| 曲沃县| 湟源县| 金门县| 静乐县| 将乐县| 德保县| 平安县| 和硕县| 察哈| 阳城县|