閱讀538 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Unix網絡編程 之 socket基礎

基本結構

(這部分的地址均為網絡地址<網絡字節序>)


1、struct sockaddr:通用套接字地址結構

    此結構用於存儲通用套接字地址。

   數據結構定義:

typedef unsigned short  sa_family_t;
struct sockaddr {
    sa_family_t sa_family;  /* address family, AF_xxx	*/
    char sa_data[14];  /* 14 bytes of protocol address	*/
};
   sa_family:實際使用地址——根據不同的協議族,采用不同的地址類,最常用的三種:

   1)本地地址族:AF_UNIX或AF_LOCAL;

#include <sys/un.h>

struct sockaddr_un {
    sa_family_t  sun_family;         /*AF_UNIX*/
    char        sun_path[108];      /*地址數據*/
};

   2)網絡地址族:AF_INET;

#include <netinet/in.h>

struct sockaddr_in {
    sa_family_t    sin_family;       /* AF_INET */
    uint16_t       sin_port;         /*端口號*/
    struct in_addr   sin_addr;        /*Internet地址*/
    unsigned char   sin_zero[8]      /*占位字節*/
};

   3)紅外地址類:AF_IRDA;

#include <sys/types.h>

struct sockaddr_irda {
    sa_family_t sir_family;  /* AF_IRDA */
    u_int8_t sir_lsap_sel;     /* LSAP selector */
    u_int32_t sir_addr;         /* Device address */
    char sir_name[25];  /*:IrDA:TinyTP ,OBEX,etc.*/
};

  sa_data[]:包含遠程主機的地址、端口號和套接字的數目,這些信息包含於字符串中。

   而在實際應用中,我們經常使用AF_INET。為了處理struct sockaddr,Unix建立了另外一個相似結構struct sockaddr_in。其結構如下所示。

struct sockaddr_in {
    sa_family_t    sin_family;       /* AF_INET */
    uint16_t       sin_port;         /*端口號*/
    struct in_addr   sin_addr;        /*Internet地址*/
    unsigned char   sin_zero[8]      /*占位字節*/
};
   此結構提供了簡潔的方法用於訪問socket address(struct sockaddr)結構中的每一個元素。sin_zero[8]是為了使兩個結構在內存中具有相同的尺寸,使用sockaddr_in時要把sin_zero[]全部設置為零值(使用bzero()函數或memset()函數)。此結構可以認為是IPv4套接字地址結構。

2、struct in_addr:因特網地址結構

   該結構用於表示Internet Address(因特網地址)。

/*Internet address*/
typedef uint32_t in_addr_t;
struct in_addr
{
    in_addr_t s_addr;
};
    在這裏,我們由s_addr的類型定義可知,此處的因特網地址長度為32bit,也就是說此處的地址為IPv4地址。同時,當使用struct sockaddr_in類型的ina變量時,ina.sin_addr.s_addr即為32bit的IP地址。當然,此處,我們應該注意這裏的IP地址是網絡字節序。   

3、IPv6套接字地址結構

    在上文,我們介紹了IPv4套接字地址結構,那麼,對於IPv6套接字,其地址結構又如何呢?

   IPv6套接字地址結構在<netinet/in.h>頭文件中定義:

struct in6_addr{
    uint8_t s6_addr[16];   /*128bits IPv6 address network byte ordered*/
 };

struct sockaddr_in6{
    sa_family_t sin6_family;    /*AF_INET6 */
    in_port_t sin6_port;  /*transport layer port network byte ordered */
    uint32_t sin6_flowinfo;  /*flow information, undefined*/
    struct in6_addr sin6_addr; /*IPv6 address network byte ordered */
    uint32_t sin6_scope_id;  /*set of interfaces for a scope*/
};
   對於IPv6套接字地址結構,我們要注意這樣幾點:

   *IPv6的地址族(sa_family)是AF_INET6,而IPv4的地址族是AF_INET;

   *結構中字段的先後順序做過編排,使得如果sockaddr_in6結構本身是64位對齊的,那麼128位的sin6_addr字段也是64位對齊的。

   *sin6_flowinfo字段分為兩個字段:低序20位流標(flow table) + 高序12位保留。

   *對於具備範圍的地址(scoped address),sin6_scope_id字段標識其範圍(scope),最常見的是鏈路局部地址(link-local address)的接口索引(interface index)。

4、struct sockaddr_storage:新的通用套接字地址結構

