粉絲不過W
TCP/IP 協議概述
OSI 參考模型及 TCP/IP 參考模型
基於國際標準化組織(ISO), 共七層:應用層、表示層、會話層、傳輸層、網絡層、數據鏈路層,物理層
將 TCP/IP 的 7層協議模型 簡化爲 4 層,便更有利於實現和使用
OSI 模型和 TCP/IP 參考模型對應關係:
網絡接口層:負責將二進制流轉換爲數據幀,並進行數據幀的發送和接收
note: 數據幀是獨立的網絡信息傳輸單元
網絡層:負責將數據幀封裝成 IP 數據報,並運行必要的路由算法
傳輸層:負責端對端之間的通信會話連接與建立。傳輸協議的選擇根據數據傳輸方式而定
應用層:負責應用程序的網絡訪問,這裏通過端口號來識別各個不同的進程
TCP/IP 協議族
ARP:用於獲得同一物理網絡中的硬件主機地址
MPLS:多協議標籤協議
IP:負責在主機和網絡之間尋址和路由數據包
ICMP:用於發送報告有關數據包的傳送錯誤的協議
IGMP:被 IP 主機用來向本地多路廣播路由器報告主機組成員的協議
TCP:爲應用程序提供可靠的通信連接。適合於一次傳輸大批數據的情況。也適用於要求得到響應的應用程序
UDP:提供了無連接通信,且不對傳送包進行可靠的保證。適合於一次傳輸少量數據可靠性則由應用層來負責
TCP 和 UDP
傳輸層 TCP 和 UDP 協議
TCP
概述
TCP 向相鄰的高層提供服務
由於 TCP 的上一層是應用層,所以 數據傳輸實現了從一個應用程序到另一個應用程序的數據傳遞
打開 socket 來使用 TCP 服務,TCP 管理到其他 socket 的數據傳遞
三次握手協議
TCP 對話通過三次握手來初始化。目的:使數據段的發送和接收同步,告訴其他主機要一次可接收的數據量,並建立虛連接
三次握手的簡單過程:
初始化主機,用一個同步標誌置位的數據段發出會話請求
接收主機通過發回下列數據段表示回覆:同步標誌置位、即將發送的數據段的起始字節的順序號、應答並帶有將收到的下一個數據段的字節順序號
請求主機再回送一個數據段,並帶有確認順序號和確認號
TCP 三次握手協議流程:
TCP 實體採用的基本協議:滑動窗口協議
當發送方傳送一個數據報時,它將啓動計時器,當該數據報到達目的地後,接收方的 TCP 實體向回發送一個數據報,其中包含有一個確認序號,意思:要收到的下一個數據報的順序號。如 發送方的定時器在確認信息到達之前超時,那發送方會重發該數據報
TCP 數據報頭
TCP 數據報頭的格式:
源端口、目的端口:16 位長。標識 遠端和本地的端口號
序號:32 位長。標識 發送的數據報的順序
確認號:32 位長。希望收到的下一個數據報的序列號
TCP 頭長:4 位長。表明 TCP 頭中包含多少個 32 位字
6 位未用
ACK:ACK : 1 表明 確認號爲 合法。如 ACK 爲 0,那 數據報不包含確認信息,確認字段被省略
PSH:表示 帶有 PUSH 標誌的數據。接收方請求數據報 一到 便可送往應用程序,不用等到緩衝區裝滿時才傳送
RST:用於復位由於主機崩潰或其他原因而出現的錯誤的連接。還可以用於 拒絕非法的數據報或 拒絕連接請求
SYN:用於建立連接
FIN:用於釋放連接
窗口大小:16 位長。窗口大小字段:在確認了字節之後還可以發送多少個字節
校驗和:16 位長。是爲了確保高可靠性。校驗頭部、數據和僞 TCP 頭部之和
可選項:0 個或多個 32 位字。包括最大 TCP 載荷,窗口比例、選擇重發數據報等選項
UDP
概述
UDP :用戶數據報協議,一種無連接協議,因此不需要像 TCP 那樣通過三次握手來建立一個連接
UDP 數據包頭
源地址、目的地址:16 位長。標識出遠端和本地的端口號
數據報的長度:包括報頭和數據部分在內的總的字節數,報頭的長度是固定,該域:計算可變長度的數據部分(別名:數據負載)
協議的選擇
對數據可靠性的要求
對數據要求高可靠性的應用: TCP 協議,如驗證、密碼字段的傳送都是不允許出錯
對數據的可靠性要求不那麼高的應用: UDP 傳送
應用的實時性
由於 TCP 協議在傳送過程中要進行三次握手、重傳確認等手段來保證數據傳輸的可靠性,所以TCP 協議會有較大的時延,因此不適合對實時性要求較高的應用,如 VOIP、視頻監控。
UDP 協議:在這些應用中能發揮很好的作用
網絡的可靠性
TCP 協議的提出:解決網絡的可靠性問題,它通過各種機制來減少錯誤發生的概率
在網絡狀況不是很好的情況下: TCP 協議,如 廣域網
在網絡狀況很好的情況下: UDP 協議,減少網絡負荷,如 局域網
網絡基礎編程
socket 概述
socket 定義
Linux 網絡編程通過 socket 接口來進行
socket 接口:一種特殊的 I/O,一種文件描述符
每一個 socket 都用一個半相關描述{ 協議,本地地址、本地端口 }來表示
該函數返回一個整型的 socket 描述符,隨後的連接建立、數據傳輸等操作都是通過 socket 來實現
一個完整的套接字用一個相關描述{ 協議,本地地址、本地端口、遠程地址、遠程端口 }
socket 類型
流式 socket(SOCK_STREAM)
流式套接字提供可靠的、面向連接的通信流;使用 TCP 協議,從而保證了數據傳輸的正確性和順序性
數據報 socket(SOCK_DGRAM)
數據報套接字定義了一種無連接的服務,數據通過相互獨立的報文進行傳輸,是無序的,且不保證是可靠、無差錯
原始 socket
原始套接字允許對底層協議如 IP 或 ICMP 進行直接訪問,它功能強大但使用不便,主要用於一些協議的開發
地址及順序處理
數據結構介紹
/* 等效,可相互轉化 */
struct sockaddr
{
unsigned short sa_family; /* 地址族 */
char sa_data[14]; /* 14 字節的協議地址,含該 socket 的 IP 地址、端口號 */
};
struct sockaddr_in
{
short int sa_family; /* 地址族 */
unsigned short int sin_port; /* 端口號 */
struct in_addr sin_addr; /* IP 地址 */
unsigned char sin_zero[8]; /*填充 0 以保持與 struct sockaddr 同樣大小*/
};
結構字段
#include <netinet/in.h>
/*
*AF_INET: IPv4 協議
*AF_INET6:IPv6 協議
*AF_LOCAL:UNIX 域協議
*AF_LINK: 鏈路地址協議
*AF_KEY: 密鑰套接字(socket)
*/
unsigned short sa_family;
數據存儲優先順序
函數說明
計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先
Internet 上數據以高位字節優先順序在網絡上傳輸
兩個字節存儲優先順序進行相互轉化時,需用到:htons、ntohs、htonl、ntohl
h 代表 host,n 代表 network,s 代表 short,l 代表 long
通常 16 位的 IP 端口號用 s 代表,而 IP 地址用 l 來代表
#include <netinet/in.h>
/*
*parameter:
* host16bit:主機字節序的 16bit 數據
* host32bit:主機字節序的 32bit 數據
* net16bit: 網絡字節序的 16bit 數據
* net32bit: 網絡字節序的 32bit 數據
*return:
* 成功:轉換的字節序
* 出錯:-1
*/
uint16_t htons(unit16_t host16bit)
uint32_t htonl(unit32_t host32bit)
uint16_t ntohs(unit16_t net16bit)
uint32_t ntohs(unit32_t net32bit)
地址格式轉化
函數說明
用戶在表達地址時採用的是點分十進制表示的數值(或 以冒號分開的十進制IPv6 地址),而 使用的 socket 編程中所使用的是二進制值
#include <arpa/inet.h>
/*
*function:
* 點分十進制地址映射爲二進制地址
*parameter:
* family:
* AF_INET: IPv4 協議
* AF_INET6:IPv6 協議
* strptr: 轉化的值
* addrptr: 轉化後的地址
*return:
* 成功:0
* 失敗:-1
*/
int inet_pton(int family, const char *strptr, void *addrptr)
/*
*function:
* 二進制地址映射爲點分十進制地址
*parameter:
* family:
* AF_INET: IPv4 協議
* AF_INET6:IPv6 協議
* addrptr:轉化後的地址
* strptr: 要轉化的值
* Len: 轉化後值的大小
*return:
* 成功:0
* 失敗:-1
*/
int inet_ntop(int family, void *addrptr, char *strptr, size_t len)
名字地址轉化
函數說明
實現 IPv4 和 IPv6 的地址和主機名之間的轉化
struct hostent
{
char *h_name; /* 正式主機名 */
char **h_aliases; /* 主機別名 */
int h_addrtype; /* 地址類型 */
int h_length; /* 地址長度 */
char **h_addr_list; /* 指向 IPv4 或 IPv6 的地址指針數組 */
};
#include <netdb.h>
/*
*ai_flags:
* AI_PASSIVE: 該套接口是用作被動地打開
* AI_CANONNAME:通知 getaddrinfo 函數返回主機的名字
*family:
* AF_INET: IPv4 協議
* AF_INET6:IPv6 協議
* AF_UNSPE:IPv4 或 IPv6 均可
*ai_socktype:
* SOCK_STREAM:字節流套接字 socket(TCP)
* SOCK_DGRAM: 數據報套接字 socket(UDP)
*ai_protocol:
* IPPROTO_IP: IP 協議
* IPPROTO_IPV4:IPv4 協議
* IPPROTO_IPV6:IPv6 協議
* IPPROTO_UDP: UDP
* IPPROTO_TCP: TCP
*note:
* 服務器端, ai_flags: AI_PASSIVE,主機名 nodename: NULL
* 客戶端,,ai_flags != AI_PASSIVE,且 主機名 nodename 和服務名 servname(端口)!= 空
*/
struct addrinfo
{
int ai_flags; /* AI_PASSIVE,AI_CANONNAME */
int ai_family; /* 地址族 */
int ai_socktype; /* socket 類型 */
int ai_protocol; /* 協議類型 */
size_t ai_addrlen; /* 地址長度 */
char *ai_canoname; /* 主機名 */
struct sockaddr *ai_addr; /* socket 結構體 */
struct addrinfo *ai_next; /* 下一個指針鏈表 */
};
函數格式
#include <netdb.h>
/*
*function:
* 主機名轉化爲 IP 地址
*parameter:
* hostname:主機名
*return:
* 成功:hostent 類型指針
* 失敗:-1
*/
struct hostent *gethostbyname(const char *hostname)
#include <netdb.h>
/*
*function:
* 自動識別 IPv4 地址和 IPv6 地址
*parameter:
* hostname:主機名
* service: 服務名或十進制的串口號字符串
* hints: 服務線索函數傳入值
* result: 返回結果
*return:
* 成功:0
* 出錯:-1
*/
int getaddrinfo(const char *hostname,
const char *service,
const struct addrinfo *hints,
struct addrinfo **result)
例:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
struct addrinfo hints, *res = NULL;
int rc;
memset(&hints, 0, sizeof(hints));
/*設置 addrinfo 結構體中各參數*/
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
/*調用 getaddinfo 函數*/
rc = getaddrinfo("127.0.0.1", "123", &hints, &res);
if(rc != 0)
{
perror("getaddrinfo");
exit(1);
}
else
{
printf("getaddrinfo success\n");
}
}