Linux下的網(wǎng)絡編程

1.全球IP因特網(wǎng)

1.1數(shù)據(jù)在互聯(lián)網(wǎng)上的傳輸過程

image

1.2 一個網(wǎng)絡程序的軟硬件組織

image

1.3 IP地址結構

  • 一個IP地址就是一個無符號32位整數(shù)。網(wǎng)絡程序將其存放在如下所示結構體中:
struct in_addr{
    uint32_t s_addr;    //大端法表示的IP地址
};
  • 因為網(wǎng)絡字節(jié)序都是大端法表示的获三,所以Unix提供了一組函數(shù)用于在網(wǎng)絡和主機間進行字節(jié)序的轉換:
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);     //返回網(wǎng)絡字節(jié)序

uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);      //返回主機字節(jié)序
  • IP地址通常以點分十進制表示法來表示的寸五。例如127.0.0.1就是0x7f000001梳凛。Unix也提供了一組函數(shù)用于在點分十進制和32位十六進制數(shù)進行轉換的函數(shù):

    #include <arpa/inet.h>
    
    int inet_pton(AF_INET,const char *src, void *dst);
                      //成功返回1梳杏,src非法返回0韧拒,出錯返回-1
    
    const char *inet_ntop(AF_INET淹接,const void *src, char *dst, socklen_t size);
                      //成功返回指向字符串的指針,出錯返回NULL
    

    參數(shù)AF_INET代表IPv4叛溢,而若是128位的IPv6地址蹈集,則是AF_INET6;

    對于inet_ntop函數(shù)雇初,成功時dst返回指向字符串表示的IP地址的前size字節(jié)拢肆。

1.4因特網(wǎng)域名

為了便于人類的記憶,DNS服務器記錄了所有IP地址和主機名的映射靖诗,我們可以通過Linux系統(tǒng)下的nslookup程序來查看域名對應的地址郭怪。

  • 默認的本地主機域名localhost總是映射為回送地址(loopback address )127.0.0.1
linux>    nslookup localhost
Address:127.0.0.1    

命令行下輸入:hostname會得到本機的IP地址。

  • 在通常情況下刊橘,多個域名可以映射到同一個或同一組IP地址鄙才;

1.5 地址轉換的示例

  • 編寫的函數(shù)hex2dd.c會將它的十六進制參數(shù)轉換為點分十進制串:
#include <stdio.h>
#include <sys/inet.h>
#include "csapp.h"

int main(int argc, char **argv)
{
    struct in_addr inaddr;
    uint32_t addr;
    char buf[MAXBUF];
  
    if(argc != 2){
        fprintf(stderr, "usage:%s <hex num>\n",argv[0]);
        exit(0);
    }
    sscanf(argv[1],"%x",&addr);
    inaddr.s_addr = htonl(addr);
    
    if(!inet_ntop(AF_INET,&inaddr,buf,MAXBUF))
        unix_error("inet_ntop");
    printf("%s\n",buf);
    exit(0);
}
  • 編寫的函數(shù)dd2hex.c會將它的點分十進制串參數(shù)轉換為十六進制:

    #include <stdio.h>
    #include <sys/inet.h>
    #include "csapp.h"
    
    int main(int argc, char **argv)
    {
        struct in_addr inaddr;
        int rc;
        
        if(argc != 2){
            fprintf(stderr, "usage:%s <dotted-decimal>\n",argv[0]);
            exit(0);
        }
        rc = inet_pton(AF_INET, argv[1], &inaddr);
        if(rc == 0)
            app_error("inet_pton error:invalid dotted-decimal address.\n");
        elseif(rc < 0)
            unix_error("inet_pton error.\n");
        
        printf("0x%x\n",ntohl(inaddr.s_addr));
        exit(0);
    }
    

2.套接字接口

所謂套接字接口其實是一組函數(shù),它們和I/O函數(shù)結合起來促绵,用以創(chuàng)建網(wǎng)絡應用攒庵。下圖是基于套接口的網(wǎng)絡應用概述:

