1.unix網絡編程基礎知識

  接觸網絡編程一年多了,最近在系統的學習vnp兩本書,對基礎知識作一些總結,但願理解的更透徹清晰,但願能有更多的沉澱。編程

1.套接口地址安全

  針對IPv4和IPv6地址族,分別定義了兩種類型的套接口地址:sockaddr_in和sockaddr_in6,兩種套接口地址結構以下所示: 服務器

/* IPv4地址族套接口地址結構 */ 
struct in_addr {
    in_addr_t s_addr;             /* IPv4地址,網絡序存儲 */       
} 
struct sockaddr_in {
    uint8_t sin_len;              /* 結構體大小 */
    sa_family_t sin_family;       /* 地址族:AF_INET */ 
    in_port_t sin_port;           /* 16位端口號,網絡序存儲 */
    struct in_addr sin_addr;      /* IPv4地址,網絡序存儲 */
    char sin_zero[8]              /* 保留字段,未使用 */ 
};

/* IPv6地址族套接口地址結構 */ 
struct in6_addr {
    uint8_t s6_addr[16];             /* IPv6地址,網絡序存儲 */       
} 
struct sockaddr_in6 {
    uint8_t sin6_len;              /* 結構體大小 */
    sa_family_t sin6_family;       /* 地址族:AF_INET */ 
    in_port_t sin6_port;           /* 16位端口號,網絡序存儲 */
    uint32_t sin6_flowinfo         /* 流標記或優先級,網絡序存儲 */
    struct in6_addr sin6_addr;      /* IPv4地址,網絡序存儲 */ 
};

這兩個套接口地址結構在/netinet/in.h頭文件中定義,上面結構體的描述和頭文件中的定義有一些差異,好比在頭文件中sa_family_t成員經過宏定義在了公共部分,另外表示結構體大小的sin_len和sin6_len成員,在某些實現中沒有定義,兩種套接口地址結構都定義了地址族類型成員,是自描述的結構。網絡

  另外爲了使套接口函數可以統一處理全部協議族的套接口地址結構,定義了通用套接口地址:架構

/* 通用套接口地址結構 */
struct sockaddr {
    uint8_t sa_len;                 /* 結構體大小 */
    sa_family_t fa_family;          /* 地址族 */ 
    char sa_data[14];               /* 協議地址 */ 
} 

通用套接口地址結構的用處只有兩個:一、用於套接口函數的聲明,任何使用套接口地址指針爲參數的函數,其指針所有聲明爲指向通用套接口地址類型。二、類型轉換,用於將特定於協議族的地址指針轉換爲通用地址指針。dom

2.套接口函數socket

  socket建立套接口描述符:tcp

#include <sys/socket.h>
/* 功能:建立套接口描述符-sockfd。 
   參數:一、domain-協議族,經常使用AF_INET、AF_INET六、AF_UNIX或AF_LOCAL等。 
         二、type-套接口類型,經常使用SOCK_STREAM、SOCK_DGRAM 等。 
         三、protocol-協議,通常設置爲0,協議有內核根據前兩個參數選擇。 
   返回值:成功則返回非負描述符,失敗返回-1。 
 */
int socket(int domain, int type, int protocol) 

  bind將套接口描述符綁定到特定的套接口地址:函數

#include <sys/socket.h>
#include <sys/socket.h>
/* 功能:將套接口描述符-sockfd綁定到特定的套接口地址。 
   參數:一、sockfd-套接口描述符。 
         二、addr-指向通用套接口地址的指針。 
         三、addrlen-套接口地址結構的大小。 
   返回值:成功則返回0,失敗返回-1,並修改errno爲相應的值。 
 */
int bind(int sockfd, const struct sockaddr *addr,
         socklen_t addrlen);

  connect將本地描述符鏈接到套接口地址addr指定的服務端:學習

#include <sys/socket.h>
/* 功能:將本地描述符鏈接到套接口地址addr指定的服務端。 
   參數:一、sockfd-套接口描述符。 
         二、addr-指向通用套接口地址的指針。 
         三、addrlen-套接口地址結構的大小。 
   返回值:成功則返回0,失敗返回-1,並修改errno爲相應的值。 
 */
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  listen將套接口描述符設置爲被動鏈接狀態,用於在指定端點進行監聽:

#include <sys/socket.h>
/* 功能:將套接口描述符設置爲被動等待鏈接狀態,用於在指定端點進行監聽。 
   參數:一、sockfd-套接口描述符。 
         二、backlog-未完成鏈接對了和已完成鏈接隊列的最大值。  
   返回值:成功則返回0,失敗返回-1,並修改errno爲相應的值。 
 */
int listen(int sockfd, int backlog);

  accept用於服務器端接受客戶端的一個鏈接:

#include <sys/socket.h>
/* 功能:用於服務器端接受客戶端的一個鏈接。 
   參數:一、sockfd-套接口描述符。 
         二、addr-指向通用套接口地址的指針,函數返回時保存了客戶端套接口地址信息。 
         三、addrlen-套接口地址結構的大小。 
   返回值:成功則返回一個已鏈接到客戶端的套接口描述符,失敗返回-1,並修改errno爲相應的值。 
 */
int accept(int sockfd, struct sockaddr *addr, 
           socklen_t *addrlen);

 3.字節序轉換函數和地址轉換函數

#include <arpa/inet.h>
/* 功能:主機序轉換成網絡序。 
   參數:一、hostlong/hostshort-待轉換32位長整形或者16位短整形。  
   返回值:轉換成網絡序的32位長整形或16爲短整形。 
 */
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
/* 功能:網絡序轉換成主機序。 參數:一、netlong/netshort-待轉換32位長整形或者16位短整形。 返回值:轉換成主機序的32位長整形或16爲短整形。 */ uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);

    備註:多字節類型數據的表示/存儲方式分爲小端序和大端序。小端序,高位字節存儲在高地址內存空間,低位字節存儲在低位內存空間,大端序偏偏相反。對於不一樣架構的主機序不同,有的架構主機序採用小端字節序,有的架構主機序採用大端架構,另外網絡序採用大端字節序當在網絡上傳輸字節流時,不須要考慮字節序的問題,當在網絡上傳輸多字節類型數據時須要考慮字節序問題

    驗證本地host字節序的方法:

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

int main(void) {
    union {
        short a;
        char c[sizeof(short)];
    } t;
    t.a = 0x0102;
    /* 高位字節存存儲於低地址內存,
     * 低位字節序存儲於高地址內存  */
    if (1 == t.c[0] && 2 == t.c[1])
        printf("big-endian\n");
    else
        printf("little-endian\n");
    return 0;
}

 

4.多字節操縱函數

  string.h中定義了兩組多字節類型操做函數,這兩組函數不對待處理的多字節類型數據作任何假設。第一組函數由b開頭,起源於4.2BSD,當前幾乎全部支持套接口的系統都提供這一組函數。

/* 功能:將s指向大小爲n的內存空間初始化爲全0。 
   參數:一、s-內存地址。
         二、n-內存大小  
   返回值:無。 
 */
void bzero(void *s, size_t n);
/* 功能:內存拷貝。 
   參數:一、src-源內存地址。
         二、dest-目標內存地址。
         三、n-拷貝內存大小。  
   返回值:無。 
 */
void bcopy(const void *src, void *dest, size_t n);
/* 功能:內存比較。 
   參數:一、s1-內存地址1。
         二、s2-內存地址2。
         三、n-比較的內存大小。  
   返回值:0-表示兩塊內存數據相同,非0-表示兩塊內存數據不一樣。 
 */
int bcmp(const void *s1, const void *s2, size_t n);

  第二組函數由mem開頭,起源於ansi c,由任何支持ansi c標準的系統提供。

#include <strings.h>
/* 功能:內存初始化。 
   參數:一、str-內存地址。
         二、c-初始化值。
         三、n-內存大小。  
   返回值:0-表示兩塊內存數據相同,非0-表示兩塊內存數據不一樣。 
 */
void *memset(void *str, int c, size_t n)
/* 功能:內存拷貝。 
   參數:一、src-源內存地址。
         二、dest-目標內存地址。
         三、n-拷貝內存大小。  
   返回值:無。 
 */
void *memcpy(void *dest, const void *src, size_t n);
/* 功能:內存比較。 
   參數:一、s1-內存地址1。
         二、s2-內存地址2。
         三、n-比較的內存大小。  
   返回值:0-表示兩塊內存數據相同,非0-表示兩塊內存數據不一樣。 
 */
int memcmp(const void *s1, const void *s2, size_t n);

  除了上面的兩組函數覺得,string.h頭文件定義了專門處理字符串的函數,這些函數以str開頭好比strcpy/strcmp等,這些函數假設處理的字符串都以0結尾。

