1. 程式人生 > 其它 >第13章學習筆記(20191213蘭毅達)

第13章學習筆記(20191213蘭毅達)

第13章學習筆記

一、概述

本章論述了TCP/IP和網路程式設計,分為兩個部分。第一部分論述了TCP/IP協議及其應用,具體包括TCP/IP棧、IP地址、主機名、DNS、IP資料包和路由器;介紹了TCP/P網路中的UDP和TCP協議、埠號和資料流;闡述了伺服器-客戶機計算模型和套接字程式設計介面;通過使用UDP和TCP套接字的示例演示了網路程式設計。第一個程式設計專案可實現一對通過網際網路執行檔案操作的TCP伺服器–客戶機,可讓使用者定義其他通訊協議來可靠地傳輸檔案內容。
本章的第二部分介紹了Web和CGI程式設計,解釋了HTTP程式設計模型、Web頁面和Web瀏覽器;展示瞭如何配置Linux HTTPD伺服器來支援使用者Web頁面、PHP和CGI程式設計;闡釋了客戶機和伺服器端動態Web頁面;演示瞭如何使用PHP和CGI建立伺服器端動態Web頁面。

二、計算機網路知識

  • TCP/IP協議包括ICMP、IP、telnet、udp等協議,是利用IP進行通訊時所必須用到的協議群的統稱。
  • IP主機和IP地址:主機是支援TCP/IP 協議的計算機或裝置。每個主機由一個32位的IP地址來標識。為了方便起見,32位的P地址號通常用點記法表示,例如:134.121.64.1,其中各個位元組用點號分開。主機也可以用主機名來表示,如dns1.eec.wsu.edu。IP地址分為兩部分,即 NetworkID欄位和HostID欄位。根據劃分,IP地址分為A~E類。例如,一個B類P地址被劃分為一個16位NetworkID,其中前2位是10,然後是一個16位的HostID欄位。發往P地址的資料包首先被髮送到具有相同networkID 的路由器。路由器將通過HostID將資料包轉發到網路中的特定主機。每個主機都有一個本地主機名localhost,預設P地址為127.0.0.1。本地主機的鏈路層是一個回送虛擬裝置,它將每個資料包路由回同一個 localhost。
  • IP協議:用於在IP主機之間傳送/接收資料包。IP盡最大努力執行。IP主機只向接收主機發送資料包,但它不能保證資料包會被髮送到它們的目的地,也不能保證按順序傳送。
  • IP資料包:由IP頭、傳送方地址和接收方I地址以及資料組成。每個資料包的大小最大為64KB。IP頭包含有關資料包的更多資訊,例如資料包的總長度、資料包使用TCP還是UDP、生存時間(TTL)計數、錯誤檢測的校驗和等。
  • 路由器:是接收和轉發資料包的特殊IP主機。一個IP資料包可能會經過許多路由器,或者跳躍到達某個目的地。每個IP包在IP報頭中都有一個8位生存時間(TTL)計數,其最大值為255。在每個路由器上,TTL會減小1。如果TTL減小到0,而包仍然沒有到達目的地,則會直接丟棄它。這可以防止任何資料包在IP網路中無限迴圈。
  • UDP:在IP上執行,用於傳送/接收資料報。與IP類似,UDP不能保證可靠性,但是快速高效。ping是一個向目標主機發送帶時間戳UDP包的應用程式。接收到一個pinging資料包後,目標主機將帶有時間戳的UDP包回送給傳送者,讓傳送者可以計算和顯示往返時間。如果目標主機不存在或宕機,當TTL減小為0時,路由器將會丟棄pinging UDP資料包。在這種情況下,使用者會發現目標主機沒有任何響應。使用者可以嘗試再次ping,或者斷定目標主機宕機。
  • TCP:是一種面向連線的協議,用於傳送/接收資料流。TCP也可在IP上執行,但它保證了可靠的資料傳輸。通常,UDP類似於傳送郵件的USPS,而TCP類似於電話連線。
  • 埠編號:埠號是分配給應用程式的唯一無符號短整數。要想使用UDP或TCP,應用程式(程序)必須先選擇或獲取一個埠號。前1024個埠號已被預留。其他埠號可供一般使用。應用程式可以選擇一個可用埠號,也可以讓作業系統核心分配埠號。
  • 網路和主機位元組序:計算機可以使用大端位元組序,也可以使用小端位元組序。在網際網路上,資料始終按網路序排列,這是大端。在小端機器上,例如基於Intel x86的PC,htons()、htonl()、ntohs()、ntohl()等庫函式,可在主機序和網路序之間轉換資料。例如,PC中的埠號1234按主機位元組序(小端)是無符號短整數。必須先通過htons(1234)把它轉換成網路序,才能使用。相反,從網際網路收到的埠號必須先通過ntohs(port)轉換為主機序。
  • TCP/IP網路中的資料流

