網絡編程基礎API、屬性設置

socket和TCP/IP協議族的關係

    數據鏈路層、網絡層、傳輸層協議是在內核中實現的。操作系統需要實現一組系統調用,使得應用程序能夠訪問這些協議提供的服務。實現這組系統調用的API主要有兩套:socket和XTI。

    由socket定義的一組API提供如下兩點功能:

    1、將應用程序數據從用戶緩衝區複製到TCP/UDP內核發送緩衝區,以交付內核來發送數據。或者是從內核TCP/UDP接收緩衝區中複製數據到用戶緩衝區,以讀取數據。

    2、應用程序可以通過它們來修改內核中各層協議的某些頭部信息或其他數據結構,從而精細地控制底層通信的行爲。比如可以通過setsockopt函數來設置IP數據報在網絡上的存活時間。


socket地址API

socket最開始的含義是一個IP地址和端口對(ip,port),它唯一地表示了使用TCP通信的一端。

通用socket地址

socket網絡編程接口中表示socket地址的是結構體sockaddr

struct sockaddr 

{
    sa_family_t sa_family;        
    char sa_data[14];   
};

sa_family 成員是地址族類型(sa_family_t )的變量。地址族類型通常與協議族類型對應。常見的協議族和對應的地址族見下表:

wKiom1PXGuzArWUZAAC-DKafHVY400.jpg

專用socket地址

linux爲各個協議族提供了專門的socket地址結構體。

UNIX 本地域協議族使用 sockaddr_un 專用socket地址結構體

struct sockaddr_un 

{

    sa_family_t sin_family;           /* 地址族:AF_UNIX*/ 
    char sun_path[108];               /*文件路徑名*/ 

};

TCP/IP協議族有sockaddr_in 和 sockaddr_in6 兩個專用的socket地址結構體,分別用於 IPV4和 IPV6。

struct sockaddr_in 

{

    sa_family_t sin_family;           /* 地址族:AF_INET*/ 
    u_int16_t sin_port;                 /* 端口號,要用網絡字節序表示 */ 
    struct in_addr sin_addr;         /* IPV4地址結構體 */  

};

struct in_addr

{

    u_int32_t s_addr;       /*IPV4地址,要用網絡字節序表示*/

}; 

當有多張網卡時,值 INADDR_ANY 意爲不管哪個網卡/哪個IP地址都處理。

所有專用socket地址類型的變量在實際使用時都需要轉化爲通用socket地址類型sockaddr(強制轉換即可)。


linux提供如下4個函數完成主機字節序和網絡字節序之間的轉換

主機字節序到網絡字節序

uint32_t htonl(uint32_t hostlong);   //4bytes

uint16_t htons(uint16_t hostshort);  //2bytes

網絡字節序到主機字節序

uint32_t ntohl(uint32_t netlong);    //4bytes

uint16_t ntohs(uint16_t netshort);   //2bytes 

長×××函數通常用來轉換IP地址,短整型函數用來轉換端口號(任何格式化的數據通過網絡傳輸時,都應該使用這些函數來轉換字節序)。


IP地址轉換函數:用點分十進制字符串表示的IPv4地址和用網絡字節序整數表示的IPv4地址之間的轉換

IP到網絡字節序

int inet_aton(const char *cp, struct in_addr *inp);  //將cp所指的字符串轉換成32位的網絡字節序二進制值

in_addr_t inet_addr(const char *cp);      //同上

網絡字節序到IP

char *inet_ntoa(struct in_addr in);  //將32位網絡字節序二進制地址轉換成點分十進制的字符串。


socket基礎API

socket 創建套接字
int socket(int domain, int type, int protocol);
    domain參數告訴系統使用哪個底層協議族。對TCP/IP協議族,該參數應設置爲PF_INET(用於IPv4)或PF_INET6(用於IPv6)
;對於UNIX本地域協議族,該參數應設置爲PF_UNIX

    type參數指定服務類型:SOCK_STREAM (流服務)或SOCK_DGRAM(數據報服務),對TCP/IP協議族,其值取SOCK_STREAM 表示傳輸層使用TCP協議,取SOCK_DGRAM表示傳輸層使用UDP協議。

    protocol參數是在前兩個參數構成的協議集合下,再選擇一個具體的協議。這個值通常都是唯一的(前兩個參數已完全決定了它的值)。幾乎在所有情況下都設置爲0,表示使用默認協議。

    Socket()調用返回一個整型socket文件描述符,失敗返回-1,並設置error。

實際上"建立一個Socket"意味着爲一個Socket數據結構分配存儲空間。

bind 綁定本機地址和端口

    bind函數並不是總是需要調用的,只有用戶進程想與一個具體的地址或端口相關聯的時候才需要調用這個函數。如果用戶進程沒有這個需要,那麼程序可以依賴內核的自動的選址機制來完成自動地址選擇,而不需要調用bind的函數,同時也避免不必要的複雜度。

    在一般情況下,對於服務器進程問題需要調用bind函數,對於客戶進程則不需要調用bind函數。  