image

2.1 套接字地址結構

從程序的角度看,套接字就是一個打開文件的描述符败晴。套接字地址存放在類型為sockaddr_in的16字節(jié)結構體中浓冒。

/*IP套接字地址結構,_in是互聯(lián)網(wǎng)的縮寫*/
struct sockaddr_in{
    uint16_t        sin_family; //協(xié)議簇(AF_INET或AF_INET6)
    uint16_t        sin_port;   //端口號
    struct in_addr  sin_addr;   //IP地址
    unsigned char   sin_zero[8];//為了與struct sockaddr邊界對齊而填充的字節(jié)
}尖坤;
  
/*通用套接字地址結構*/
struct sockaddr{
    uint16_t        sa_family;  //協(xié)議簇(AF_INET或AF_INET6)
    char            sa_data[14];//地址數(shù)據(jù)
}稳懒;

為什么會出現(xiàn)兩種套接字地址?

網(wǎng)絡編程函數(shù)connect慢味、bind和accept要求一個指向與協(xié)議有關的套接字地址結構的指針场梆。但套接字接口設計者面臨的問題是如何定義這些函數(shù),使之能夠接受各種類型的套接字地址結構纯路。而當時void *指針還沒有發(fā)明或油,所以解決辦法是設計的套接字函數(shù)都采用通用地址結構作為參數(shù),而所有特定協(xié)議的套接字指針在使用時都強制轉換成通用結構驰唬。

為了簡化代碼顶岸,Steven指導定義:

typedef struct sockaddr SA;

然后,無論何時需要將sockaddr_in結構強制轉換成sockaddr結構時定嗓,我們都使用(SA)蜕琴。

2.2 socket

該函數(shù)創(chuàng)建一個套接字描述符;

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol); //成功返回非負描述符宵溅,錯誤返回-1
  • domain: 使用哪種類型的IP地址,如AF_INET上炎。

  • type :套接字工作類型恃逻,如SOCK_STREAM代表套接字是連接的一個端點雏搂。

  • protocol: 一般取0

最好的方法是利用getaddrinfo函數(shù)自動生成這些參數(shù),以后會說寇损。這里函數(shù)返回的套接字描述符僅是部分打開的凸郑,還不能用于讀和寫。

2.3 connect

客戶端利用該函數(shù)建立與服務器的連接矛市。

#include <sys/socket.h>

int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);
                                                    //成功返回0芙沥,錯誤返回-1
  • clientfd: 客戶端套接字描述符;
  • addr: 通用套接字地址
  • addrlen: 套接字地址長度浊吏,一般取sizeof(sockaddr_in)

該函數(shù)阻塞等待與服務器的連接而昨,若成功就表示套接字描述符現(xiàn)在可以讀寫了,并且得到的連接是由(客戶端IP地址:客戶端分配的臨時端口號)唯一表示找田。最好的方法也是利用getaddrinfo函數(shù)自動生成這些參數(shù)歌憨。

2.4 bind

服務器用它將套接字地址和套接字描述符綁定起來。

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
                                                    //成功返回0墩衙,錯誤返回-1
  • sockfd: 服務器套接字描述符务嫡;
  • addr: 通用套接字地址
  • addrlen: 套接字地址長度,一般取sizeof(sockaddr_in)

最好的方法也是利用getaddrinfo函數(shù)自動生成這些參數(shù)漆改。

2.5 listen

服務器調(diào)用該函數(shù)告訴內(nèi)核心铃,該描述符是被服務器用來監(jiān)聽來自客服端連接請求的。

#include <sys/socket.h>

int listen(int sockfd, int backlog);
                                                    //成功返回0挫剑,錯誤返回-1
  • sockfd: 需要轉化為監(jiān)聽套接字的描述符
  • backlog: 內(nèi)核在開始拒絕連接請求之前于个,隊列中要排隊的未完成的連接請求的數(shù)量。一般設為一個較大的值暮顺,比如1024厅篓。

2.6 accept

服務器調(diào)用該函數(shù)等待來自客服端的連接請求。