三、網路程式設計

  • 套接字協議及其資料傳輸特性
int socket(int domain, int type, int protocol);       // domain:採取的協議族,一般為 PF_INET;type:資料傳輸方式,一般為 SOCK_STREAM;protocol:使用的協議,一般設為 0 即可。
                //成功時返回檔案描述符,失敗時返回 -1
  • 建立套接字的函式 socket 的三個引數的含義:
    domain:使用的協議族。一般只會用到 PF_INET,即 IPv4 協議族。
    type:套接字型別,即套接字的資料傳輸方式。主要是兩種:SOCK_STREAM(即 TCP)和 SOCK_(即 UDP)。
    protocol:選擇的協議。一般情況前兩個引數確定後,protocol 也就確定了,所以設為 0 即可。
  • 套接字型別
    同一個協議族可能有多種資料傳輸方式,因此在指定了 socket 的第一個引數後,還要指定第二個引數 type。
  • SOCK_STREAM 代表的是 TCP 協議,會建立面向連線的套接字,有如下特點:
    1.可靠傳輸,傳輸的資料不會消失。
    2.按序傳輸。
    3.傳輸的資料沒有邊界:從面向連線的位元組流角度理解。接收方收到資料後放到接收快取中,使用者使用 read 函式像讀取位元組流一樣從中讀取資料,因此傳送方 write 的次數和接收方 read 的次數可以不一樣。
int tcp_socket = socket(PF_INET, SOCK_STREAM, 0);
  • SOCK_DGRAM 代表的是 UDP 協議,會建立面向訊息的套接字,有如下特點:
    1.快速傳輸。
    2.傳輸的資料可能丟失、損壞。
    3.傳輸的資料有資料邊界:這意味著接收資料的次數要和傳輸次數相同,一方呼叫了多少次 write(send),另一方就應該呼叫多少次 read(recv)。
    4.限制每次傳輸的資料大小。
int udp_socket = socket(PF_INET, SOCK_DGRAM, 0);
  • 套接字地址結構

IPv4套接字結構

IPv4套接字地址結構通常稱為“網際套接字地址結構”,它以sockaddr_in命名,定義在<netinnet/in.h>標頭檔案中。

以下是網際(IPv4)套接字地址結構:sockaddr_in

struct in_addr;
{
   int_addr_t s_addr; /*32位IPv4的地址*/
                      /*網路位元組命令*/
};
 
struct sockaddr_in 
{
uint8_t     sin_len;
sa_family_t sin_family;
in_port_t   sin_port;
 
struct in_addr sin_addr;
char           sin_zero[8];
};

POIX規範只需要這個結構中的3個欄位:sin_family、sin_add、sin_port。對於符合POIX的實現來說,定義額外的結構欄位是可以接收的,這對於網際套接字地址結構來說也是正常的。幾乎所有的實現都增加了sin_zero欄位,所以所有的套接字地址結構大小都至少是16位元組。

