liunx 套接字編程(Linux_C++)

網絡中的進程是如何通訊的?html

在網絡中進程之間進行通訊的時候,那麼每一個通訊的進程必須知道它要和哪一個計算機上的哪一個進程通訊.不然通訊無從談起!在本地能夠經過進程PID來惟一標識一個進程,可是在網絡中這是行不通的.其實TCP/IP協議族已經幫咱們解決了這個問題,網絡層的「ip地址能夠惟一標識網絡中的主機,而傳輸層的「協議+端口能夠惟一標識主機中的應用程序(進程).這樣利用三元組(ip地址,協議,端口)就能夠標識網絡的進程了,網絡中的進程通訊就能夠利用這個標誌與其它進程進行交互.linux

什麼是套接字?數據庫

套接字是做爲4BDS UNIX的進程通訊機制,它用於描述 IP 地址和端口,是一個用於通訊鏈接的文件描述符.舉個例子來講.套接字就好像是銀行的服務窗口.而後每一個服務窗口上面都有一個標號(對應套接字的 IP 地址),而後銀行又有規定標號爲多少的窗口提供什麼服務,標號爲窗口提供什麼服務(窗口標號就相似套接字中記錄的 IP 地址,服務就相似着套接字的端口號),那麼客戶就能夠根據這個窗口標號去請求所須要的服務.編程

在 socket 中是怎麼作到對另外一個套接字鏈接的緩存

首先很明顯,上面一直在強調兩個進程在進行與網絡通訊的時候,兩個進程都必須知道對方是誰(經過"ip地址+端口"),不然就沒法進行通訊,能夠設想下你寫了封信給你的朋友小明,那麼你忘了寫寄信地址和寄信人.那麼你把這份信寄了.而後你就開始等待小明的回信,那麼小明就算收到信了也不知道這封信是誰寄的和該把回覆信寄給誰.那麼你能收到回信麼?安全

可是有時候你在寫通訊時候好比 tcp 的客戶端,你發現你沒有對套接字進行地址綁定也依舊能夠和服務器通訊,那這又是爲何?由於操做系統幫你作了這件事,操做系統在發現你沒有將一個套接字綁定到對應的 ip 地址和端口號而調用 listen 和connect 的時候.那麼它會自動給你分配 IP 地址和端口號.接着上面那個寄信的例子,假設你在寄信的時候是託給個朋友幫你寄.那麼這個好心的朋友發現了你犯的這個粗心的錯誤.那麼他便把在寄信人和寄信地址處填寫上了你的名字和你的地址,那麼小明就能夠和你正常通訊了.服務器

既然這爲何服務器還要大費周章的去對一個監聽端口進行地址綁定呢?咱們仍是用上面那個寄信的例子來講,假設你頗有錢,有三套房子(即服務器對應三個 IP),你依舊沒有寫寄信人和寄信地址.那麼你雖然託朋友給你寄,可是你這個朋友也不肯定你如今住那套房子裏,那麼他便把這個三個地址之中的一個地址填了上去.而偏偏很不幸的是你已近好久沒有住那套房子了.那麼小明即便回覆了你的信,你也可能收不到信了.因此服務器必需要對監聽套接字進行地址進行綁定!網絡

什麼是大小端架構

由於計算機內存是以字節(八位)爲單位來存儲東西的.那麼在存儲大於一字節的數據時候就會存在一個問題,按什麼樣的順序去存放這樣的數據,是低位字節排放在內存的低地址端仍是低位字節排放在內存的高地址端.然而這個每每和具體CPU架構有關,而非操做系統.那麼首先咱們來講明下大端模式和小端模式的區別:socket

    • 小端模式(Little-Endian):    就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端.(邏輯上的低低高高)
    • 大端模式(Big-Endian):      就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端.(像數據流同樣填充)

這樣也許很差理解那麼舉個例子來講.若是將一個 16 位的整數 0x1234 存放到一個短整型變量(short)中,這個短整型變量採用大端或者小端模式在內存中的存儲由下表所示.

 

socket 中的 tcp 編程的大體流程

注意問題

  1. 大型數據的發送與接收.這裏打大型數據指的是大於接收緩衝區或者發送緩衝區的數據.對於這種數據就須要屢次調用 recv 或 send 等等的函數來發送或者接收.
  2. TCP 是基於流發送的.因此當間隔很短髮送的數據,在服務器中可能所以一次性收到,對此要進行相關處理.
  3. 若 TCP 保持長鏈接的話,要進行檢查鏈接是否存在.常見的方法是用心跳包來檢查鏈接是否存在.
  4. 大小端問題.在網絡傳輸數據的時候記得只要超過兩字節的二進制數據就要用 htosXX 函數和 stohXX 函數來對數據進行處理,由於在終端平臺的大小端問題.可是超過兩字節的字符數據不須要注意終端平臺大小端問題.在網絡中 TCP/IP 各層協議將字節序(網絡字節序)定義爲Big-Endian
  5. 對於爲綁定地址和端口的套接字系統會隨機分配端口和綁定本機上的 IP 給套接字用.對於因特網域,若是指定IP地址爲 INADDR_ANY,套接字端點能夠被綁定到全部的系統網絡接口.這意味着能夠接受到這個系統鎖安裝的全部網卡的數據包,若是調用 connect 或 listen ,但沒有綁定地址到一個套接字,系統會選一個地址並將其綁定到套接字.

