Linux網路編程,第一部分:BSD套介面篇(下)(翻譯)

來源:互聯網
上載者:User

(接上篇)
2、建立對應的用戶端
    正如你正要看到的,相比服務端,用戶端的代碼就要簡單多了。在這個程式中你必須提供兩個命令列參數:服務端所在機器主機名稱或IP地址,和服務段綁定的連接埠。當然,服務端還必須在用戶端運行以前就已經正常運行:P。

/*
 * Listing 2:
 * An example client for "Hello, World!" server
 * Ivan Griffin (ivan.griffin@ul.ie)
 */

/* Hellwolf Misty translated */

#include <stdio.h>                /* perror() */
#include <stdlib.h>               /* atoi() */
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>               /* read() */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

int main(int argc, char *argv[])
{
    int clientSocket,
        remotePort,
        status = 0;
    struct hostent *hostPtr = NULL;
    struct sockaddr_in serverName = { 0 };
    char buffer[256] = "";
    char *remoteHost = NULL;

    if (3 != argc)
    {
        fprintf(stderr, "Usage: %s
  /n",
            argv[0]);
        exit(1);
    }

    remoteHost = argv[1];
    remotePort = atoi(argv[2]);

    clientSocket = socket(PF_INET, SOCK_STREAM,
  IPPROTO_TCP);
    if (-1 == clientSocket)
    {
        perror("socket()");
        exit(1);
    }

    /*
     * 首先假定是DNS主機名稱
     * 註:
     * struct    hostent{
     *      char *h_name;       /* official name of host */
     *      char **h_aliases;   /* alias list */
     *      int  h_addrtype;    /* host address type */
     *      int  h_length;     /* length of address */
     *      char **h_addr_list; /* list of addresses from name server */
     * };
     * #define   h_addr  h_addr_list[0] 
     * 注意到了嗎?h_addr是一個宏,如果你用gdb調試時
     * display phostent->h_addr出錯的話不要奇怪
     */
    hostPtr = gethostbyname(remoteHost); /* struct hostent *hostPtr; */
    if (NULL == hostPtr)
    {/* 不是?? */
        hostPtr = gethostbyaddr(remoteHost,
  strlen(remoteHost), AF_INET);/* 應該是點分形式的IP地址吧*/
        if (NULL == hostPtr) /* 還不是,!-_- */
        {
        perror("Error resolving server address");
        exit(1);
        }
    }

    serverName.sin_family = AF_INET;
    serverName.sin_port = htons(remotePort);
    (void) memcpy(&serverName.sin_addr,
     hostPtr->h_addr,
     hostPtr->h_length);

/* 這裡並不需要再bind了,因為connect已經可以為我們解決一切 */
    status = connect(clientSocket,
        (struct sockaddr*) &serverName,
                      sizeof(serverName));
    if (-1 == status)
    {
        perror("connect()");
        exit(1);
    }
/* connect成功後,一個雙工(duplex)的網路連接就被建立好了
 * 像伺服器一樣,用戶端可以使用read()和write()接收資料
    /*
     * 用戶端的具體代碼應該從這裡開始實施
     * 比如從服務端接受和回應資訊等等
     */
    while (0 < (status = read(clientSocket,
                               buffer, sizeof(buffer) - 1)))
    {
        printf("%d: %s", status, buffer);
        /* 註:如果讀成功,status表示獲得的位元組數(包括'/0') */
    }

    if (-1 == status)
    {
        perror("read()");
    }

    close(clientSocket);

    return 0;
}