#include <sys/socket.h>

int accept(int listenfd, struct sockaddr *addr, int *addrlen);
                                                    //成功返回非負描述符捶码,錯誤返回-1

該函數(shù)等待來自客戶端的連接請求到達監(jiān)聽描述符listenfd羽氮,然后在addr中填寫客服端的套接字地址,并返回一個已連接描述符惫恼,而它可以用來與客戶端通信档押。

監(jiān)聽描述符和已連接描述符的區(qū)別:

  • 監(jiān)聽描述符作為客戶端連接請求的一個端點,通常被創(chuàng)建一次祈纯,存在于服務器的整個生命周期令宿;
  • 已連接描述符是客戶端與服務器之間已經(jīng)建立起來的連接的一個端點,服務器每次接受連接請求時都會創(chuàng)建一次腕窥,它只存在與服務器為一個客戶端服務的過程中粒没。

3.轉換函數(shù)

3.1 getaddrinfo

函數(shù)將主機名(網(wǎng)址或點分十進制IP地址)和服務名(端口號)的字符串轉化成套接字地址結構。它是可重入和協(xié)議無關的簇爆,是代替gethostbyname和getservbyname函數(shù)的替代品癞松。

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

struct addrinfo{
    int         ai_flags;   /*hints的參數(shù)*/
    int         ai_family;  /*套接字函數(shù)的第一個參數(shù)*/
    int         ai_socktype;/*套接字函數(shù)的第二個參數(shù)*/
    int         ai_protocol;/*套接字函數(shù)的第三個參數(shù)*/
    char        *ai_canonname;/*主機的官方名*/
    size_t      ai_addrlen; /*套接字地址長度*/
    struct sockaddr *ai_addr; /*套接字地址指針*/
    struct addrinfo *ai_next; /*指向下一個addrinfo條目*/
};


int getaddrinfo( const char *host, const char *service,
                const struct addrinfo *hints,
                struct addrinfo **result);      //成功返回0爽撒,錯誤返回非零錯誤代碼。

void freeaddrinfo(struct addrinfo *result);     //無返回

const char *gai_strerror(int errcode);          //返回錯誤消息字符串
  • host:可以是域名响蓉,也可以是點分十進制IP地址硕勿。

  • service :可以是服務名(http等),也可是十進制端口號枫甲。如果不想把主機名或端口號轉換成套接字地址源武,就把相應的參數(shù)設置為NULL,但是至少要有一個有效想幻。

  • hints :控制參數(shù)粱栖,它是一個特定的addrinfo指針,通過對該addrinfo結構體ai_family举畸、ai_socktype查排、ai_protocol、ai_flags成員的設置抄沮,可以控制getaddrinfo函數(shù)返回的套接字地址列表的特性跋核。但其余成員必須設置為0。實際中一般先用memset函數(shù)將整個結構清零叛买,然后又選擇的對某些成員賦值砂代。

    • ai_family: 可以設置為AF_INET或AF_INET6。前者表示只返回IPv4的地址率挣,后者返回IPv6的地址刻伊;
    • ai_socktype :對host關聯(lián)的每個地址,該函數(shù)默認最多返回3個addrinfo結構椒功,每個的ai_socktype字段不同:“連接”捶箱、“數(shù)據(jù)報”和“原始套接字”。如果ai_socktype被設置為SOCK_STREAM动漾,則將限制為對每個地址只返回1個連接的addrinfo結構丁屎。
    • ai_flags : 它是一個位掩碼,可以進一步修改默認行為,其取值可以是以下宏的
      • AI_ADDRCONFIG: 要求只有本地主機被配置為IPv4時旱眯,getaddrinfo返回IPv4地址晨川。
      • AI_CANONNAME: 將列表中第一個addrinfo結構的ai_canonname字段指向host的官方名字。
      • AI_NUMERICSERV:強制getaddrinfo的service參數(shù)為端口號删豺。
      • AI_PASSIVE: getaddrinfo默認返回用于客戶端的主動套接字共虑,如果設置了該標志,那么函數(shù)會返回用于服務器的被動套接字呀页,此時host設置為NULL妈拌。得到的套接字地址結構中的地址字段sa_data[14]是通配符地址(wildcard address),表示這個服務器會接受所有發(fā)送到服務器的IP地址的請求赔桌。
  • result : 指向一個包含sockaddr(套接字地址結構)地址的addrinfo結構體鏈表供炎。一般調(diào)用完這個函數(shù)之后渴逻,會遍歷該鏈表疾党,依次嘗試每個套接字地址音诫,直到socket和connect或bind連接成功。