int bind(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);

listen 設置監聽端口

    listen函數使主動連接套接口變爲被動連接套接口,使得一個進程可以接受其它進程的請求,從而成爲一個服務器進程。在TCP服務器編程中listen函數把進程變爲一個服務器,並指定相應的套接字變爲被動連接。

int listen(int sockfd, int backlog);

參數:

    sockfd 之前由socket函數返回。在被socket函數返回時,它是一個主動連接的套接字。也就是此時系統假設用戶會對這個套接字調用connect函數,期待它主動與其它進程連接。而在服務器編程中,用戶希望這個套接字可以接受外來的連接請求,也就是被動等待用戶來連接。由於系統默認一個套接字是主動連接的,所以需要通過某種方式來告訴系統,用戶進程通過系統調用 listen來完成這件事。

    backlog 這個參數涉及到一些網絡的細節。在進程處理一個一個連接請求的時候,可能還存在其它的連接請求。因 爲TCP連接是一個過程,所以可能存在一種半連接的狀態,有時由於同時嘗試連接的用戶過多,使得服務器進程無法快速地完成連接請求。如果這個情況出現了, 服務器進程希望內核如何處理呢?內核會在自己的進程空間裏維護一個隊列以跟蹤這些完成的連接但服務器進程還沒有接手處理或正在進行的連接,這樣的一個隊列內核不可能讓其任意大,所以必須有一個大小的上限。這個backlog告訴內核使用這個數值作爲上限。毫無疑問,服務器進程不能隨便指定一個數值,內核有一個許可的範圍。這個範圍是實現相關的。很難有某種統一,一般這個值會小30以內。

accept 接受TCP連接

    accept系統調用從listen系統調用接受的連接的隊列中(struct inet_connection_sock->isck_accept_queue.listen_sock->syn_table)取一個連接,並建立一個新的socket,把這個連接(struct request_sock)賦給這個新的sokcet,此後,該連接就由這個新的socket負責與對端進行通訊。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    參數:addr是回傳指針。存入的是接受的連接對端的地址信息。accept返回一個新的套接字描述符fd。

connect 建立連接

    connect函數會產生網絡數據的發送,TCP的三次握手也正是在此時開始,connect會先發送一個SYN包給服務端,並從最初始的CLOSED狀態進入到SYN_SENT狀態,在此狀態等待服務端的確認包,通常情況下這個確認包會很快到達,以致於我們根本無法使用netstat命令看到SYN_SENT狀態的存在,不過我們可以做一個極端情況的模擬,讓客戶端去連接一個隨意指定服務器(如IP地址爲88.88.88.88),因爲該服務器很明顯不會反饋給我們SYN包的確認包(SYN ACK),客戶端就會在一定時間內處於SYN_SENT狀態,並在預定的超時時間(比如3分鐘)之後從connect函數返回,connect調用一旦失敗(沒能到達ESTABLISHED狀態)這個套接字便不可用,若要再次調用connect函數則必須要重新使用socket函數創建新的套接字。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

socket 關閉套接字

int close(int sockfd);  //關閉雙向通訊

int shutdown(int sockfd, int howto);   //tcp連接是雙向的(可讀寫),當使用close時,會把讀寫通道都關閉。有時希望只關閉一個方向的,可使用shutdown。

    howto = 0  //關閉讀通道,但可以繼續往套接字寫數據。

    howto = 1  //關閉寫通道,只能從套接字讀取數據。

    howto = 2  //與close()一樣。

地址信息函數

想知道一個連接socket的本端socket地址,以及遠端的socket地址,使用下面兩個函數:

int getsockname(int sockfd, struct sockaddr* address, socklen_t* address_len); //獲取本端socket地址

int getpeername(int sockfd, struct sockaddr* address, socklen_t* address_len); //獲取遠端socket地址

socket屬性設置     

http://blog.csdn.net/chary8088/article/details/2486377

fcntl系統調用是控制文件描述符屬性的通用POSIX方法,下面兩個系統調用是專門用來讀取和設置socket文件描述符屬性的方法。

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); 

int setsockopt(int sockfd, int level, int optname,  const void *optval, socklen_t optlen);

參數:

sockfd:指定被操作的目標socket。

level:指定要操作哪個協議的選項(屬性) 

        SOL_SOCKET: 通用socket選項,與協議無關、IPPROTO_IP: IPv4選項、IPPROTO_IPV6: IPv6選項、IPPROTO_TCP: TCP選項 

optname:指定選項的名稱

optval:被操作選項的值

optlen(選項長度) :

wKiom1PXM1eBxBLlAAUNqMtd2s0755.jpg


網絡信息API

linux提供了一套網絡信息API,以實現主機名和IP地址之間的轉換,以及服務名稱和端口號之間的轉換。

socket地址的兩個要素(IP地址和端口號),都是用數值表示。不便於記憶,不便於擴展。可以用主機名來訪問一臺機器,避免直接使用IP地址,可以用服務名稱來代替端口號。

例:telnet 127.0.0.1 80

    telnet localhost www

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章