需要注意的幾點:

  • 發送文字通常都工作正常。但是記住不同的系統對換行的實現有差別(比如,Unix使用/0x12,而微軟使用/0x15/0x12)。
  • 不同的實現可能使用不同的位元組序(byte-order)。但不用擔心,BSD的設計者們在一考慮了這一點。有很多現成的函數實現了這種轉換,他們都有一定的命名規則:htons代表實現host-to-network的short結構的轉換,還有htonl,ntohs,ntohl,也很容易判斷出他們的工作方式。至於網路位元組序是大端對齊(big-endian)還是小端對齊(little-endian)並不是個問題,因為在TCP/IP網路上它已經被標準化了(註:網路位元組序用的是大端對齊)。當然除非你一直在網路上發一個字元(註:還得是ASCII的),不用這些轉換函式不會引起大問題,但通常情況下,你會遭遇到位元組序問題。這還要看你的機器,有時侯這些函數就是一個空宏,有時候它們確實是函數。有趣的是,最常見的網路編程的bugs來源是忘記了在填充sockaddr_in結構的sin_addr.s_addr欄位時忘記了使用這些函數,即使使用INADDR_ANY也必須這麼做。
  • 網路編程的一個重要目標就是不給雙方帶來不可預料的麻煩。比如說,伺服器在訪問關鍵資料時必須通過必要的機制同步對這部分資源的訪問,避免由此帶來的死結並且保證資料的有效性。
  • 大多數情況下,你不能從機器間傳遞指標並試圖使用它。
    <>類似的,大多數情況下,你也不能試圖通過套介面傳遞一個檔案描述符從一個進程到另一個進程(非子進程)並直接使用它。BSD和SVR4系統提供了在不相關的進程間傳遞檔案描述符的不同方法;然而,在linux下最簡單的方法就是通過使用/proc檔案系統。
       
  • 此外,你必須保證你正確的解決了short writes問題。Short write發生在write()調用僅僅部分的將緩衝區寫給一個檔案描述符對應的裝置。它們發生的原因歸咎於作業系統的緩衝區,和底層傳輸系統的流量控制系統。某些系統調用,通常被稱作慢系統調用(slow system calls)可能會被(其他調用)中斷。一些可能不會被自動重起,所以你必須明確地在網路編程時解決這一問題。下面的代碼解決了short write問題:
    /*
     * Listing 3:
     * Handling short writes
     * Ivan Griffin (ivan.griffin@ul.ie)
     */
    /* Hellwolf Misty translated */
    int bytesToSend = 0,
        bytesWritten = 0,
        num = 0;

/*
 * 這裡用到的bytesToSend, buffer, and
 * fileDesc必須已經在其他某個地方有定義.
 */

for (bytesWritten = 0; bytesWritten < bytesToSend;
     bytesWritten += num)
{
    num = write(fileDesc,
        (void *)( (char *)buffer +
  (char *)bytesWritten ),
        bytesToSend - bytesWritten);

    if (num < 0)
    {
        perror("write()");

        if (errno != EINTR)
        {
            exit(1);
        }
    }
}

使用多線程而不是多進程可能會減輕伺服器的負擔,並且更加有效。線程間內容相關的轉化(當然是指同一個進程空間)通常開銷比進程間上下文轉換小得多。然而,如此多的子線程都在操作網路I/O,如果它們在核心級還可以,但如果它們是使用者級的,整個進程都會因為第一個調用I/O的線程而阻塞。這將會導致不願看到的其他線程的饑餓狀態直到I/O的完成。正如你看到的,當使用簡單的forking模型時在父進程和子進程中關閉不必要的套介面檔案描述符是相當尋常的。這保護了進程潛在的錯誤讀寫這些描述符的可能性。但是不要試圖在使用執行緒模式時這樣做,進程中的多線程共用同一個記憶體虛擬位址空間和檔案描述符集。如果你在一個線程中關閉了一個描述符,那麼進程中的其他所有的線程都將無法得到該描述符。

3、不需連線的資料轉送——UDP
   下面的代碼顯示了一個使用UDP的服務端。UDP程式很像TCP程式,但他們又很大的不同。首先,UDP不保證可靠的傳輸——如果你需要在使用UDP時獲得可靠性,你必須或者自己實現或者轉而用TCP。
  
   像TCP程式一樣,用UDP你可以建立一個套介面並將其綁定到特定地址。UDP服務端不監聽(listen)和接受(accept)外來的串連,客戶也不必顯式的串連到伺服器。事實上,在UDP用戶端和服務段之間並沒有太大的區別。服務端必須綁定到一個確定的連接埠和地址好讓用戶端知道向哪裡發送資料。而且當你的服務端使用send(),用戶端也應該使用對應的recv族函數。
