天天看點

2017-2018-1 20155302 第十四周作業

2017-2018-1 20155302 第十四周作業

重新精學第十一章網絡程式設計相關知識

  • 第十一章網絡程式設計因為之前在劉念老師的課上有所涉及有所講解是以婁老師并沒有着重講這塊知識,但我個人認為此章知識非常重要,是我們學習WEB程式設計和資訊安全程式設計的基礎,而且這章知識自問之前學習的并不好,并不牢靠,于是借着此次機會更加深入的學習重溫一下網絡程式設計方面的知識,好為今後的網絡程式設計道路做好鋪墊,學習一章内容兩門課都能受益,何樂而不為呢?

首先回答幾個問題,這些問題也是之前概念混淆所遺留下來的,此次重新學習解決:

1.網絡程式設計是什麼?

  • 網絡程式設計的本質是兩個裝置之間的資料交換,當然,在計算機網絡中,裝置主要指計算機。資料傳遞本身沒有多大的難度,不就是把一個裝置中的資料發送給兩外一個裝置,然後接受另外一個裝置回報的資料。
  • 現在的網絡程式設計基本上都是基于請求/響應方式的,也就是一個裝置發送請求資料給另外一個,然後接收另一個裝置的回報。
  • 在網絡程式設計中,發起連接配接程式,也就是發送第一次請求的程式,被稱作用戶端(Client),等待其他程式連接配接的程式被稱作伺服器(Server)。用戶端程式可以在需要的時候啟動,而伺服器為了能夠時刻相應連接配接,則需要一直啟動。例如以打電話為例,首先撥号的人類似于用戶端,接聽電話的人必須保持電話暢通類似于伺服器。
  • 連接配接一旦建立以後,就用戶端和伺服器端就可以進行資料傳遞了,而且兩者的身份是等價的。
  • 在一些程式中,程式既有用戶端功能也有伺服器端功能,最常見的軟體就是BT、emule這類軟體了。

2.之前課程提到網絡程式設計就是使用socket,于是乎很久以來都有一種錯覺網絡程式設計就是socket,或者說我們就會socket,那網絡程式設計必須使用socket嗎?除了socket我們還能用些什麼呢?

  • 常見的socket程式設計可以了解為作業系統向程式員提供的TCP/IP協定接口。最近了解了NVMe和RDMA,發現TCP/IP成為了這種高速存儲場景下的瓶頸。這樣場景下,都恨不得直接到硬體。是以,我們可以自己定義并實作一套協定,适用性不談,底層能力和對網絡知識的掌握肯定提升一大截。
  • tcp 協定是一個權衡了各種網絡資源、主機資源、可靠性、穩定性等等因素的結果,socket 隻是 tcp 實作所提供的 API。我們也可以通過 socket 使用 udp。是以,不使用 tcp 而使用 udp,那我們的程式就需要實作 tcp 的功能(假設我們需要),如資料包重排序、擁塞控制、流量控制等等。如果我們連 udp 都不想用,那可以自己封 IP 包,做包切分等等,換句話說我們在實作整個網絡協定棧裡面挺關鍵的一部分。也就是說越往底層我們要做的事情就越多。

教材内容精學及回顧

11.1用戶端-伺服器程式設計模型

用戶端-伺服器模型中的基本操作是事務,它由四步組成:

用戶端向伺服器發送一個請求,發起一個事務;

伺服器收到請求後,解釋之,并操作它的資源;

伺服器給用戶端發送一個響應,例如将請求的檔案發送回用戶端;

用戶端收到響應并處理它,例如Web浏覽器在螢幕上顯示網頁。

認識到用戶端和伺服器是程序而不是具體的機器或主機是重要的。

本節擴充學習

Q:根據事務初步編寫用戶端及伺服器的僞代碼

A:

伺服器:

循環:
	擷取緩沖區内容(本地);
	發送消息;
	接收消息;
	列印出來(本地);
           

用戶端:

循環:
	接收消息;
	列印消息(本地);
	擷取緩沖區内容(本地);
	發送出去;
           

11.2 網絡

網絡主機的硬體組成:

2017-2018-1 20155302 第十四周作業

以太網段:

2017-2018-1 20155302 第十四周作業

橋接以太網:

2017-2018-1 20155302 第十四周作業

區域網路的概念視圖:

2017-2018-1 20155302 第十四周作業

通過路由器連接配接起來多個不相容的區域網路:

2017-2018-1 20155302 第十四周作業

網際網路絡的的至關重要的特性是:它能夠采取完全不同的和不相容技術的各種區域網路和廣域網組成。每台主機和其他每台主機都是實體相連的。

解決讓某台源主機跨過所有不相容的網絡發送資料到另一台目的主機的解決辦法是一層運作在每台主機和路由器上的協定軟體,這個軟體實作一種協定,這種協定必須提供兩種基本能力:

1,命名方法:internet協定通過定義一種一直的主機位址格式,消除了這些差異。每台主機會被配置設定至少一種這種internet位址,這個位址唯一的辨別了它。

2,傳送機制:互連網絡協定定義一種把資料位捆紮成不連續的組塊chunk--也就是包---的統一方式,消除了這種差異。一個包由標頭和有效載荷組成。

Q1:學習了本節雖然學到了很多網絡知識但還是腦海中冒出了幾個疑問,比如交換機如何知道将幀轉發到哪個端口?

A1:使用MAC位址表就可以知道。交換機之是以能夠直接對目的節點發送資料包,而不是像集線器一樣以廣播方式對所有節點發送資料包,最關鍵的技術就是交換機可以識别連在網絡上的節點的網卡MAC位址,并把它們放到一個叫做MAC位址表的地方。這個MAC位址表存放于交換機的緩存中,并記住這些位址,這樣一來當需要向目的位址發送資料時,交換機就可在MAC位址表中查找這個MAC位址的節點位置,然後直接向這個位置的節點發送。

Q2:我們平時使用網絡的時候經常會遇到丢包現象,那麼丢包問題是什麼呢?

A2:什麼是丢包:資料包的傳輸,不可能百分之百的能夠完成,因為種種原因,總會有一定的損失。碰到這種情況,INTERNET會自動的讓雙方的電腦根據協定來補包和重傳該包。如果網絡線路好、速度快,包的損失會非常小,補包和重傳的工作也相對較易完成,是以可以近似的将所傳輸的資料看做是無損的。但是,如果網絡線路較差,資料的損失量就會非常大,補包工作又不是百分之百完成的。這種情況下,資料的傳輸就會出現空洞,造成丢包。

11.3全球IP網際網路

網際網路應用程式的軟硬體組織:

2017-2018-1 20155302 第十四周作業

從程式員的角度,可以把網際網路看做一個世界範圍的主機集合,它滿足以下特性:

  • 主機集合被映射成一組32位的IP位址。
  • 這組IP位址被映射成一組叫做網際網路域名的辨別符。
  • 網際網路主機上的程序能夠通過連接配接和任何其他網際網路主機上的程序通信。

IP位址結構:

/* IP address structure */
struct in_addr {
	uint32_t s_addr; /* Address in network byte order (big-endian) */
};
           

網絡和主機位元組順序間實作轉換的函數:

2017-2018-1 20155302 第十四周作業

應用程式使用inet_pton和inet_ntop函數來實作IP位址和點分十進制之間的轉換:

2017-2018-1 20155302 第十四周作業

網際網路域名層次結構的一部分:

2017-2018-1 20155302 第十四周作業

一個連接配接是由它兩端的套接字位址唯一确定的,這對套接字位址叫做套接字對(socket pair)。由下列代碼來表示:

(cliaddr:cliport,servaddr:servport)

當用戶端發起一個連接配接請求時,用戶端套接字位址中的端口是由核心自動配置設定的,稱為臨時端口。然而,伺服器套接字位址中的端口通常是某個知名的端口,是和這個服務相對應的。例如,Web伺服器通常使用端口80,Email伺服器通常使用端口25。

2017-2018-1 20155302 第十四周作業

Q:編寫一個程式,将它的十六進制轉換為點分十進制串并列印出來。(把點分十進制轉換為十六進制呢?)

A:實驗代碼:

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

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/*将十六進制參數轉換成點分十進制 
例如:0x8002c2f2  ->128.2.194.242
*/
int my_htonl(char *argv)
{
    struct in_addr inaddr;//網絡位元組序
    unsigned int addr;//點分十進制

    sscanf(argv, "%x", &addr);
    inaddr.s_addr = htonl(addr);
    printf("%s/n", inet_ntoa(inaddr));

    return 0;
}