getaddrinfo函數(shù)返回的addrinfo結構中的ai_addr指向的套接字地址可以直接用來傳遞給套接字接口中的函數(shù)(socket雪位、connect竭钝、bind踊兜、listen蛹屿、accept等)暂殖,該特點使得我們編寫的客戶端和服務器能夠獨立于某個特殊版本的IP協(xié)議悍及。下圖展示了getaddrinfo返回的數(shù)據(jù)結構:

image
  • 為了避免內(nèi)存泄漏芬萍,一般在調(diào)用完getaddrinfo函數(shù)之后蹄梢,會調(diào)用freeaddrinfo函數(shù)釋放該鏈表傍睹;
  • 如果getaddrinfo遇到錯誤搀菩,應用程序可以調(diào)用gai_strerror函數(shù)將錯誤代碼轉換成字符串螃成。
  • 當getaddrinfo創(chuàng)建列表中的addrinfo結構時旦签,會填寫除了ai_flags的每個字段。

3.2 getnameinfo

函數(shù)將一個套接字地址結構轉化成相應的主機名(網(wǎng)址或點分十進制IP地址)和服務名(端口號)字符串寸宏,并將它們復制到host和service緩沖區(qū)宁炫。它也是可重入和協(xié)議無關的,是代替gethostbyaddr和getservbyport函數(shù)的替代品氮凝。

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getnameinfo( const struct sockaddr *sa, socklen_t salen,
                char *host, size_t hostlen,
                char *service, size_t servlen, int flags);
                //成功返回0羔巢,錯誤返回非零錯誤代碼。
  • sa :指向大小為salen字節(jié)的套接字地址結構罩阵;

  • host :指向大小為hostlen字節(jié)的緩沖區(qū)竿秆;如果不想要主機名,可以設置為NULL稿壁,hostlen設置為0幽钢。

  • service :指向大小為servlen字節(jié)的緩沖區(qū);如果不想要服務名常摧,也可以設置為NULL搅吁,servlen設置為0,但二者不能同時都設為NULL落午。

  • flags :它是一個位掩碼谎懦,可以進一步修改默認行為,其取值可以是以下宏的或。

    • NI_NUMERICHOST: getnameinfo函數(shù)默認返回域名溃斋,若設置此項則返回數(shù)字地址界拦。
    • NI_NUMERICSERV: 默認返回服務名(如果可能的話),若設置此項則返回端口號梗劫。

3.3 域名翻譯程序