5.套接口ip地址到字符串轉換函數

#include <arpa/inet.h>
/* 功能:將字符串IP地址轉換成機器IP地址,。 
   參數:一、af-地址族:AF_INET, AF_INET6。
         二、strptr-字符串ip。
         三、addrptr-地址結構。  
   返回值:1-成功,0-針對af, strptr不是有效的ip地址格式,。 
 */
int inet_pton(int af, const char *strptr, void *addrptr);
/* 功能:將機器IP地址轉換成字符串IP地址,。 
   參數:一、af-地址族:AF_INET, AF_INET6。
         二、addrptr-機器地址結構。
         三、strptr-字符串ip。
         四、size-strptr的長度,爲了防止內核寫溢出。  
   返回值:成功則返回指向strptr的指針,失敗-返回null,並設置errno爲相應值。。 
 */
const char *inet_ntop(int af, const void *addrptr, char *strptr, socklen_t size);

6. 套接口讀寫函數

  readn從一個socket中讀取n個字節(引用unp示例代碼):

size_t Readn(int fd, void *vptr, size_t n)
{
    size_t    nleft;
    size_t    nread;
    char    *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ( (nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR)
                nread = 0;        /* 系統調用被信號打斷,從新調用 */
            else
                return(-1);     /* 發生異常 */ 
        } else if (nread == 0)
            break;                /* 讀到結束符,讀到本地tcp收到的fin時 */

        nleft -= nread;
        ptr   += nread;
    }
    return(n - nleft);        /* return >= 0 */
}

  Writen向套接口描述符中寫入n個字節(引用unp示例代碼):

/* 功能:從套接口中讀取n個字節 
   返回值:n-成功;-1:失敗*/
size_t Writen(int fd, const void *vptr, size_t n)
{
    size_t        nleft;
    size_t        nwritten;
    const char    *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;        /* 被信號打斷,再次調用 */
            else
                return(-1);            /* 寫異常:write返回0-寫一個tcp鏈接關閉的sock */
        }

        nleft -= nwritten;
        ptr   += nwritten;
    }
    return(n);
}                  

 

  Readline從描述符fd中讀取一行(引用unp示例代碼):

static int    read_cnt;/* 緩衝區中可讀的字節數 */
static char    *read_ptr;/* 當前緩衝區中可讀字節的指針 */
static char    read_buf[MAXLINE];/* 讀緩衝區 */

/* 功能:讀去一個字節, 
   返回值:1-成功;0-讀到文件結束符;-1-系統調用錯誤*/
static ssize_t
my_read(int fd, char *ptr)
{
    if (read_cnt <= 0) { /* 當緩衝區中可讀字節數爲0時,從文件中讀數據到緩衝區 */
again:
        if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
            if (errno == EINTR) /* 系統調用被信號打斷 */
                goto again;
            return(-1);
        } else if (read_cnt == 0) /* 讀到文件結束符,正常返回0 */
            return(0);
        read_ptr = read_buf;
    }

    read_cnt--;
    *ptr = *read_ptr++; /* 從緩衝區中讀一個字節給用戶 */
    return(1);
}

/* 功能:從fd中讀取一行到vptr中,行的最大長度maxlen 
   返回值:n-成功,實際讀取的字節數;-1:讀失敗*/
ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t    n, rc;
    char    c, *ptr;

    ptr = vptr;
    for (n = 1; n < maxlen; n++) {
        if ( (rc = my_read(fd, &c)) == 1) {
            *ptr++ = c;
            if (c == '\n')
                break;    /* newline is stored, like fgets() */
        } else if (rc == 0) {
            *ptr = 0;
            return(n - 1);    /* EOF, n - 1 bytes were read */
        } else
            return(-1);        /* error, errno set by read() */
    }

    *ptr = 0;    /* null terminate like fgets() */
    return(n);
}


/* 功能:從fd中讀取一行到vptr中,行的最大長度maxlen 
   返回值:n-成功;-1-失敗,並打印錯誤日誌*/
ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
    ssize_t        n;

    if ( (n = readline(fd, ptr, maxlen)) < 0)
        err_sys("readline error");
    return(n);
}

  Readline存在的問題:由於間接使用了靜態全局變量,所以Readline是不可重入的,不是線程安全的。

相關文章
相關標籤/搜索