struct sockaddr_storage{
    uint8_t    ss_len;    //length of the struct
    sa_family_t ss_family;   //address family: AF_xxx value
};
   在上文,我們可以知道struct sockaddr為通用套接字地址結構,但是由於因特網地址結構的限製,該結構僅支持IPv4地址。而新的通用套接字地址結構struct sockaddr_storage克服了現有struct sockaddr的一些缺點,足以容納係統所支持的任何任何套接字地址結構。

   sockaddr_storage類型提供的通用套接字地址結構相比sockaddr存在以下兩點差別:

(1)、如果係統支持的任何套接字地址結構有對齊需要,那麼sockaddr_storage能夠滿足最苛刻的對齊要求;

(2)、sockaddr_storage足夠大,能夠容納係統支持的任何套接字地址結構。

5、套接字地址結構的比較:

 


基本轉換函數


1、值-結果參數

   當往一個套接字函數傳遞一個套接字地址結構時,該結構總是以引用形式來傳遞,也就是說傳遞的是指向該結構的一個指針。該結構的長度也作為一個參數來傳遞,不過其傳遞方式取決於該結構的傳遞方向:是從進程到內核,還是從內核到進程。

(1)、從進程到內核傳遞套接字地址結構的函數有3個:bind、connect和sendto。這些函數一個參數是指向某個套接字地址結構的指針,另一個參數是該結構的整數大小。例如:

struct sockaddr_in serv;

connect(sockfd, (struct sockaddr *) &serv, sizeof(serv));
   既然指針和指針所指內容的大小都傳遞給了內核,內核就知道到底需要從進程複製多少數據。下圖展示了這個情形。

 

(2)、從內核到進程傳遞套接字地址結構的函數有4個:accept、recvfrom、getsockname和getpeername。這四個函數的其中兩個參數是指向某個套接字地址結構的指針和指向表示該結構大小的整數變量的指針。例如:

struct sockaddr_un cli; /*Unix domain*/

socklen_t len;
len = sizeof(cli);
getpeername(unixfd, (struct sockaddr *)&cli, &len);
   把套接字地址結構大小這一參數從一個整數改為指向某個整數變量的指針,其原因在於:

   當函數被調用時,結構大小是一個值(value),它告訴內核該結構的大小,這樣內核在寫該結構時不至於越界;當函數返回時,結構大小又是一個結果(result),它告訴進程內核在該結構中究竟存儲了多少信息。這種類型的參數稱為“值-結果(value-result)”參數。下圖展示了這個情形。

 

   關於值-結果參數,在後麵介紹套接字調用函數時會進一步解析。

2、字節排序函數

   IP地址的三種表示格式:

1)ASCII(點分十進製字符串)

2)網絡地址(網絡字節序,Network Byte Order)

3)主機地址(主機字節序,Host Byte Order)

   那麼,網絡字節序和主機字節序有何不同?

   內存在存儲數據時有兩種處理方法:一種是將低序字節存儲在起始地址,此稱為小端(little-endian)字節序;另一種是將高序字節存儲在起始地址,此稱為大端(big-endian)字節序。采用大端方式進行數據存放符合人類的正常思維,而采用小端方式進行數據存放利於計算機處理。

   對於2個字節的數據存儲,大端存儲與小端存儲的區別如下圖:

 

   在套接字地址表示方麵,網絡地址(網絡字節序)一般采用大端字節序,而主機地址(主機字節序)主要取決於係統。

   那麼主機地址和網絡地址(或者說主機字節序和網絡字節序)各自主要應用於什麼場合?

   主機地址主要用於主機處理時,因為計算機更加擅長處理小端字節序(對采用小端存儲的主機);而在網絡協議中,發送協議棧和接受協議棧必須就多字節字段的各個字節的傳送順序達成一致,為了便於人類思維過程,一般采用大端字節序,即網絡字節序。

   主機字節序和網絡字節序之間相互轉化的過程即為字節排序的過程。這兩種字節序之間的轉換使用以下4個函數:

#include <netinet/in.h>

/*均返回網絡字節序的值*/
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);