利用上兩節(jié)所學的兩個函數(shù)享甸,編寫程序hostinfo.c截碴,當輸入一個域名時,會得到相應的點分十進制IPv4地址蛉威。

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int main(int argc, char **argv) 
{
    struct addrinfo *p, *listp, hints;
    char buf[MAXLINE];
    int rc, flags;

    if (argc != 2) {
    fprintf(stderr, "usage: %s <domain name>\n", argv[0]);
    exit(0);
    }

    /* Get a list of addrinfo records */
    memset(&hints, 0, sizeof(struct addrinfo));                         
    hints.ai_family = AF_INET;       /* IPv4 only */        //line:netp:hostinfo:family
    hints.ai_socktype = SOCK_STREAM; /* Connections only */ //line:netp:hostinfo:socktype
    if ((rc = getaddrinfo(argv[1], NULL, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(rc));
        exit(1);
    }

    /* Walk the list and display each IP address */
    flags = NI_NUMERICHOST; /* Display address string instead of domain name */
    for (p = listp; p; p = p->ai_next) {
        Getnameinfo(p->ai_addr, p->ai_addrlen, buf, MAXLINE, NULL, 0, flags);
        printf("%s\n", buf);
    } 

    /* Clean up */
    Freeaddrinfo(listp);

    exit(0);
}

首先日丹,初始化hints結構,使getaddrinfo返回我們想要的地址蚯嫌。我們想得到用作“連接”的IPv4地址哲虾,且只想得到域名,不要服務名束凑。

然后,遍歷addrinfo結構鏈表栅盲,用getnameinfo將每個套接字地址轉換成IPv4地址字符串汪诉。

最后,用freeaddrinfo函數(shù)釋放鏈表谈秫。

運行程序扒寄,我們會看到twitter.com映射到4個IP地址。

linux> ./hostinfo twitter.com
199.16.156.102
199.16.156.230
199.16.156.6
199.16.156.70

4. 套接字接口的輔助函數(shù)

套接字接口函數(shù)和轉換函數(shù)看上去有些可怕孝常,相當復雜凌亂旗们。這一節(jié)會介紹一些包裝函數(shù),它們將會大大簡化客戶端和服務器通信程序的編寫构灸。

4.1 open_clientfd

客戶端可以直接利用該函數(shù)建立與服務器的連接。

#include "csapp.h"

int open_clientfd(char * hostname, char *port);
                //成功返回套接字描述符喜颁,出錯返回-1

下面是它的源代碼,它是可重入和協(xié)議無關的半开。

int open_clientfd(char *hostname, char *port) {
    int clientfd, rc;
    struct addrinfo hints, *listp, *p;

    /* Get a list of potential server addresses */
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;  /* Open a connection */
    hints.ai_flags = AI_NUMERICSERV;  /* ... using a numeric port arg. */
    hints.ai_flags |= AI_ADDRCONFIG;  /* Recommended for connections */
    if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc));
        return -2;
    }
  
    /* Walk the list for one that we can successfully connect to */
    for (p = listp; p; p = p->ai_next) {
        /* Create a socket descriptor */
        if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 
            continue; /* Socket failed, try the next */

        /* Connect to the server */
        if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) 
            break; /* Success */
        if (close(clientfd) < 0) { /* Connect failed, try another */  //line:netp:openclientfd:closefd
            fprintf(stderr, "open_clientfd: close failed: %s\n", strerror(errno));
            return -1;
        } 
    } 

    /* Clean up */
    freeaddrinfo(listp);
    if (!p) /* All connects failed */
        return -1;
    else    /* The last connect succeeded */
        return clientfd;
}

假設服務器運行在主機hostname上寂拆,并在端口port上監(jiān)聽連接請求。

首先纠永,調(diào)用getaddrinfo,返回一個addrinfo結構體鏈表尝江。遍歷該列表依次嘗試列表中的每個條目中的ai_addr指向的套接字地址,直到調(diào)用socket和connect成功。如果一個失敗苍日,則在下一次嘗試前關閉掉這個套接字描述符。如果成功建立連接相恃,就釋放列表內(nèi)存,并把套接字描述符返回給客戶端嫌佑,客戶端就可以利用它與所有Unix I/O函數(shù)與服務器通信了豆茫。

4.2 open_listenfd

服務器可以直接利用該函數(shù)創(chuàng)建一個監(jiān)聽描述符侨歉,準備好建立與客戶端的連接屋摇。

#include "csapp.h"

int open_listenfd(char *port);
                //成功返回套接字描述符,出錯返回-1

open_listenfd函數(shù)返回一個打開的監(jiān)聽描述符幽邓,且已經(jīng)準備好在端口port上接受客戶端的連接請求炮温。下面是它的源代碼:

int open_listenfd(char *port) 
{
    struct addrinfo hints, *listp, *p;
    int listenfd, rc, optval=1;

    /* Get a list of potential server addresses */
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;             /* Accept connections */
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */
    hints.ai_flags |= AI_NUMERICSERV;            /* ... using port number */
    if ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (port %s): %s\n", port, gai_strerror(rc));
        return -2;
    }

    /* Walk the list for one that we can bind to */
    for (p = listp; p; p = p->ai_next) {
        /* Create a socket descriptor */
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 
            continue;  /* Socket failed, try the next */

        /* Eliminates "Address already in use" error from bind */
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,    //line:netp:csapp:setsockopt
                   (const void *)&optval , sizeof(int));

        /* Bind the descriptor to the address */
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
            break; /* Success */
        if (close(listenfd) < 0) { /* Bind failed, try the next */
            fprintf(stderr, "open_listenfd close failed: %s\n", strerror(errno));
            return -1;
        }
    }


    /* Clean up */
    freeaddrinfo(listp);
    if (!p) /* No address worked */
        return -1;

    /* Make it a listening socket ready to accept connection requests */
    if (listen(listenfd, LISTENQ) < 0) {
        close(listenfd);
    return -1;
    }
    return listenfd;
}

首先,調(diào)用getaddrinfo牵舵,返回一個addrinfo結構體鏈表柒啤。遍歷該列表依次嘗試列表中的每個條目中的ai_addr指向的套接字地址,直到調(diào)用socket和bind成功畸颅。如果失敗担巩,則在下一次嘗試前關閉掉這個套接字描述符。如果成功綁定没炒,就釋放列表內(nèi)存涛癌,并調(diào)用listen函數(shù)將該套接字描述符轉換為監(jiān)聽描述符返回給調(diào)用者,服務器就可以利用它與所有Unix I/O函數(shù)響應客戶端了送火。

  • 我們使用了setsockopt函數(shù)來配置服務器拳话,使得服務器能夠被終止、重啟和立即接受連接种吸。一個重啟的服務器默認將在30秒內(nèi)拒絕客戶端的連接請求弃衍。關于setsockopt的使用很復雜,將會專門寫篇文章來講解他的使用方法坚俗。
  • 我們使用了AI_PASSIVE標志并將host參數(shù)設置為NULL镜盯,這樣每個套接字地址字段都會被設置為通配符地址,表示服務器接受發(fā)送到本機所有IP地址的請求猖败。

4.3 編寫客戶端echo和服務器程序

4.3.1客戶端程序echoclient

客戶端首先與服務器建立連接速缆,之后進入循環(huán)等待從標準輸入讀取文本行發(fā)送給服務器。再等待從服務器取回回送的行辙浑,并輸出結果到標準輸出激涤。

#include "csapp.h"

int main(int argc, char **argv) 
{
    int clientfd;
    char *host, *port, buf[MAXLINE];
    rio_t rio;

    if (argc != 3) {
    fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
    exit(0);
    }
    host = argv[1];
    port = argv[2];

    clientfd = Open_clientfd(host, port);
    Rio_readinitb(&rio, clientfd);

    while (Fgets(buf, MAXLINE, stdin) != NULL) {
    Rio_writen(clientfd, buf, strlen(buf));
    Rio_readlineb(&rio, buf, MAXLINE);
    Fputs(buf, stdout);
    }
    Close(clientfd); //line:netp:echoclient:close
    exit(0);
}
  • 當fgets遇到EOF或鍵盤上鍵入Ctrl + D,或重定向到標準輸入的文件中用盡了所有文本行時,循環(huán)就終止倦踢。
  • 循環(huán)終止后,客戶端關閉描述符犁嗅。此時會導致發(fā)送一個EOF到服務器褂微。

4.3.2 服務器程序echoserver

服務器首先打開監(jiān)聽描述符园爷,進入循環(huán)等待與客戶端建立連接童社,連接之后首先輸出客戶端的域名和IP扰楼,之后調(diào)用echo函數(shù)為其服務。echo函數(shù)返回后關閉已連接的描述符项栏,連接終止沼沈。

#include "csapp.h"

void echo(int connfd);

