<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    海闊天空

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

    2009年11月17日

    經(jīng)典的Linux Socket 編程 示例代碼 (下)


    摘自:http://fanqiang.chinaunix.net/a4/b7/20010810/1200001101.html


    在例程main()函數(shù)快要結(jié)束時,我們看到,在服務(wù)器接受了客戶機(jī)的連接請求后,將為其創(chuàng)建子進(jìn)程,并在子進(jìn)程中執(zhí)行代理服務(wù)程序do_proxy()。
    -----------------------------------------------------------------/****************************************************************
    function:    do_proxy
    description:  does the actual work of virtually connecting a client to the telnet service on the          isolated host.
    arguments:   usersockfd socket to which the client is connected. return value: none.
    calls:     none.
    globals:     reads hostaddr.
    ****************************************************************/
    void do_proxy (usersockfd)
    int usersockfd;
    {
    int isosockfd;
    fd_set rdfdset;
    int connstat;
    int iolen;
    char buf[2048];
    /* open a socket to connect to the isolated host */
    if ((isosockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
    errorout("failed to create socket to host");
    /* attempt a connection */
    connstat = connect(isosockfd,(struct sockaddr *) &hostaddr, sizeof(hostaddr));
    switch (connstat) {
    case 0:
    break;
    case ETIMEDOUT:
    case ECONNREFUSED:
    case ENETUNREACH:
    strcpy(buf,sys_myerrlist[errno]);
    strcat(buf,"\r\n");
    write(usersockfd,buf,strlen(buf));
    close(usersockfd);
    exit(1);
    /* die peacefully if we can't establish a connection */
    break;
    default:
    errorout("failed to connect to host");
    }
    /* now we're connected, serve fall into the data echo loop */
    while (1) {
    /* Select for readability on either of our two sockets */
    FD_ZERO(&rdfdset);
    FD_SET(usersockfd,&rdfdset);
    FD_SET(isosockfd,&rdfdset);
    if (select(FD_SETSIZE,&rdfdset,NULL,NULL,NULL) < 0)
    errorout("select failed");
    /* is the client sending data? */
    if (FD_ISSET(usersockfd,&rdfdset)) {
    if ((iolen = read(usersockfd,buf,sizeof(buf))) <= 0)
    break; /* zero length means the client disconnected */
    rite(isosockfd,buf,iolen);
    /* copy to host -- blocking semantics */
    }
    /* is the host sending data? */
    if (FD_ISSET(isosockfd,&rdfdset)) {
    f ((iolen = read(isosockfd,buf,sizeof(buf))) <= 0)
    break; /* zero length means the host disconnected */
    rite(usersockfd,buf,iolen);
    /* copy to client -- blocking semantics */
    }
    }
    /* we're done with the sockets */
    close(isosockfd);
    lose(usersockfd);
    }
    -----------------------------------------------------------------
    在 我們這段代理服務(wù)器例程中,真正連接用戶主機(jī)和遠(yuǎn)端主機(jī)的一段操作,就是由這個do_proxy()函數(shù)來完成的。回想一下我們一開始對這段 proxy程序用法的介紹。先將我們的proxy與遠(yuǎn)端主機(jī)綁定,然后用戶通過proxy的綁定端口與遠(yuǎn)端主機(jī)建立連接。而在main()函數(shù)中,我們的 proxy由一段服務(wù)器程序與用戶主機(jī)建立了連接,而在這個do_proxy()函數(shù)中,proxy將與遠(yuǎn)端主機(jī)的相應(yīng)服務(wù)端口(由用戶在命令行參數(shù)中指 定)建立連接,并負(fù)責(zé)傳遞用戶主機(jī)和遠(yuǎn)端主機(jī)之間交換的數(shù)據(jù)。
    由于要和遠(yuǎn)端主機(jī)建立連接,所以我們看到do_proxy()函數(shù)的前半部分實際上相當(dāng)于一段標(biāo)準(zhǔn)的客戶機(jī)程序。首先創(chuàng)建一個新的套接字描述符 isosockfd,然后調(diào)用函數(shù)connect()與遠(yuǎn)端主機(jī)之間建立連接。函數(shù)connect()的定義為:
    -----------------------------------------------------------------
    #include <sys/types.h>
    #include <sys/socket.h>
    int connect(int sockfd, struct sockaddr *servaddr, int addrlen);
    -----------------------------------------------------------------
    參數(shù)sockfd是調(diào)用函數(shù)socket()返回的套接字描述符,參數(shù)servaddr指向遠(yuǎn)程服務(wù)器的套接字地址結(jié)構(gòu),參數(shù)addrlen指定這個 套接字地址結(jié)構(gòu)的長度。函數(shù)connect()執(zhí)行成功時返回"0",如果執(zhí)行失敗則返回"-1",并將全局變量errno設(shè)置為相應(yīng)的錯誤類型。在例程 中的switch()函數(shù)調(diào)用中對以下三種出錯類型進(jìn)行了處理: ETIMEDOUT、ECONNREFUSED和ENETUNREACH。這三個出錯類型的意思分別為:ETIMEDOUT代表超時,產(chǎn)生這種情況的原因 有很多,最常見的是服務(wù)器忙,無法應(yīng)答客戶機(jī)的連接請求;ECONNREFUSED代表連接拒絕,即服務(wù)器端沒有準(zhǔn)備好的傾聽套接字,或是沒有對傾聽套接 字的狀態(tài)進(jìn)行監(jiān)聽;ENETUNREACH表示網(wǎng)絡(luò)不可達(dá)。
    在本例中,connect()函數(shù)的第二個參數(shù)servaddr是全局變量hostaddr,其中存儲著函數(shù)parse_args()轉(zhuǎn)換好的命令行 參數(shù)。如果連接建立失敗,在例程中就調(diào)用我們自定義的函數(shù)errorout()輸出信息"failed to connect to host"。errorout()函數(shù)的定義為:
    -----------------------------------------------------------------
    /****************************************************************
    function:  errorout
    description: displays an error message on the console and kills the current process.
    arguments:  msg -- message to be displayed.
    return value: none -- does not return.
    calls:    none.
    globals:   none.
    ****************************************************************/
    void errorout (msg)
    char *msg;
    {
    FILE *console;
    console = fopen("/dev/console","a");
    fprintf(console,"proxyd: %s\r\n",msg);
    fclose(console);
    exit(1);
    }
    -----------------------------------------------------------------
    do_proxy()函數(shù)的后半部分是通過proxy建立用戶主機(jī)與遠(yuǎn)端主機(jī)之間的連接。我們既有proxy與用戶主機(jī)連接的套接字 (do_proxy()函數(shù)的參數(shù)usersockfd),又有proxy與遠(yuǎn)端主機(jī)連接的套接字isosockfd,那么最簡單直接的通信建立方式就是 從一個套接字讀,然后直接寫到另一個套接字去。如:
    -----------------------------------------------------------------
    int n;
    char buf[2048];
    while((n=read(usersockfd, buf, sizeof(buf))>0)
    if(write(isosockfd, buf, n)!=n)
    err_sys("write wrror\n");
    -----------------------------------------------------------------
    這種形式的阻塞I/O在單向數(shù)據(jù)傳遞的時候是非常有效的,但是在我們的proxy操作中是要求用戶主機(jī)和遠(yuǎn)端主機(jī)雙向通信的,這樣就要求我們對兩個套 接字描述符既能夠讀由能夠?qū)憽H绻€是采用這種方式的阻塞I/O的話,很有可能長時間阻塞在一個描述符上。因此例程在處理這個問題的時候調(diào)用了 select()函數(shù),這個函數(shù)允許我們執(zhí)行I/O多路轉(zhuǎn)接。其具體含義就是select()函數(shù)可以構(gòu)造一個表,在這個表中包含了我們所有要用到的文件 描述符。然后我們可以調(diào)用一個函數(shù),這個函數(shù)可以檢測這些文件描述符的狀態(tài),當(dāng)某個(我們指定的)文件描述符準(zhǔn)備好進(jìn)行I/O操作時,此函數(shù)就返回,告知 進(jìn)程哪個文件描述符已經(jīng)可以執(zhí)行I/O操作了。這樣就避免了長時間的阻塞。
    還有一個函數(shù)poll()可以實現(xiàn)I/O多路轉(zhuǎn)接,由于在例程中調(diào)用的是select(),我們就只對select()進(jìn)行一下比較詳細(xì)的介紹。select()系列函數(shù)的詳細(xì)描述為:
    -----------------------------------------------------------------
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    int select(int n, fd_set *readfds, fd_set *writefds, fd_est *exceptfds, struct timeval *timeout);
    FD_CLR(int fd, fd_set *set);
    FD_ISSET(int fd, fd_set *set);
    FD_SET(int fd, fd_set *set);
    FD_ZERO(fd_set *set);
    -----------------------------------------------------------------
    select()函數(shù)將創(chuàng)建一個我們所關(guān)心的文件描述符表,它的參數(shù)將在內(nèi)核中為這些文件描述符設(shè)置我們所關(guān)心的條件,例如是否是可讀、是否可寫以及 是否異常,而且在參數(shù)中還可以設(shè)置我們希望等待的最大時間。在select()成功執(zhí)行時,它將返回目前已經(jīng)準(zhǔn)備好的描述符數(shù)量,同時內(nèi)核可以告訴我們各 個描述符的狀態(tài)信息。如果超時,則返回"0",如果出錯,則函數(shù)返回"-1",并同時設(shè)置errno為相應(yīng)的值。
    select()的最后一個參數(shù)timeout將設(shè)置等待時間。其中結(jié)構(gòu)timeval是在文件<bits/time.h>中定義的。
    -----------------------------------------------------------------
    struct timeval
    {
    __time_t tv_sec; /* Seconds */
    __time_t tv_usec; /* Microseconds */
    };
    -----------------------------------------------------------------
    參數(shù)timeout的設(shè)置有三種情況。象例程中這樣timeout==NULL時,這表示用戶希望永遠(yuǎn)等待,直到我們指定的文件描述符中的一個已準(zhǔn)備 好,或者是捕捉到一個信號。如果是由于捕捉到信號而中斷了這個無限期的等待過程的話,select()將返回"-1",同時設(shè)置errno的值為 EINTR。
    如果timeout->tv_sec==0&&timeout->tv_usec==0,那么這表示完全不等待。 Select()測試了所有指定文件描述符后立即返回。這是得到多個描述符狀態(tài)而不阻塞select()函數(shù)的輪詢方法。
    如果timeout->tv_sec!=0||timeout->tv_usec!=0,那么這兩個參數(shù)的值即為我們希望函數(shù)等待的時 間。其中tv_sec設(shè)置時間單位為秒,tv_usec設(shè)置時間單位為微秒。如果在超時的時候,在我們指定的所有文件描述符里面仍然沒有任何一個準(zhǔn)備好的 話,則select()將返回"0"。
    中間三個參數(shù)的數(shù)據(jù)類型是fd_set,它的意思是文件描述符集,而readfds, writefds和exceptfds則分別是指向文件描述符集的指針,他們分別描述了我們所關(guān)心的可讀、可寫以及狀態(tài)異常的各個文件描述符。之所以我們 稱select()可以創(chuàng)建一個文件描述符"表",那個所謂的表就是由這三個參數(shù)指向的數(shù)據(jù)結(jié)構(gòu)組成的。其具體結(jié)構(gòu)如圖1所示。其中在每個set_fd數(shù) 據(jù)類型中都為我們關(guān)心的所有文件描述符保留了一位。所以在監(jiān)測文件描述符狀態(tài)的時候,就在這些set_fd數(shù)據(jù)結(jié)構(gòu)中查詢相關(guān)的位。
    第一個參數(shù)n用來說明到底需要遍歷多少個描述符位。n的值一般是這樣設(shè)置的,從我們關(guān)心的所有文件描述符中選出最大值再加1。例如我們設(shè)置的所有文件 描述符中最大的為6,那么將n設(shè)置為7,則系統(tǒng)在檢測描述符狀態(tài)的時候,就只用遍歷前7位(fd0~fd6)的狀態(tài)。不過如果不想這樣麻煩的話,我們可以 象例程中那樣將n的值直接設(shè)置為FD_SETSIZE。這是系統(tǒng)中設(shè)定的最大文件描述符個數(shù),不同的系統(tǒng)這個值也不相同,一般是256或是1024。這樣 在檢測描述符狀態(tài)的時候,函數(shù)將遍歷所有的描述符位。
    在調(diào)用select()函數(shù)實現(xiàn)多路I/O轉(zhuǎn)接時,首先我們要聲明一個新的文件描述符集,就象例程中這樣:
    fd_set rdfdset;
    然后調(diào)用FD_ZERO()清空此文件描述符集的所有位,以免下面檢測描述符位的時候返回錯誤結(jié)果:
    FD_ZERO(&rdfdset);
    然后調(diào)用FD_SET()在文件描述符集中設(shè)置我們關(guān)心的位。在本例中,我們關(guān)心的就是分別與用戶主機(jī)和遠(yuǎn)端主機(jī)連接的兩個套接字描述符,所以執(zhí)行這樣的語句:
    FD_SET(usersockfd,&rdfdset);
    FD_SET(isosockfd,&rdfdset);
    然后調(diào)用select()返回描述符狀態(tài),此時描述符狀態(tài)被存儲進(jìn)描述符集,也就是set_fd數(shù)據(jù)結(jié)構(gòu)中。在圖1中我們看到所有的描述符位狀態(tài)都是 "0",在select()返回后,例如fd0可讀,則在readfds描述符集中fd0對應(yīng)的位上將狀態(tài)標(biāo)志設(shè)置為"1",如果fd1可寫,則 writefds描述符集中fd1對應(yīng)的位上將狀態(tài)標(biāo)志設(shè)置為"1",狀態(tài)異常的情況也也與此相同。在本例中,我們只關(guān)心兩個套接字描述符是否可寫,因此 執(zhí)行這樣的select()函數(shù):
    select(FD_SETSIZE,&rdfdset,NULL,NULL,NULL)
    那么在select()返回后怎樣檢測set_fd數(shù)據(jù)結(jié)構(gòu)中描述符位的狀態(tài)呢?這就要調(diào)用函數(shù)FD_ISSET(),如果對應(yīng)文件描述符的狀態(tài)為"已準(zhǔn)備好"(即描述符位為"1"),則FD_ISSET()返回"1",否則返回"0"。
    -----------------------------------------------------------------
    if (FD_ISSET(usersockfd,&rdfdset)) {
    if ((iolen = read(usersockfd,buf,sizeof(buf))) <= 0)
    break; /* zero length means the host disconnected */
    write(isosockfd,buf,iolen);
    -----------------------------------------------------------------
    這一段代碼就實現(xiàn)從套接字usersockfd(用戶主機(jī))到套接字isosockfd(遠(yuǎn)端主機(jī))的無阻塞傳輸。而下一段代碼實現(xiàn)反方向的無阻塞傳輸:
    -----------------------------------------------------------------
    if (FD_ISSET(isosockfd,&rdfdset)) {
    if ((iolen = read(isosockfd,buf,sizeof(buf))) <= 0)
    break; /* zero length means the host disconnected */
    write(usersockfd,buf,iolen);
    -----------------------------------------------------------------
    這樣就通過proxy實現(xiàn)了用戶主機(jī)與遠(yuǎn)端主機(jī)之間的通信。
    對這段proxy代碼我只是寫了一些自己的理解,大多數(shù)是一些函數(shù)的用法,這些都是linux網(wǎng)絡(luò)編程中一些最基礎(chǔ)的知識,如果有不對的地方,還請各位大蝦批評指正。





    posted @ 2009-11-17 21:14 石頭@ 閱讀(754) | 評論 (0)編輯 收藏

    經(jīng)典的Linux Socket 編程 示例代碼 (上)

    http://fanqiang.chinaunix.net/a4/b7/20010810/1200001101.html


    Linux是一個可靠性非常高的操作系統(tǒng),但是所有用過Linux的朋友都會感 覺到, Linux和Windows這樣的"傻瓜"操作系統(tǒng)(這里絲毫沒有貶低Windows的意思,相反這應(yīng)該是Windows的優(yōu)點)相比,后者無疑在易操作 性上更勝一籌。但是為什么又有那么多的愛好者鐘情于Linux呢,當(dāng)然自由是最吸引人的一點,另外Linux強(qiáng)大的功能也是一個非常重要的原因,尤其是 Linux強(qiáng)大的網(wǎng)絡(luò)功能更是引人注目。放眼今天的WAP業(yè)務(wù)、銀行網(wǎng)絡(luò)業(yè)務(wù)和曾經(jīng)紅透半邊天的電子商務(wù),都越來越倚重基于Linux的解決方案。因此 Linux網(wǎng)絡(luò)編程是非常重要的,而且當(dāng)我們一接觸到Linux網(wǎng)絡(luò)編程,我們就會發(fā)現(xiàn)這是一件非常有意思的事情,因為以前一些關(guān)于網(wǎng)絡(luò)通信概念似是而非 的地方,在這一段段代碼面前馬上就豁然開朗了。在剛開始學(xué)習(xí)編程的時候總是讓人感覺有點理不清頭緒,不過只要多讀幾段代碼,很快我們就能體會到其中的樂趣 了。下面我就從一段Proxy源代碼開始,談?wù)勅绾芜M(jìn)行Linux網(wǎng)絡(luò)編程。

       首先聲明,這段源代碼不是我編寫的,讓我們感謝這位名叫Carl Harris的大蝦,是他編寫了這段代碼并將其散播到網(wǎng)上供大家學(xué)習(xí)討論。這段代碼雖然只是描述了最簡單的proxy操作,但它的確是經(jīng)典,它不僅清晰地 描述了客戶機(jī)/服務(wù)器系統(tǒng)的概念,而且?guī)缀醢薒inux網(wǎng)絡(luò)編程的方方面面,非常適合Linux網(wǎng)絡(luò)編程的初學(xué)者學(xué)習(xí)。
    這段Proxy程序的用法是這樣的,我們可以使用這個proxy登錄其它主機(jī)的服務(wù)端口。假如編譯后生成了名為Proxy的可執(zhí)行文件,那么命令及其參數(shù)的描述為:
    ./Proxy <proxy_port> <remote_host> <service_port>
    其中參數(shù)proxy_port是指由我們指定的代理服務(wù)器端口。參數(shù)remote_host是指我們希望連接的遠(yuǎn)程主機(jī)的主機(jī)名,IP地址也同樣有 效。這個主機(jī)名在網(wǎng)絡(luò)上應(yīng)該是唯一的,如果您不確定的話,可以在遠(yuǎn)程主機(jī)上使用uname -n命令查看一下。參數(shù)service_port是遠(yuǎn)程主機(jī)可提供的服務(wù)名,也可直接鍵入服務(wù)對應(yīng)的端口號。這個命令的相應(yīng)操作是將代理服務(wù)器的 proxy_port端口綁定到remote_host的service_port端口。然后我們就可以通過代理服務(wù)器的proxy_port端口訪問 remote_host了。例如一臺計算機(jī),網(wǎng)絡(luò)主機(jī)名是legends,IP地址為10.10.8.221,如果在我的計算機(jī)上執(zhí)行:
    [root@lee /root]#./proxy 8000 legends telnet
    那么我們就可以通過下面這條命令訪問legends的telnet端口。
    -----------------------------------------------------------------
    [root@lee /root]#telnet legends 8000
    Trying 10.10.8.221...
    Connected to legends(10.10.8.221).
    Escape character is '^]'

    Red Hat Linux release 6.2(Zoot)
    Kernel 2.2.14-5.0 on an i686
    Login:
    -----------------------------------------------------------------
    上面的綁定操作也可以使用下面的命令:
    [root@lee /root]#./proxy 8000 10.10.8.221 23
    23是telnet服務(wù)的標(biāo)準(zhǔn)端口號,其它服務(wù)的對應(yīng)端口號我們可以在/etc/services中查看。

    下面我就從這段代碼出發(fā)談?wù)勎覍inux網(wǎng)絡(luò)編程的一些粗淺的認(rèn)識,不對的地方還請各位大蝦多多批評指正。

    ◆main()函數(shù)
    -----------------------------------------------------------------
    #include <stdio.h>
    #include <ctype.h>
    #include <errno.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/file.h>
    #include <sys/ioctl.h>
    #include <sys/wait.h>
    #include <sys/types.h>
    #include <netdb.h>
    #define TCP_PROTO   "tcp"
    int proxy_port;    /* port to listen for proxy connections on */
    struct sockaddr_in hostaddr;   /* host addr assembled from gethostbyname() */
    extern int errno;   /* defined by libc.a */
    extern char *sys_myerrlist[];
    void parse_args (int argc, char **argv);
    void daemonize (int servfd);
    void do_proxy (int usersockfd);
    void reap_status (void);
    void errorout (char *msg);
    /*This is my modification.
    I'll tell you why we must do this later*/
    typedef void Signal(int);
    /****************************************************************
    function:    main
    description:   Main level driver. After daemonizing the process, a socket is opened to listen for         connections on the proxy port, connections are accepted and children are spawned to         handle each new connection.
    arguments:    argc,argv you know what those are.
    return value:  none.
    calls:      parse_args, do_proxy.
    globals:     reads proxy_port.
    ****************************************************************/
    main (argc,argv)
    int argc;
    char **argv;
    {
    int clilen;
    int childpid;
    int sockfd, newsockfd;
    struct sockaddr_in servaddr, cliaddr;
    parse_args(argc,argv);
    /* prepare an address struct to listen for connections */
    bzero((char *) &servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = proxy_port;
    /* get a socket... */
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {
    fputs("failed to create server socket\r\n",stderr);
    exit(1);
    }
    /* ...and bind our address and port to it */
    if   (bind(sockfd,(struct sockaddr_in *) &servaddr,sizeof(servaddr)) < 0) {
    fputs("faild to bind server socket to specified port\r\n",stderr);
    exit(1);
    }
    /* get ready to accept with at most 5 clients waiting to connect */
    listen(sockfd,5);
    /* turn ourselves into a daemon */
    daemonize(sockfd);
    /* fall into a loop to accept new connections and spawn children */
    while (1) {
    /* accept the next connection */
    clilen = sizeof(cliaddr);
    newsockfd = accept(sockfd, (struct sockaddr_in *) &cliaddr, &clilen);
    if (newsockfd < 0 && errno == EINTR)
    continue;
    /* a signal might interrupt our accept() call */
    else if (newsockfd < 0)
    /* something quite amiss -- kill the server */
    errorout("failed to accept connection");
    /* fork a child to handle this connection */
    if ((childpid = fork()) == 0) {
    close(sockfd);
    do_proxy(newsockfd);
    exit(0);
    }
    /* if fork() failed, the connection is silently dropped -- oops! */
    lose(newsockfd);
    }
    }
    -----------------------------------------------------------------
    上面就是Proxy源代碼的主程序部分,也許您在網(wǎng)上也曾經(jīng)看到過這段代碼,不過細(xì)心的您會發(fā)現(xiàn)在上面這段代碼中我修改了兩個地方,都是在預(yù)編譯部分。一個地方是在定義外部字符型指針數(shù)組時,我將原代碼中的
    extern char *sys_errlist[];
    修改為
    extern char *sys_myerrlist[];原因是在我的Linux環(huán)境下頭文件"stdio.h"已經(jīng)對sys_errlist[]進(jìn)行了如下定義:
    extern __const char *__const sys_errlist[];
    也許Carl Harris在94年編寫這段代碼時系統(tǒng)還沒有定義sys_errlist[],不過現(xiàn)在我們不修改一下的話,編譯時系統(tǒng)就會告訴我們sys_errlist發(fā)生了定義沖突。
    另外我添加了一個函數(shù)類型定義:
    typedef void Sigfunc(int);
    具體原因我將在后面向大家解釋。

    套接字和套接字地址結(jié)構(gòu)定義

    這段主程序是一段典型的服務(wù)器程序。網(wǎng)絡(luò)通訊最重要的就是套接字的使用,在程序的一開始就對套接字描述符sockfd和newsockfd進(jìn)行了定義。 接下來定義客戶機(jī)/服務(wù)器的套接字地址結(jié)構(gòu)cliaddr和servaddr,存儲客戶機(jī)/服務(wù)器的有關(guān)通信信息。然后調(diào)用parse_args (argc,argv)函數(shù)處理命令參數(shù)。關(guān)于這個parse_args()函數(shù)我們待會兒再做介紹。

    創(chuàng)建通信套接字

      下面就是建立一個服務(wù)器的詳細(xì)過程。服務(wù)器程序的第一個操作是創(chuàng)建一個套接字。這是通過調(diào)用函數(shù)socket()來實現(xiàn)的。socket()函數(shù)的具體描述為:
    -----------------------------------------------------------------
    #include <sys/types.h>
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);
    -----------------------------------------------------------------
    參數(shù)domain指定套接字使用的協(xié)議族,AF_INET表示使用TCP/IP協(xié)議族,AF_UNIX表示使用Unix協(xié)議族,AF_ISO表示套接 字使用ISO協(xié)議族。type指定套接字類型,一般的面向連接通信類型(如TCP)設(shè)置為SOCK_STREAM,當(dāng)套接字為數(shù)據(jù)報類型時,type應(yīng)設(shè) 置為SOCK_DGRAM,如果是可以直接訪問IP協(xié)議的原始套接字則type應(yīng)設(shè)置為SOCK_RAW。參數(shù)protocol一般設(shè)置為"0",表示使 用默認(rèn)協(xié)議。當(dāng)socket()函數(shù)成功執(zhí)行時,返回一個標(biāo)志這個套接字的描述符,如果出錯則返回"-1",并設(shè)置errno為相應(yīng)的錯誤類型。

    設(shè)置服務(wù)器套接字地址結(jié)構(gòu)

    在通常情況下,首先要將描述服務(wù)器信息的套接字地址結(jié)構(gòu)清零,然后在地址結(jié)構(gòu)中填入相應(yīng)的內(nèi)容,準(zhǔn)備接受客戶機(jī)送來的連接建立請求。這個清零操作可以用 多種字節(jié)處理函數(shù)來實現(xiàn),例如bzero()、bcopy()、memset()、memcpy()等,以字母"b"開始的兩個函數(shù)是和BSD系統(tǒng)兼容 的,而后面兩個是ANSI C提供的函數(shù)。這段代碼中使用的bzero()其描述為:
    void bzero(void *s, int n);
    函數(shù)的具體操作是將參數(shù)s指定的內(nèi)存的前n個字節(jié)清零。memset()同樣也很常用,其描述為:
    void *memset(void *s, int c, size_t n);
    具體操作是將參數(shù)s指定的內(nèi)存區(qū)域的前n個字節(jié)設(shè)置為參數(shù)c的內(nèi)容。
    下一步就是在已經(jīng)清零的服務(wù)器套接字地址結(jié)構(gòu)中填入相應(yīng)的內(nèi)容。Linux系統(tǒng)的套接字是一個通用的網(wǎng)絡(luò)編程接口,它應(yīng)該支持多種網(wǎng)絡(luò)通信協(xié)議,每一 種協(xié)議都使用專門為自己定義的套接字地址結(jié)構(gòu)(例如TCP/IP網(wǎng)絡(luò)的套接字地址結(jié)構(gòu)就是struct sockaddr_in)。不過為了保持套接字函數(shù)調(diào)用參數(shù)的一致性,Linux系統(tǒng)還定義了一種通用的套接字地址結(jié)構(gòu):
    -----------------------------------------------------------------
    <linux/socket.h>
    struct sockaddr
    {
    unsigned short sa_family; /* address type */
    char sa_data[14]; /* protocol address */
    }
    -----------------------------------------------------------------
    其中sa_family意指套接字使用的協(xié)議族地址類型,對于我們的TCP/IP網(wǎng)絡(luò),其值應(yīng)該是AF_INET,sa_data中存儲具體的協(xié)議地 址,不同的協(xié)議族有不同的地址格式。這個通用的套接字地址結(jié)構(gòu)一般不用做定義具體的實例,但是常用做套接字地址結(jié)構(gòu)的強(qiáng)制類型轉(zhuǎn)換,如我們經(jīng)常可以看到這 樣的用法:
    bind(sockfd,(struct sockaddr *) &servaddr,sizeof(servaddr))
    用于TCP/IP協(xié)議族的套接字地址結(jié)構(gòu)是sockaddr_in,其定義為:
    -----------------------------------------------------------------
    <linux/in.h>
    struct in_addr
    {
    __u32 s_addr;
    };
    struct sochaddr_in
    {
    short int sin_family;
    unsigned short int sin_port;
    struct in_addr sin_addr;
    /*This part has not been taken into use yet*/
    nsigned char_ _ pad[_ _ SOCK_SIZE__- sizeof(short int) -sizeof(unsigned short int) -       sizeof(struct in_addr)];
    };
    #define sin_zero_ - pad
    -----------------------------------------------------------------
    其中sin_zero成員并未使用,它是為了和通用套接字地址struct sockaddr兼容而特意引入的。在編程時,一般都通過bzero()或是memset()將其置零。其他成員的設(shè)置一般是這樣的:
    servaddr.sin_family = AF_INET;
    表示套接字使用TCP/IP協(xié)議族。
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    設(shè)置服務(wù)器套接字的IP地址為特殊值INADDR_ANY,這表示服務(wù)器愿意接收來自任何網(wǎng)絡(luò)設(shè)備接口的客戶機(jī)連接。htonl()函數(shù)的意思是將主機(jī)順序的字節(jié)轉(zhuǎn)換成網(wǎng)絡(luò)順序的字節(jié)。
    servaddr.sin_port = htons(PORT);
    設(shè)置通信端口號,PORT應(yīng)該是我們已經(jīng)定義好的。在本例中servaddr.sin_port = proxy_port;這是表示端口號是函數(shù)的返回值proxy_port。
    另外需要說明的一點是,在本例中,我們并沒有看到在預(yù)編譯部分中包含有<linux/socket.h>和< linux/in.h>這兩個頭文件,那是因為這兩個頭文件已經(jīng)分別被包含在<sys/types.h>和< sys/types.h>中了,而且后面這兩個頭文件是與平臺無關(guān)的,所以在網(wǎng)絡(luò)通信中一般都使用這兩個頭文件。

    服務(wù)器公開地址

      如果服務(wù)器要接受客戶機(jī)的連接請求,那么它必須先要在整個網(wǎng)絡(luò)上公開自己的地址。在設(shè)置了服務(wù)器的套接字地址結(jié)構(gòu)之后,可以通過調(diào)用函數(shù)bind()綁定服務(wù)器的地址和套接字來完成公開地址的操作。函數(shù)bind()的詳細(xì)描述為:
    -----------------------------------------------------------------
    #include <sys/types.h>
    #include <sys/socket.h>
    int bind(int sockfd, struct sockaddr *addr, int addrlen);
    -----------------------------------------------------------------
    參數(shù)sockfd是我們通過調(diào)用socket()創(chuàng)建的套接字描述符。參數(shù)addr是本機(jī)地址,參數(shù)addrlen是套接字地址結(jié)構(gòu)的長度。函數(shù)執(zhí)行成功時返回"0",否則返回"-1",并設(shè)置errno變量為EADDRINUAER。
    如果是服務(wù)器調(diào)用bind()函數(shù),如果設(shè)置了套接字的IP地址為某個本地IP地址,那么這表示服務(wù)器只接受來自于這個IP地址的特定主機(jī)發(fā)出的連接 請求。不過一般情況下都是將IP地址設(shè)置為INADDR_ANY,以便接受所有網(wǎng)絡(luò)設(shè)備接口送來的連接請求。
    客戶機(jī)一般是不會調(diào)用bind()函數(shù)的,因為客戶機(jī)在連接時不用指定自己的套接字地址端口號,系統(tǒng)會自動為客戶機(jī)選擇一個未用端口號,并且用本地 IP地址自動填充客戶機(jī)套接字地址結(jié)構(gòu)中的相應(yīng)項。但是在某些特定的情況下客戶機(jī)需要使用特定的端口號,例如Linux中的rlogin命令就要求使用保 留端口號,而系統(tǒng)是不能為客戶機(jī)自動分配保留端口號的,這就需要調(diào)用bind()來綁定一個保留端口號了。不過在一些特殊的環(huán)境下,這樣綁定特定端口號也 會帶來一些負(fù)面影響,如在HTTP服務(wù)器進(jìn)入TIME_WAIT狀態(tài)后,客戶機(jī)如果要求再次與服務(wù)器建立連接,則服務(wù)器會拒絕這一連接請求。如果客戶機(jī)最 后進(jìn)入TIME_WAIT狀態(tài),則馬上再次執(zhí)行bind()函數(shù)時會返回出錯信息"-1",原因是系統(tǒng)會認(rèn)為同時有兩次連接綁定同一個端口。

    轉(zhuǎn)換Listening套接字

    接下來,服務(wù)器需要將我們剛才與IP地址和端口號完成綁定的套接字轉(zhuǎn)換成傾聽listening套接字。只有服務(wù)器程序才需要執(zhí)行這一步操作。我們通過調(diào)用函數(shù)listen()實現(xiàn)這一操作。listen()的詳細(xì)描述為:
    -----------------------------------------------------------------
    #include <sys/socket.h>
    int listen(int sockfd, int backlog);
    -----------------------------------------------------------------
    參數(shù)sockfd指定我們要求轉(zhuǎn)換的套接字描述符,參數(shù)backlog設(shè)置請求隊列的最大長度。函數(shù)listen()主要完成以下操作。
    首先是將套接字轉(zhuǎn)換成傾聽套接字。因為函數(shù)socket()創(chuàng)建的套接字都是主動套接字,所以客戶機(jī)可以通過調(diào)用函數(shù)connect()來使用這樣的 套接字主動和服務(wù)器建立連接。而服務(wù)器的情況恰恰相反,服務(wù)器需要通過套接字接收客戶機(jī)的連接請求,這就需要一個"被動"套接字。listen()就可將 一個尚未連接的主動套接字轉(zhuǎn)換成為這樣的"被動"套接字,也就是傾聽套接字。在執(zhí)行了listen()函數(shù)之后,服務(wù)器的TCP就由CLOSED變成 LISTEN狀態(tài)了。
    另外listen()可以設(shè)置連接請求隊列的最大長度。雖然參數(shù)backlog的用法非常簡單,只是一個簡單的整數(shù)。但搞清楚請求隊列的含義對理解TCP 協(xié)議的通信過程建立非常重要。TCP協(xié)議為每個傾聽套接字實際上維護(hù)兩個隊列,一個是未完成連接隊列,這個隊列中的成員都是未完成3次握手的連接;另一個 是完成連接隊列,這個隊列中的成員都是雖然已經(jīng)完成了3次握手,但是還未被服務(wù)器調(diào)用accept()接收的連接。參數(shù)backlog實際上指定的是這個 傾聽套接字完成連接隊列的最大長度。在本例中我們是這樣用的:listen(sockfd,5);表示完成連接隊列的最大長度為5。

    接收連接

    接下來我們在主程序中看到通過名為daemonize()的自定義函數(shù)創(chuàng)建一個守護(hù)進(jìn)程,關(guān)于這個daemonize()以及守護(hù)進(jìn)程的相關(guān)概念,我 們等一會兒再做詳細(xì)介紹。然后服務(wù)器程序進(jìn)入一個無條件循環(huán),用于監(jiān)聽接收客戶機(jī)的連接請求。在此過程中如果有客戶機(jī)調(diào)用connect()請求連接,那 么函數(shù)accept()可以從傾聽套接字的完成連接隊列中接受一個連接請求。如果完成連接隊列為空,這個進(jìn)程就睡眠。accept()的詳細(xì)描述為:
    -----------------------------------------------------------------
    #include <sys/socket.h>
    int accept(int sockfd, struct sockaddr *addr, int *addrlen);
    -----------------------------------------------------------------
    參數(shù)sockfd是我們轉(zhuǎn)換成功的傾聽套接字描述符;參數(shù)addr是一個指向套接字地址結(jié)構(gòu)的指針,參數(shù)addrlen為一個整型指針。當(dāng)函數(shù)成功執(zhí) 行時,返回3個結(jié)果,函數(shù)返回一個新的套接字描述符,服務(wù)器可以通過這個新的套接字描述符和客戶機(jī)進(jìn)行通信。參數(shù)addr所指向的套接字地址結(jié)構(gòu)中將存放 客戶機(jī)的相關(guān)信息,addrlen指針將描述前述套接字地址結(jié)構(gòu)的長度。在通常情況下服務(wù)器對這些信息不是很感興趣,因此我們經(jīng)常可以看到一些源代碼中將 accept()函數(shù)的后兩個參數(shù)都設(shè)置為NULL。不過在這段proxy源代碼中需要用到有關(guān)的客戶機(jī)信息,因此我們看到通過執(zhí)行
    newsockfd = accept(sockfd, (struct sockaddr_in *) &cliaddr, &clilen);
    將客戶機(jī)的詳細(xì)信息存放在地址結(jié)構(gòu)cliaddr中。而proxy就通過套接字newsockfd與客戶機(jī)進(jìn)行通信。值得注意的是這個返回的套接字描 述符與我們轉(zhuǎn)換的傾聽套接字是不同的。在一段服務(wù)器程序中,可以始終只用一個傾聽套接字來接收多個客戶機(jī)的連接請求;而如果我們要和客戶機(jī)建立一個實際的 連接的話,對每一個請求我們都需要調(diào)用accept()返回一個新的套接字。當(dāng)服務(wù)器處理完畢客戶機(jī)的請求后,一定要將相應(yīng)的套接字關(guān)閉;如果整個服務(wù)器 程序?qū)⒁Y(jié)束,那么一定要將傾聽套接字關(guān)閉。
    如果accept()函數(shù)執(zhí)行失敗,則返回"-1",如果accept()函數(shù)阻塞等待客戶機(jī)調(diào)用connect()建立連接,進(jìn)程在此時恰好捕捉到 信號,那么函數(shù)在返回"-1"的同時將變量errno的值設(shè)置為EINTR。這和accept()函數(shù)執(zhí)行失敗是有區(qū)別的。因此我們在代碼中可以看到這樣 的語句:
    -----------------------------------------------------------------
    if (newsockfd < 0 && errno == EINTR)
    continue;
    /* a signal might interrupt our accept() call */
    else if (newsockfd < 0)
    /* something quite amiss -- kill the server */
    errorout("failed to accept connection");
    -----------------------------------------------------------------
    可以看出程序在處理這兩種情況時操作是完全不同的,同樣是accept()返回"-1",如果有errno == EINTR,那么系統(tǒng)將再次調(diào)用accept()接受連接請求,否則服務(wù)器進(jìn)程將直接結(jié)束。

    posted @ 2009-11-17 21:13 石頭@ 閱讀(2379) | 評論 (0)編輯 收藏

    主站蜘蛛池模板: 4480yy私人影院亚洲| 久久精品亚洲视频| 亚洲欧美日韩国产精品一区| 18以下岁毛片在免费播放| 亚洲精品网站在线观看你懂的| 美丽姑娘免费观看在线观看中文版 | 日韩成人免费视频| 亚洲第一福利网站| 亚洲精品视频在线免费| 91亚洲性爱在线视频| 黄页免费的网站勿入免费直接进入| 亚洲精品视频在线观看免费| 18国产精品白浆在线观看免费| 亚洲五月综合缴情婷婷| 成人au免费视频影院| 成人亚洲国产精品久久| 国产亚洲色视频在线| 99精品热线在线观看免费视频| 亚洲激情电影在线| 四虎成人精品一区二区免费网站| 免费在线观看亚洲| 亚洲精品午夜国产VA久久成人| 精品熟女少妇av免费久久| 亚洲精品456人成在线| 亚洲国产精品激情在线观看| a毛片免费全部播放完整成| 666精品国产精品亚洲| 国产高清免费在线| 99精品全国免费观看视频..| 亚洲第一区视频在线观看| 在线观着免费观看国产黄| 中文字幕视频在线免费观看| 亚洲最新中文字幕| 免费乱码中文字幕网站| 久草福利资源网站免费| 亚洲乱码av中文一区二区| 日韩精品亚洲aⅴ在线影院| 国产免费久久精品99re丫y| 国产精品成人免费观看| 亚洲激情视频图片| 亚洲中文字幕无码不卡电影|