資料型別 說明 標頭檔案
int8_t 帶符號的8位整數 <sys/typcs.h>
uint8_t 無符號的8位整數 <sys/typcs.h>
int16_t 帶符號的16位整數 <sys/typcs.h>
uint16_t 無符號的16位整數 <sys/typcs.h>
int32_t 帶符號的32位整數 <sys/typcs.h>
uint32_t 無符號的32位整數 <sys/typcs.h>
sa_family_t 套接字地址結構的地址族 <sys/socket.h>
socklen_t 套接字地址結構的長度,一般為uint32_t <sys/socket.h>
in_addr_t
IPv4地址,一般為unit32_t

<netine/in.h>
in_port_t TCP或UDP埠,一般為uint16_t <netine/in.h>
(擴充套件)POSIX(Portable Operating System Interface,可移植作業系統介面)

POIX是介面。

POSIX標準定義了作業系統應該為應用程式提供的介面標準,是IEEE為要在各種UNIX作業系統上執行的軟體而定義的一系列API標準的總稱,其正式稱呼為IEEE 1003,而國際標準名稱為ISO/IEC 9945。

(一些常見的縮寫)

addr(address,地址)

info(information,資訊)

  • 通用套接字地址結構

通用套接字地址結構:sockaddr

struct sockaddr
{
uint8_t           sa_len;
sa_family_t       sa_family;
char              sa_data[14];
};
IPv6套接字地址結構

IPv6套接字地址結構在<netinet/in.h>標頭檔案中定義

struct in6_addr
{
unit8_t s6_add[16];
 
};
#define SIN6_LEN
struct sockaddr_in6
{ 
uint8_t           sin6_len;
sa_family_t       sin6_family;
in_port_t         sin6_port;
uint32_t          sin6_flowinfo;
struct in6_addr   sin6_addr;
uint32_t          sin6_scope_id;
};

新的struct sockaddr_storage足以容納系統所支援的任何套接字地址結構。sockaddr_storage結構在<netinet/in.h>標頭檔案中定義

struct sockaddr_storage
{
uint8_t       ss_len;
sa_family_t   ss_family;
};

不同套接字地址結構的比較

  • 值-結果引數

傳遞方式取決於該結構的傳遞方向:是從程序到核心,還是從核心到程序、

1)從程序到核心傳遞套接字地址結構的函式有3個:bind,connect和sendto

這些函式的一個引數時指向某個套接字地址結構的指標,另一個引數時該結構的整數大小。

!!套接字地址結構大小的資料型別是socklen_t。(POIX建議將socklen_t定義為uint32_t)

2)從核心到程序傳遞套接字地址結構的函式有4個:accept,recvfrom,getsocknamo和getpeername。

這4個函式的其中兩個引數是指向某個套接字地址結構的指標和指向表示該結構大小的整數變數的指標。

值-結果引數:把套接字地址結構大小這個引數從一個整數改為指向某個整數變數的指標,其原因在於,當函式被呼叫時,結構大小是一個值,它告訴核心該結構的大小,這樣核心在寫該結構時不至於越界;

當函式返回時,結構大小又是一個結果,它告訴程序核心在該結構中究竟儲存了多少資訊。

  • 位元組排序函式

小端和大端(記憶體中儲存兩個位元組有兩種方法)

小端(little-endian):將低序位元組儲存在起始地址

大端(big-endian):將高序位元組儲存在起始地址

主機位元組序:某個給定系統所用的位元組序

輸出位元組序的程式:

#iclude"unp.h"
int main(int argc,char **argv)
{
  union{
     short   s;
     char    c[sizeof(short)];
        }un;
un.s=0x0102;
printf("%s:",CUP_VENDOR_OS);
if(sizeof(short)==2){
    if(un.c[0]==1&&un.c[1]==2)
            printf("big-endian\n");
    else if (un.c[0]==2&&un.c[1]==1)
            printf("little-endian\n");
    else
            printf("unknown\n");
}else
      printf("sizeof(short)=%d\n",sizeof(short));
exit(0);
}
  • 位元組操縱函式