地址結構體

IPv4:  爲了使用不一樣格式地址可以被傳入到套接字函數,地址被轉換成通用的地址結構 sockaddr IPv4,在 IPv4 因特網域(AF_INET)中,套接字地址用以下結構 sockaddr_in 表示:
struct in_addr{in_addr_t s_addr;};
struct sockaddr_in{
    sa_family_t sin_family;
    in_port_t  sin_port;
    struct in_addr sin_addr;
};
IPv6:  在 IPv6 因特網域(AF_INET6)套接字地址用以下結構 sockaddr_in6 表示:
struct in6_addr{
    sa_family_t  sin6_family;
    in_port_t  sin6_port;
    uint32_t sin6_flowinfo;
    struct in6_addr sin6_addr;
    uint32_t  sin6_scope_id;
};

地址查詢

經過調用 gethostent 獲取主機信息,當 gethostent 返回時,獲得一個指向 hostent 結構體指針,該結構體可能包含一個靜態的數據緩存區,每次調用 gethostlent 將會覆蓋這個緩存區.返回的地址採用網絡字節序.相關函數:

    • gethostent
    • sethostent
    • endhostent

相關結構體:

struct hostent{
    char *h_name;
    char **h_aliases;
    char h_addrtype;
    char h_length;
    char **h_addr_list;
...
};

將協議名字和協議號採用如下函數映射

相關函數

    • getprotobyname
    • getprotobynumber
    • getprotoent
    • setprotoent
    • endprotoend

相關結構體 

struct protoent{
    char* p_name;
    char** p_aliases;
    int p_proto;
} 

服務查詢

服務是由地址的端口號部分表示的.每一個服務由惟一的熟知的端口號來提供,採用函數 getservbyname 能夠將一個服務名字映射到一個端口號,函數getservbyport將一個端口號映射到一個服務器名,或者採用函數 getservent順序掃描服務數據庫.相關函數:

    • getservbyname
    • getservbyport
    • getservent
    • setservent
    • endservent
相關結構體:
struct servent{
    char *s_name;
    char **s_aliases;
    int s_port;
    cjar *s_proto;
};

套接字的經常使用 API

  • 建立套接字:           socket()
    SOCK_RAW 套接字提供一個數據報接口用於直接訪問下面的網絡層(因特網域中爲IP).使用這個接口時,應用程序負責構造本身的協議首部,這是由於傳輸協議(TCP和UDP等)被繞過了.當建立一個原始套接字時須要具備超級用戶權限,用以防止惡意程序繞過內建安全機制來創造報文.
  • 對套接字進行端口和地址的綁定:  bind()
    bind一些限制
    • 在進程所運行的機器上,制定的地址必須有效,不能制定一個其餘機器的地址
    • 地址必須和建立套接字時的地址族所支持的格式相匹配
    • 端口號必須不小與1024,除非該進程具備相應的特權
    • 通常只有套接字端點可以與地址綁定,儘管有些協議能夠多重綁定
    假設我服務器綁定的套接字地址爲 123.255.1.3 客戶端只能從 123.255.1.3 地址創建請求鏈接請求
  • 監聽套接字:           listen() 
  • 鏈接套件子:           connect() 
    若是套接字描述符處於非阻塞模式下,那麼在鏈接不能立刻創建的時,connect 將會返回 -1,而且將 errno 設爲特殊的錯誤碼,EINPROGRESS . 應用程序可使用 select 或 poll 來判斷文件描述符什麼時候可寫,若是可寫完成鏈接.函數 connect 還能夠用於無鏈接的網絡服務(SOCK_DGRAM).若在 SOCK_DGRAM 套接字上調用 connect,全部發送報文目的地址設字爲 connect 調用中所制定的那個地址,這樣每次傳送報文時就不須要在提供地址,另外,僅接受來自指定地址的報文
  • 就收請求:            accept()
    函數 accept 鎖返回的文件描述符是套接字描述符,該描述符鏈接到調用 connect 的客戶端.這個新的套接字描述符和原始套接字(sockfd)具備相同的套接字類型和地址族.傳給 accept 的原始套接字沒有關聯到這個鏈接,而是繼續保持可用狀態並接受其餘鏈接請求.若是沒有鏈接請求等待處理,accept會阻塞直到一個請求到來,若是sockfd處於非阻塞模式,accept會返回-1並將errno設置爲 EAGAIN 或 EWOULDBLOCK
  • 數據通訊:            
    • send() \ recv()
    • write() \ read()
    • recvfrom() \ sendto()
  • 關閉套接字:           close()
  • 解析主機名和地址:        gethostbyname()/gethostbyaddr()
  • 得到到接字地址getpeername 得到以鏈接的套接字鏈接着的對方地址
  • getsockname 得到套接字綁定的地址
  • 查詢/設置套件子選項:         getsockopt()/setsockopt()
  • 資料

參考資料

相關文章
相關標籤/搜索