UDP服務端程式清單:
/*
 * Listing 4:
 * Example UDP (connectionless) server
 * Ivan Griffin (ivan.griffin@ul.ie)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define MAX_MESG_SIZE 4096
char mesg[MAX_MESG_SIZE] = "";

int main(int argc, char *argv[])
{
    int udpSocket = 0,
        myPort = 0,
        status = 0,
        size = 0,
        clientLength = 0;
    struct sockaddr_in serverName = { 0 },
        clientName = { 0 };

    if (2 != argc)
    {
        fprintf(stderr, "Usage: %s/n",
  argv[0]);
        exit(1);
    }

    myPort = atoi(argv[1]);

    udpSocket = socket(PF_INET, SOCK_DGRAM, /* PF_INET和SOCK_DGRAM組合代表了UDP */
  IPPROTO_UDP);
    if (-1 == udpSocket)
    {
        perror("socket()");
        exit(1);
    }

    memset(&serverName, 0, sizeof(serverName));
    memset(&clientName, 0, sizeof(clientName));

    serverName.sin_family = AF_INET;
    serverName.sin_addr.s_addr = htonl(INADDR_ANY);
    serverName.sin_port = htons(myPort);

    status = bind(udpSocket, (struct sockaddr *)
  &serverName, sizeof(serverName));
    if (-1 == status)
    {
        perror("bind()");
        exit(1);
    }

    for (;;)
    {
/* ssize_t  recvfrom(int s, void *buf, size_t len, int flags, struct sock-
 *     addr *from, socklen_t *fromlen);
 */    
        size = recvfrom(udpSocket, mesg,
  MAX_MESG_SIZE, 0,
  (struct sockaddr *) &clientName,
  &clientLength);
        if (size == -1)
        {
            perror("recvfrom()");
            exit(1);
        }
/* ssize_t  sendto(int  s,  const  void *msg, size_t len, int flags, const
 *     struct sockaddr *to, socklen_t tolen);
 */
        status = sendto(udpSocket, mesg, size, 0,
            (struct sockaddr *) &clientName,
  clientLength);
        if (status != size)
        {
            fprintf(stderr,
  "sendto(): short write./n");
            exit(1);
        }
    }

    /* never reached */
    return 0;
}

將TCP的用戶端改寫成UDP的用戶端將留作一個練習

4、/etc/services檔案
    為了串連到一個服務端,你必須首Crowdsourced Security Testing道其監聽的地址和連接埠。許多常見的服務(FTP,TELNET等等)的資訊都列在了一個文本文

件/etc/services中。getservbyname()函數可以用名稱詢問一個服務的詳細情況包括它的連接埠號碼(注意!它已經是網路位元組序了),它的原型在

/usr/include:
struct servent *getservbyname(const char *name, const char *proto); 預設的proto是"tcp"

struct servent
{
 char *s_name; /* official service name */
 char **s_aliases; /* alias list */
 int s_port; /* port number, network
    * byte-order--so do not
     * use host-to-network macros */
 char *s_proto; /* protocol to use */
};

5、總結
    這篇文章介紹了Linux下使用C和BSD Socket API進行網路編程。總的來說,用這套API來寫代碼將是相當耗費勞力的,特別是與其他一些

技術相比。在今後的文章中,我會將BSD Socket API與另外兩個可以選擇的Linux下的方法進行比較:遠端程序呼叫(RPCs,Remote Procedure

Calls)和CORBA(Common Object Request Broker Achitecture,公用對象請求代理[調度]程式體繫結構)。RPCs在Ed Petron的文章"Remote

Procedure Calls"(Linux Journal Issue #42,1997年十月)中有介紹。

相關資源:
UNIX Network Programming, W. Richard Steves, Prentice Hall, 1990.

An Introductory 4.4BSD Interprocess Communication Tutorial, Stuart Sechrest, University of California, Berkeley. 1986.

Available via anonymous FTP at: ftp://ftp.NetBSD.ORG/pub/NetBSD/misc/lite2-docs/psd/20.ipctut.ps.gz.

An Advanced 4.4BSD Interprocess Communication Tutorial, Samuel J. Leffler, Robert S. Fabry, William N. Joy, Phil Lapsley,

University of California, Berkeley. 1986 Available via anonymous FTP at: ftp://ftp.NetBSD.ORG/pub/NetBSD/misc/lite2-

docs/psd/21.ipc.ps.gz.

Java and the Client-Server, Joe Novosel, Linux Journal, Issue 33, January 1997.

RFC 793: Transmission Control Protocol, J. Postel (ed.), September 1981. Available via HTTP at

http://www.internic.net/rfc/rfc793.txt.

RFC 1337: TIME-WAIT Assassination Hazards in TCP, R. Braden, May 1992. Available via HTTP at

http://www.internic.net/rfc/rfc1337.txt.

Programming UNIX Sockets in C FAQ, Vic Metcalfe, Andrew Gierth and other contributers, February 1997. Available via HTTP at

http://kipper.york.ac.uk/~vic/sock-faq/html/unix-socket-faq.html

作者簡介:
Ivan Griffin is a research postgraduate student in the ECE department at the University of Limerick, Ireland. His interests

include C++/Java, WWW, ATM, the UL Computer Society (http://www.csn.ul.ie/) and, of course, Linux

(http://www.trc.ul.ie/~griffini/linux.html). His e-mail address is ivan.griffin@ul.ie.

Dr. John Nelson is a senior lecturer in Computer Engineering at the University of Limerick. His interests include mobile

communications, intelligent networks, Software Engineering and VLSI design. His e-mail address is john.nelson@ul.ie.

所有的程式都可以在這裡獲得: ftp://ftp.ssc.com/pub/lj/listings/issue46/2333.tgz

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.