bzero:bzero把目標位元組串指定數目的位元組置為0。我們常用該函式把一個套接字地址結構初始化為0.

bocpy:指定數目的位元組從源位元組串移動到目標位元組串。

bcmp:比較兩個任意的位元組串,若相同返回值為0,否則返回值為非0.

memset:把目標位元組串指定數目的位元組置為c。

mencmp:比較兩個任意的字串,若相同為0,否則返回一個非0值,是大於0還是小於0則取決於第一個不等的位元組。

5.inet_aton、net_addr和inet_ntoa函式(地址轉換函式)

6.inet_pton和inet_ntop函式

函式中的p代表表達(presentation),n代表數值(numeric)

地址的表達格式通常是ASCII字串,數值格式則是存放到套接字地址結構中的二進位制。

1)只支援IPv4的inet_pton函式的簡單定義

int inet_pton(int family,const char *strptr,void *addrptr)
{
    if(family==AF_INET)
    {
         struct in_addr  in_val;
    if(inet_aton(strptr,&in_val))
    {
        memcpy(addrptr,&in_val,sizeof(struct int_addr));
        return(1);
    }
    return(0);
    }
    errno=EAFNOSUPPROT;
    return(-1);
}

2)只支援IPv4的inet_ntop函式的簡化版本

const char*
inet_ntop(int family,const void *addrptr,char *strptr,size_t len)
{
   const u_char *p=(const u_char *)addrptr;
   if(family==AF_INET)
   {char temp[INET_ADDRSTRLEN];
   snprintf(temp,sizeof(temp),"%d,%d,%d,%d",p[0],p[1],p[2],p[3]);
   if(strlen(temp)>=len)
   { errno=ENOSPC;
     return (NULL);
   }
   strcpy(strptr,temp);
   return (strptr);
}
errno =EAFNOSUPPORT;
return (NULL);
}
  • sock_ntop和相關函式

作用:它以指向某個套接字地址結構的指標為引數,檢視該結構的內部,然後呼叫適當的函式返回該地址的表達格式。

#include"unp.h"
char *sock_ntop(const struct sockaddr *sockaddr,socklen_t addrlen);

//若成功則為非空指標,若出錯為NULL

四、實踐內容與截圖

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
 
#define MAXLINE 256
#define PORT 7777
void sys_err(char *msg){
    perror(msg);
    exit(-1);
}
int main(int argc , char **argv){
 
 
    int sockFd,n;
    char recvLine[MAXLINE];
    struct sockaddr_in servAddr;
 
    if (argc != 2) {
        sys_err("usage: a.out <IPaddress>");
    }
 
    sockFd=socket(AF_INET,SOCK_STREAM,0);
 
 
    memset(&servAddr,0,sizeof(servAddr));
 
    servAddr.sin_family = AF_INET;
    servAddr.sin_port = htons(PORT);
    if (inet_pton(AF_INET,argv[1],&servAddr.sin_addr) <= 0) {
 
        sys_err("inet_pton error");
    }
 
    connect(sockFd,(struct sockaddr *)&servAddr,sizeof(servAddr));
 
 
    while((n=read(sockFd,recvLine,MAXLINE)) >0 ){
        recvLine[n] = '\0';
        if(fputs(recvLine,stdout) == EOF){
            sys_err("fputs error");
        }
    }
    if(n <0){
        sys_err("read error");
    }
    return 0;
}

五、問題與解決

問題:在網路程式設計過程中,伺服器端和客戶端的套接字有什麼區別?
解決:據網上部落格的答案,兩種方式差別不大,並沒有本質的區別,建議主控機器作為伺服器,其它作為客戶端。原因在於,監聽需要一個固定的埠,相比之下在一臺機器上保證一個埠不衝突,遠比在眾多機器當中保證埠不衝突來得容易。