/*将點分十進制參數轉換成十六進制
例如: 128.2.194.242->0x8002c2f2
*/
int my_ntohl(char * argv)
{
    struct in_addr inaddr;//網絡位元組序
    unsigned int addr;//點分十進制

    if(inet_aton(argv, &inaddr) != 0){
        addr = ntohl(inaddr.s_addr);
        printf("0x%x/n", addr);
    }
    return 0;
}



int main( )
{
    char * test_arry1  = "0x8002c2f2";
    char * test_arry2  = "128.2.194.242";

    my_htonl(test_arry1 );
    my_ntohl(test_arry2);
    return 0;
}
           

書中标準答案:

2017-2018-1 20155302 第十四周作業

11.4套接字接口

套接字接口:

套接字程式設計函數詳細學習及了解:

  • socket() 函數
    #include <sys/types.h>          /* See NOTES */
             #include <sys/socket.h>
             int socket(int domain, int type, int protocol);
               
family:協定族

type:套接字類型

protocol:協定類型

SOCKET socket( int af, // 指定位址族 AF_INET int type, // 指定套接字類型(SOCK_STREAM|SOCK_DGRAM) int protocol// 指定協定(TCP/IP | UDP)或(0)時自動選擇 );

  • connect() 函數
    #include <sys/types.h>          /* See NOTES */
             #include <sys/socket.h>
             int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
               
sockfd:套接字描述符,由socket()函數傳回的。

addr:指向對端套接字位址結構的指針。

addrlen:對端套接字位址結構的大小

int connect( SOCKET s, const struct sockaddr FAR *name,//設定對方伺服器資訊(IP和端口) int namelen //将要連接配接伺服器端口的資訊長度 )

  • bind() 函數
    #include <sys/types.h>          /* See NOTES */
             #include <sys/socket.h>
             int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
               
sockfd: 套接字描述符,由前面的socket()函數成功時傳回

myaddr: 指向本地協定位址結構對象的指針

addrlen: 本地協定位址結構的大小。

int bind( SOCKET s, // 結構體對象其名稱填寫在S處 const struct sockaddr FAR *name, // 指定該套接字的位址指針。 int namelen // 指定該 名字元串的長度 );

  • listen函數
    #include <sys/types.h>          /* See NOTES */
             #include <sys/socket.h>
             int listen(int sockfd, int backlog);
               

int listen( SOCKET s, //套接字 int backlog );//設定監聽等待隊列的最大數目

listen()函數僅有TCP伺服器調用,主要有兩個作用:
  1. 将一個未連接配接的套接字轉換為被動套接字,訓示核心應該接收指向該套接字的連接配接請求。(socket()函數建立套接字時,預設設為主動套接字)
  2. 指定核心應該為相應的套接字排隊的最大連接配接個數。
  • accept() 函數
    #include <sys/types.h>          /* See NOTES */
             #include <sys/socket.h>
             int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
               
sockfd: 核心建立的新的套接字描述符,用于描述與傳回的用戶端之間的連接配接

addr:對端程序(用戶端)的協定位址

addrlen: 對端協定位址結構的大小。

getaddrinfo函數詳解:

用處:getsockaddr這個函數的功能是将主機名映射成主機的位址,是個新的接口,以取代以前的gethostbyname這個函數,因為後者不能處理ipv6的位址,并且以被标記為廢棄。

說明:包含頭檔案

#include<netdb.h>

函數原型:

int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );

參數說明:

hostname:一個主機名或者位址串(IPv4的點分十進制串或者IPv6的16進制串)
service:服務名可以是十進制的端口号,也可以是已定義的服務名稱,如ftp、http等
hints:可以是一個空指針,也可以是一個指向某個addrinfo結構體的指針,調用者在這個結構中填入關于期望傳回的資訊類型的暗示。舉例來說:如果指定的服務既支援TCP也支援UDP,那麼調用者可以把hints結構中的ai_socktype成員設定成SOCK_DGRAM使得傳回的僅僅是适用于資料報套接口的資訊。
result:本函數通過result指針參數傳回一個指向addrinfo結構體連結清單的指針。
傳回值:0——成功,非0——出錯。
           
getnameinfo函數詳解:

用處:以一個套接口位址為參數,傳回一個描述主機的字元串和一個描述服務的字元串。

說明:

函數原型:

int getnameinfo (const struct sockaddr *sockaddr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) ;

成功為0,出錯為非0。

參數說明:

NI_MAXHOST:傳回的主機字元串的最大長度
NI_MAXSERV:傳回的服務字元串的最大長度
           

Q:自主設計一個socket套接字聊天程式并顯示位元組數。

A:

僞代碼:

server.c:

do{
    gets(sendBuf);
    send();
    recv();
    puts(recvBuf);
}while(傳回值不為-1);
           

client.c:

do{
    recv();
    puts(recvBuf);
    gets(sendBuf);
    send();
}while(傳回值不為-1);
           

實驗代碼:

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

#define BUFLEN 10

int main(int argc, char **argv)
{
    int sockfd, newfd;
    struct sockaddr_in s_addr, c_addr;
    char buf[BUFLEN];
    socklen_t len;
    unsigned int port, listnum;
    
    /*建立socket*/
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
        perror("socket");
        exit(errno);
    }else
        printf("socket create success!\n");
    /*設定伺服器端口*/    
    if(argv[2])
        port = atoi(argv[2]);
    else
        port = 4567;
    /*設定偵聽隊列長度*/
    if(argv[3])
        listnum = atoi(argv[3]);
    else
        listnum = 3;
    /*設定伺服器ip*/
    bzero(&s_addr, sizeof(s_addr));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(port);
    if(argv[1])
        s_addr.sin_addr.s_addr = inet_addr(argv[1]);
    else
        s_addr.sin_addr.s_addr = INADDR_ANY;
    /*把位址和端口幫定到套接字上*/
    if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){
        perror("bind");
        exit(errno);
    }else
        printf("bind success!\n");
    /*偵聽本地端口*/
    if(listen(sockfd,listnum) == -1){
        perror("listen");
        exit(errno);    
    }else
        printf("the server is listening!\n");
    while(1){
        printf("*****************聊天開始***************\n");
        len = sizeof(struct sockaddr);
        if((newfd = accept(sockfd,(struct sockaddr*) &c_addr, &len)) == -1){
            perror("accept");        
            exit(errno);
        }else
            printf("正在與您聊天的用戶端是:%s: %d\n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
        while(1){
        _retry:
            /******發送消息*******/
            bzero(buf,BUFLEN);
            printf("請輸入發送給對方的消息:");
            /*fgets函數:從流中讀取BUFLEN-1個字元*/
            fgets(buf,BUFLEN,stdin);
            /*列印發送的消息*/
            //fputs(buf,stdout);
            if(!strncasecmp(buf,"quit",4)){
                printf("server 請求終止聊天!\n");
                break;
            }
            /*如果輸入的字元串隻有"\n",即回車,那麼請重新輸入*/
            if(!strncmp(buf,"\n",1)){
                printf("輸入的字元隻有回車,這個是不正确的!!!\n");
                goto _retry;
            }    
            /*如果buf中含有'\n',那麼要用strlen(buf)-1,去掉'\n'*/            
            if(strchr(buf,'\n'))
                len = send(newfd,buf,strlen(buf)-1,0);
            /*如果buf中沒有'\n',則用buf的真正長度strlen(buf)*/    
            else
                len = send(newfd,buf,strlen(buf),0);
            if(len > 0)
                printf("消息發送成功,本次共發送的位元組數是:%d\n",len);            
            else{
                printf("消息發送失敗!\n");
                break;            
            }
            /******接收消息*******/
            bzero(buf,BUFLEN);
            len = recv(newfd,buf,BUFLEN,0);
            if(len > 0)
                printf("用戶端發來的資訊是:%s,共有位元組數是: %d\n",buf,len);
            else{
                if(len < 0 )
                    printf("接受消息失敗!\n");
                else
                    printf("用戶端退出了,聊天終止!\n");
                break;        
            }
        }
        /*關閉聊天的套接字*/
        close(newfd);
        /*是否退出伺服器*/
        printf("伺服器是否退出程式:y->是;n->否? ");
        bzero(buf, BUFLEN);
        fgets(buf,BUFLEN, stdin);
        if(!strncasecmp(buf,"y",1)){
            printf("server 退出!\n");
            break;
        }
    }
    /*關閉伺服器的套接字*/
    close(sockfd);
    return 0;
}
           

client.c:

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

#define BUFLEN 10

int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in s_addr;
    socklen_t len;
    unsigned int port;
    char buf[BUFLEN];    
    
    /*建立socket*/
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
        perror("socket");
        exit(errno);
    }else
        printf("socket create success!\n");

    /*設定伺服器端口*/    
    if(argv[2])
        port = atoi(argv[2]);
    else
        port = 4567;
    /*設定伺服器ip*/
    bzero(&s_addr, sizeof(s_addr));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(port);
    if (inet_aton(argv[1], (struct in_addr *)&s_addr.sin_addr.s_addr) == 0) {
        perror(argv[1]);
        exit(errno);
    }
    /*開始連接配接伺服器*/    
    if(connect(sockfd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr)) == -1){
        perror("connect");
        exit(errno);
    }else
        printf("conncet success!\n");
    
    while(1){
        /******接收消息*******/
        bzero(buf,BUFLEN);
        len = recv(sockfd,buf,BUFLEN,0);
        if(len > 0)
            printf("伺服器發來的消息是:%s,共有位元組數是: %d\n",buf,len);
        else{
            if(len < 0 )
                printf("接受消息失敗!\n");
            else
                printf("伺服器退出了,聊天終止!\n");
            break;    
        }
    _retry:    
        /******發送消息*******/    
        bzero(buf,BUFLEN);
        printf("請輸入發送給對方的消息:");
        /*fgets函數:從流中讀取BUFLEN-1個字元*/
        fgets(buf,BUFLEN,stdin);
        /*列印發送的消息*/
        //fputs(buf,stdout);
        if(!strncasecmp(buf,"quit",4)){
            printf("client 請求終止聊天!\n");
            break;
        }
        /*如果輸入的字元串隻有"\n",即回車,那麼請重新輸入*/
        if(!strncmp(buf,"\n",1)){
            printf("輸入的字元隻有回車,這個是不正确的!!!\n");
            goto _retry;
        }
        /*如果buf中含有'\n',那麼要用strlen(buf)-1,去掉'\n'*/    
        if(strchr(buf,'\n'))
            len = send(sockfd,buf,strlen(buf)-1,0);
        /*如果buf中沒有'\n',則用buf的真正長度strlen(buf)*/    
        else
            len = send(sockfd,buf,strlen(buf),0);
        if(len > 0)
            printf("消息發送成功,本次共發送的位元組數是:%d\n",len);            
        else{
            printf("消息發送失敗!\n");
            break;            
        }
    }
    /*關閉連接配接*/
    close(sockfd);

    return 0;
}
           