/*均返回主機字節序的值*/
uint16_t ntonhs(uint16_t net16bitvalue);
uint32_t ntonhl(uint32_t net32bitvalue);
  在這些函數的名字中,h代表host,n代表network,s代表short,l代表long。如今,我們把s視為16位的值(例如TCP/UDP端口號),把l視為32位的值(例如IPv4地址)。

   這些函數在處理時,會根據主機係統到底是小端存儲還是大端存儲來相應地調整函數的處理過程,如果主機係統支持小端存儲,則對應實現字節反轉過程,反之,則這些函數為空宏。

   在什麼時候應該使用這些函數呢?當我們存在內核與進程之間的套接字地址結構訪問時就必須使用相應的字節轉換函數來實現地址的正常傳遞。

3、字節操縱函數

   操縱多字節字段的函數有兩組(Berkeley&ANSIC),它們既不對數據作解釋,也不假設數據是以空字符結束的C字符串。

   名字以b(表示字節)開頭的第一組函數起源於4.2BSD,現今支持套接字函數的係統仍然提供它們。名字以mem(表示內存)開頭的第二組函數起源於ANSIC標準,支持ANSIC函數庫的所有係統都提供它們。

Berkeley:

#include <string.h>

void bero(void *dest, size_t, nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);
/*相等返回0,否則返回非0值*/
/*此處使用const限定詞,表示所限製的指針所指的內容不會被函數更改。換句話說,函數隻是讀而不修改由const指針所指的內存單元。*/

ANSIC:

#include <string.h>

void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);
/*相等返回0,否則返回非0值,或大於0,或小於0*/
 4、地址轉換函數

   地址轉換函數實現ASCII字符串(點分十進製)與網絡字節序的二進製(存放在套接字地址結構中的值)之間的網際地址的轉換。

   地址轉換函數有兩組,分別是:

(1)、inet_aton、inet_addr和inet_ntoa函數

   完成點分十進製(如”192.168.1.1”)與它長度為32位的網絡字節序二進製間轉換IPv4地址。

#include <arpa/inet.h>

int inet_aton(const char *strptr, struct in_addr *addrptr);
/*
此函數將strptr所指的C字符串轉換成一個32位的網絡字節序二進製,並通過指針addrptr來存儲。若成功,返回1,否則返回0。
 */

in_addr_t inet_addr(const char *strptr);
/*
此函數與inet_aton函數執行相同的操作,返回32位的網絡字節序二進製值,否則返回INADDR_NONE。注意,此函數不能處理255.255.255.255點分十進製數串。
 */

char *inet_ntoa(struct in_addr inaddr);
/*
此函數將一個32位的網絡字節序二進製IPv4地址轉換成相應的點分十進製數串。
由該函數的返回值所指向的字符串駐留在靜態內存中。另外。該函數是以一個結構而不是以指向該結構的一個指針作為參數。
 */

   注:inet_addr()函數已被廢棄,建議采用inet_aton()函數。

(2)、inet_pton和inet_ntop函數

    這兩個函數式隨IPv6出現的新函數,對於IPv4和IPv6地址均適用。函數名中的p代表表達(presentation)和數值(numeric)。地址的表達格式通常是ASCII字符串,數值格式則是存放到套接字地址結構中的二進製值。

#include <arpa/inet.h>

int inet_pton(int family, const char *strptr, void *addrptr);
/*
此函數轉換由strptr指針所指的字符串,並通過addrptr指針存放二進製結果。若成功,則返回值為1,否則返回0。
 */

const char *inet_ntop(int family, const void *addrptr, size_t len);
/*
此函數執行從數值格式(addrptr)到表達格式(strptr)的轉換。
strptr參數不能是空指針,調用者必須為目標存儲單元分配內存並指定大小。調用成功時,返回此指針。
 */
  對於這兩個函數,family字段可以是AF_INET或AF_INET6。如果以不被支持的地址族作為family參數,這兩個函數會返回錯誤,並將errno置為EAFNOSUPPORT。

   另外,在inet_ntop()函數中,len參數是目標存儲單元的大小,以免該函數溢出其調用者的緩衝區。為了有助於指定此大小,在<netinet/in.h>頭文件中有如下定義:

#define INET_ADDRSTRLEN 16 /*IPv4 dotted-decimal*/
#define INET6_ADDRSTRLEN 46 /*IPv6 hex-string*/
   如果len太小,不足以容納表達格式結果,那麼返回空指針,並置errno為ENOSPC。

最後更新:2017-04-03 05:39:56

  上一篇:go kafka詳解一、Kafka簡介
  下一篇:go poj 1157 LITTLE SHOP OF FLOWERS dp