《UNIX網絡編程 卷1:套接字聯網API》讀書筆記(一):網絡編程簡介

概述vim

  要編寫經過計算機網絡通訊的程序,首先要肯定這些程序相互通訊所用的協議。大多數網絡是按照劃分紅客戶和服務器來組織的。本章及後續章節的焦點是TCP/IP協議族,也可稱爲網際協議族。下圖爲客戶與服務器使用TCP在同一個以太網中通訊:服務器

圖1.1 客戶與服務器使用TCP在同一個以太網進行通訊網絡

  同一網絡中的客戶機與服務器無需出於同局域網,上圖1.1所示的是同一個局域網。下圖1.2所示的是處於不一樣局域網的客戶機與服務器,這兩個局域網經過使用路由器鏈接到廣域網。socket

 

圖1.2 出於不一樣局域網的客戶主機與服務器主機經過廣域網進行鏈接tcp

  現在討論Unix是常常使用POSIC一詞,它是一種被多數廠商採納的標準。函數

一個簡單的時間獲取客戶程序ui

//    該頭文件包含了大部分網絡程序都須要的許多系統頭文件
#include    "unp.h"

//    main函數定義,其形式參數就是命令行參數
int
main(int argc, char **argv)
{
    int                    sockfd, n;
    char                recvline[MAXLINE + 1];
    struct sockaddr_in    servaddr;

    if (argc != 2)
        err_quit("usage: a.out <IPaddress>");

//    socket函數建立一個網際(AF_INET)字節流(SOCK_STREAM)套接字,該函數返回一個小整數描述符。若是socket函數調用失敗,調用err_sys函數放棄程序運行
    if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        err_sys("socket error");

//    把IP地址和端口號填入一個網際套接字地址結構(一個名爲servadrr的sockdrr_in結構變量),使用bzero把整個結構清零
    bzero(&servaddr, sizeof(servaddr));
//    置地址族爲AF_INET,端口號爲13,IP地址爲第一個命令行參數的值(argv[1])
//    網際套接字結構中IP地址和端口號必須使用特定格式,爲此調用庫函數htons去轉換二進制端口號,又調用inet_pton去把ASCII命令行參數轉換爲合適的格式
    servaddr.sin_family = AF_INET;
    servaddr.sin_port   = htons(13);    /* daytime server */    
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
        err_quit("inet_pton error for %s", argv[1]);

//    connect函數應用於TCP套接字時,將由它的第二個參數指向套接字地址結構指定的服務器創建一個TCP鏈接
//    套接字地址結構的長度必須做爲該函數的第三個參數指定
    if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
        err_sys("connect error");

//    使用read函數讀取服務器的應答,並用標準I/O函數fputs輸出結果
//    把read放在循環以便讀取完數據,當read返回0或者負數時終止循環
    while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
        recvline[n] = 0;    /* null terminate */
        if (fputs(recvline, stdout) == EOF)
            err_sys("fputs error");
    }
    if (n < 0)
        err_sys("read error");

//    終止程序運行
    exit(0);
}

 

  

  咱們從官網www.unpcook.com下載源代碼unpv13e.tar.gz。解壓後進入文件夾。個人是Ubuntu系統。根據文件夾中Readme的提示輸入相應的命令。this

 

redhat@redhat-virtual-machine:~/桌面/unpv13e$ ./configure    

redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd ./lib
redhat@redhat-virtual-machine:~/桌面/unpv13e/lib$ make


redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd ./libfree
redhat@redhat-virtual-machine:~/桌面/unpv13e/libfree$ make
//    若是報錯以下,則須要在當前目錄下打開inet_ntop.c文件
//    將第60行的size_t size修改成socklen_t size 而後保存
//    從新輸入make後不報錯便可

gcc -I../lib -g -O2 -D_REENTRANT -Wall   -c -o inet_ntop.o inet_ntop.c  
inet_ntop.c: In function ‘inet_ntop’:  
inet_ntop.c:60:9: error: argument ‘size’ doesn’t match prototype  
  size_t size;  
         ^  