測試截圖:

2017-2018-1 20155302 第十四周作業

11.5/11.6Web伺服器

HTTP請求支援許多不同的方法,包括GET/POST/OPTIONS/HEAD/PUT/DELETE/TRACE。其中GET最常用。

GET方法指導伺服器生成和傳回URI(統一資源辨別符,是URL的字尾,包括檔案名和可選的參數)。

請求報頭為伺服器提供了額外的資訊,例如浏覽器的商标名等。

HTTP響應和請求是類似的,它包括:一個響應行,後面跟随0個或多個響應報頭,然後是終止報頭的空行,再跟随一個響應主體。

Q1:家庭作業11.9

A1:

serve_static中的存儲器映射語句改為:

srcp = (char*)malloc(sizeof(char)*filesize);
rio_readn(srcfd, srcp, filesize);
close(srcfd);
rio_writen(fd, srcp, filesize);
free(srcp); ````

Q2:家庭作業11.8

A2:

在main函數之前加入代碼:

	int chdEnded ;
	#include <signal.h>
	void child_signal(int sig)
	{
	    pid_t pid;
	    while((pid = waitpid(-1, NULL, WNOHANG)) > 0)
	    ;
	    chdEnded = 1; 
	} 
在main函數中添加語句 ````signal(SIGCHILD, child_handle);````
每次accept之前,讓````chdEnded = 0;````
并且在doit()中的````serve_dynamic````之後添加:
````while(!chdEnded) pause();//or do sth````
删掉serve_dynamic裡的````wait(NULL);````

###結對學習成果

我和我的結對成員秦詩茂認真的重新溫習了本章,就套接字程式設計函數中的參數以及所用到的函數含義做了深入的探讨學習,并且改進了socket聊天程式,從原本隻能對話更新成了可以統計位元組數,秦同學還給我講了如何傳送檔案的代碼改進方式,我虛心向他學習了。并且找了些網絡程式設計相關的題目一起做。

結對照片:![](http://images2017.cnblogs.com/blog/1071516/201712/1071516-20171219210730521-239010098.jpg)

###本周代碼量截圖

![](http://images2017.cnblogs.com/blog/1071516/201712/1071516-20171223194231787-1645826663.png)

###碼雲連結

[代碼連結](https://gitee.com/bestiisjava2017/yxc20155302)