1. 程式人生 > >【win網路程式設計】socket中的recv阻塞和select的用法

【win網路程式設計】socket中的recv阻塞和select的用法

轉載請註明出處:作者 kikilizhm

在編寫ftp客戶端程式時,在聯通後使用recv函式進行接收歡迎資訊時,需要申請記憶體進行接收資料儲存,一次讀取成功,但是由於一個隨機的ftp服務端在說,歡迎資訊的大小是不知道的,所以在嘗試使用死迴圈,在閱讀recv的說明時講到返回值即是接收到的位元組數,那麼返回0的時候就代表結束了,實踐發現recv是個阻塞函式,在連線不斷開的情況下,會一直處於阻塞狀態,也不會返回0.也就是說程式不能這麼一直讀,如果對端連線沒有關閉,則在沒有資料的情況下,呼叫recv會阻塞,如果對端關閉連線,則立即返回0.

所以就需要使用到select函式來操作。

MSDN中對select的介紹連線為:ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.chs/winsock/winsock/select_2.htm

select的功能為檢測一個或者多個socket是否可讀或者可寫,或者有錯誤產生。根據設定可以處於阻塞、非阻塞、等待固定時間返回。

原型:

select Function

The select function determines the status of one or more sockets, waiting if necessary, to perform synchronous I/O.

int select(
  __in          int nfds,
  __in_out      fd_set* ,
  __in_out      fd_set* ,
  __in_out      fd_set* ,
  __in          const struct timeval* 
);

Parameters

nfds

Ignored. The nfds parameter is included only for compatibility with Berkeley sockets.

忽略。nfds引數在這裡只是為了和伯克利套接字相相容。(這個引數在linux中有意義)

readfds

Optional pointer to a set of sockets to be checked for readability.

指向一組等待判斷可讀性的socket的指標,可不設定。

writefds

Optional pointer to a set of sockets to be checked for writability.

指向一組等待判斷可寫性的socket的指標,可不設定。

exceptfds

Optional pointer to a set of sockets to be checked for errors.

和上面兩個一樣,指向待檢測是否發生錯誤的一組socket的指標,可不設定。

timeout

Maximum time for select to wait, provided in the form of a TIMEVAL structure. Set the timeout parameter to null for blocking operations.

select函式最大等待時間,使用TIMEVAL結構體。設定為NULL時阻塞。

TIMEVAL結構體定義如下:

typedef struct timeval {
  long tv_sec;    //秒
  long tv_usec;   //毫秒
} timeval;

Return Value

        返回fd_set結構中準備好的(可讀、可寫或者發生錯誤)socket控制代碼的總個數。等待時間到則返回0,發生錯誤返回SOCKET_ERROR。 操作fs_set結構         windows提供幾個巨集對fs_set結構進行操作:

Removes the descriptor s from set.

從fd_set集合中移除一個描述符。

Nonzero if s is a member of the set. Otherwise, zero.

檢測一個描述符是否是fd_set集合的可讀或者可寫成員,不在返回0,是返回非0.

Adds descriptor s to set.

向fs_set集合中新增一個描述符。

Initializes the set to the null set.

初始化fd_set集合為NULL。

例子:

以讀取FTP伺服器的歡迎資訊為例。

注意:在使用過程中如果只是想檢測可讀,千萬不要在寫檢測的引數裡同時賦值。我在寫例子的過程中不小心將同一個rfds同時賦在了讀寫引數裡,結果是雖然不可讀了,但是select仍然返回非0值,因為同一個socket可寫。找了半天才發現錯誤。

這樣就不用擔心申請的記憶體空間不能一次讀完緩衝區了。也不用擔心recv一直阻塞在那裡了。

#include<stdio.h>
#include <winsock2.h>
#include <string.h>

int main(void)
{
    SOCKET fp;
    FILE * ffp;
    struct fd_set rfds;
    struct sockaddr_in ipadd;
    struct timeval timeout = {3,0};
    char * readbuff[10] = {0};
    WSADATA wData;

    WSAStartup(0x0202,&wData);
    fp = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    memset(&ipadd, 0, sizeof(struct sockaddr_in));
    ipadd.sin_family = AF_INET;
    ipadd.sin_addr.s_addr = inet_addr("192.168.1.101");
    ipadd.sin_port = htons(21);

    if(0 != connect(fp, &ipadd, sizeof(struct sockaddr_in)))
    {
        printf("\r\nerror");
    }
    ffp = fopen("test.txt", "rw+");
    int ret;
    while(1)
    {
        FD_ZERO(&rfds);  /* 清空集合 */
        FD_SET(fp, &rfds);  /* 將fp新增到集合,後面的FD_ISSET和FD_SET沒有必然關係,這裡是新增檢測 */

        ret=select(0, &rfds, NULL, NULL, &timeout);
        printf("\r\nselect ret = %d",ret);
        if(0 > ret)
        {
                closesocket(fp);
                fclose(ffp);
                return -1;
        }
        else if(0 == ret)
        {
            break;
        }
        else
        {
            if(FD_ISSET(fp,&rfds))  /* 這裡檢測的是fp在集合中是否狀態變化,即可以操作。 */
            {
                ret = recv(fp, readbuff, 9, 0);
if(0 == ret) return 0;    /* 此處需要檢測!否則ftp傳送資料時,後面會迴圈接收到0位元組資料 */
                // printf("\r\n%s",readbuff);
                fputs(readbuff, ffp);
                memset (readbuff,0,10);
            }
        }



    }
    printf("\r\nread successful!");
    fclose(ffp);
    closesocket(fp);
}