In file included from inet_ntop.c:27:0:  
/usr/include/arpa/inet.h:64:20: error: prototype declaration  
 extern const char *inet_ntop (int __af, const void *__restrict __cp,  
                    ^  
make: *** [inet_ntop.o] Error 1  


redhat@redhat-virtual-machine:~/桌面/unpv13e/libfree$ cd ../libgai
redhat@redhat-virtual-machine:~/桌面/unpv13e/libgai$ make
//    如下的warning不用理會
/usr/include/arpa/inet.h: In function ‘inet_ntop’:  
inet_ntop.c:152:23: warning: ‘best.len’ may be used uninitialized in this function [-Wmaybe-uninitialized]  
   if (best.base == -1 || cur.len > best.len)  
                       ^  
inet_ntop.c:123:28: note: ‘best.len’ was declared here  
  struct { int base, len; } best, cur;  
                            ^  
gcc -I../lib -g -O2 -D_REENTRANT -Wall   -c -o inet_pton.o inet_pton.c  
ar rv ../libunp.a in_cksum.o inet_ntop.o inet_pton.o  
a - in_cksum.o  
a - inet_ntop.o  
a - inet_pton.o  
ranlib ../libunp.a  

//    用root權限將以上編譯生成的libunp.a 文件複製到/usr/lib目錄中

redhat@redhat-virtual-machine:~/桌面/unpv13e/libgai$ cd ..
redhat@redhat-virtual-machine:~/桌面/unpv13e$ sudo cp libunp.a /usr/lib
[sudo] redhat 的密碼: 

//    打開unp.h文件將其中的#include "../config.h" 改爲 #include "config.h"
redhat@redhat-virtual-machine:~/桌面/unpv13e$ vim lib/unp.h 

//    進入intro目錄編譯客戶端文件並用root權限運行
redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd intro/
redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$  make daytimetcpcli
redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$  sudo ./daytimetcpcli 127.0.0.1
//    錯誤提示沒法鏈接
connect error: Connection refused

//    咱們先打開服務器
redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ make daytimetcpsrv
redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ sudo ./daytimetcpsrv

//    而後再打開另外一個終端,在那裏再運行客戶端便可
redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ sudo ./daytimetcpcli 127.0.0.1
[sudo] redhat 的密碼: 
Mon Dec 25 21:00:36 2017

   

  上面提到了客戶端獲取時間的程序代碼,下面爲服務器端的程序。spa

#include    "unp.h"
#include    <time.h>

int
main(int argc, char **argv)
{
    int                    listenfd, connfd;
    struct sockaddr_in    servaddr;
    char                buff[MAXLINE];
    time_t                ticks;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    //    填寫一個網際套接字地址結構並調用bind函數,把服務器的端口捆綁到所建立的套接字中

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(13);    /* daytime server */

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    
    //    調用listen函數把該套接字轉換成一個監聽套接字,使來自客戶端的鏈接能夠在該套接字上由內核接受
    //    socket、bind、listen這三個函數調用步驟是任何tcp服務器準備監聽描述符的正常步驟
    //    LISTENQ定義在頭文件中,它指定系統內核容許在這個監聽描述副符上排隊的最大客戶鏈接數

    Listen(listenfd, LISTENQ);

    //    服務器進程在accept調用中被投入睡眠,等待客戶的鏈接
    //    TCP鏈接的三次握手完畢時accept返回,其返回值是一個被稱爲已鏈接描述符的新描述符    
    for ( ; ; ) {
        connfd = Accept(listenfd, (SA *) NULL, NULL);

    //    time函數獲取當前時間,ctime函數把時間轉換成直觀可讀的時間格式
        ticks = time(NULL);
    
    //    snprintf函數在這個字符串末尾添加一個回車符和一個換行符
    //    write函數把結果字符串寫給客戶

        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
        Write(connfd, buff, strlen(buff));
    //    終止鏈接
        Close(connfd);
    }
}
相關文章
相關標籤/搜索