int main(int argc, char **argv) 
{
    int listenfd, connfd;
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;  /* Enough space for any address */  //line:netp:echoserveri:sockaddrstorage
    char client_hostname[MAXLINE], client_port[MAXLINE];

    if (argc != 2) {
    fprintf(stderr, "usage: %s <port>\n", argv[0]);
    exit(0);
    }

    listenfd = Open_listenfd(argv[1]);
    while (1) {
    clientlen = sizeof(struct sockaddr_storage); 
    connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
    Getnameinfo((SA *) &clientaddr, clientlen, client_hostname, MAXLINE, 
                 client_port, MAXLINE, 0);
    printf("Connected to (%s, %s)\n", client_hostname, client_port);
    echo(connfd);
    Close(connfd);
    }
    exit(0);
}


void echo(int connfd) 
{
    size_t n; 
    char buf[MAXLINE]; 
    rio_t rio;

    Rio_readinitb(&rio, connfd);
    while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) { //line:netp:echo:eof
    printf("server received %d bytes\n", (int)n);
    Rio_writen(connfd, buf, n);
    }
}
  • clientaddr是一個套接字地址結構庆冕,被傳遞給accept函數(shù),在accept返回前會將連接到的客戶端套接字地址填入到clientaddr拷姿。
  • 將clientaddr聲明為struct sockaddr_storage是因為該結構足夠大能裝下任何類型的套接字地址旱函,以保持代碼的協(xié)議無關性棒妨。
  • 我們這里建立的echo服務器一次只能處理一個客戶端連接。需要不停的在多個客戶端間迭代服務拘泞,也稱之為迭代服務器陪腌。
  • echo函數(shù)反復讀寫文本行烟瞧,直到rio_readlineb函數(shù)遇到EOF参滴。

獲取更多知識卵洗,請點擊關注:
嵌入式Linux&ARM
CSDN博客
簡書博客
知乎專欄


最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末过蹂,一起剝皮案震驚了整個濱河市酷勺,隨后出現(xiàn)的幾起案子脆诉,更是在濱河造成了極大的恐慌击胜,老刑警劉巖偶摔,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辰斋,死亡現(xiàn)場離奇詭異宫仗,居然都是意外死亡旁仿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門梭姓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來誉尖,“玉大人铡恕,你說我怎么就攤上這事探熔『娲欤” “怎么了饮六?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵卤橄,是天一觀的道長窟扑。 經(jīng)常有香客問我,道長橘霎,這世上最難降的妖魔是什么殖属? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮忱辅,結果婚禮上墙懂,老公的妹妹穿的比我還像新娘损搬。我一直安慰自己柜与,他們只是感情好弄匕,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布迁匠。 她就那樣靜靜地躺著驹溃,像睡著了一般豌鹤。 火紅的嫁衣襯著肌膚如雪布疙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天截型,我揣著相機與錄音菠劝,去河邊找鬼睁搭。 笑死园骆,一個胖子當著我的面吹牛锌唾,可吹牛的內(nèi)容都是我干的晌涕。 我是一名探鬼主播痛悯,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼惧财,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了垮衷?” 一聲冷哼從身側響起搀突,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤甸昏,失蹤者是張志新(化名)和其女友劉穎轩勘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體花墩,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡冰蘑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年祠肥,在試婚紗的時候發(fā)現(xiàn)自己被綠了仇箱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剂桥。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡属提,死狀恐怖冤议,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情堪滨,我是刑警寧澤椿猎,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站按灶,受9級特大地震影響鸯旁,放射性物質發(fā)生泄漏铺罢。R本人自食惡果不足惜残炮,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一泉瞻、第九天 我趴在偏房一處隱蔽的房頂上張望袖牙。 院中可真熱鬧舅锄,春花似錦皇忿、人聲如沸鳍烁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卫键。三九已至莉炉,卻和暖如春钓账,著一層夾襖步出監(jiān)牢的瞬間梆暮,已是汗流浹背啦粹。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工唠椭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贪嫂,地道東北人力崇。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像敌厘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子饱狂,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容