【網絡編程套接字】IP地址 & 端口 & TCP 及 UDP 協議實現

IP地址

作用:在網絡當中唯一標識一臺主機

本質:IPV4:uint32_t 類型的值,最大的範圍是 42 億多,採用點分十進制來表示 IP 地址(例:172.16.99.129),每一個字節能表示的最大數據爲 255

目的 IP 地址:標識數據去向
源 IP 地址:標識數據來向

IPV6:16個字節的整數,128 位的無符號整型數據,IPV6 天然不向下兼容 IPV4

port

作用:在一臺主機當中標識一個進程

本質:uint16_t 端口的範圍 0 - 65535,其中 0 - 1023 是知名端口,例:mysql – 3306,oracle – 1521

使用:網絡當中的程序,通信的時候,都是需要使用端口進行通信
客戶端:主動發起請求的一方,被稱爲客戶端
服務端:被動的在固定位置上接受請求的一方是服務器

問題:爲什麼不使用 PID 作爲請求與在主機當中查找進程的標準?
答案:由於進程的關閉和重啓會導致每次的 PID 不一樣,對用戶訪問造成了困擾

在網絡程序當中存在兩個端口
源端口:src_port,目的端口:dest_port

在網絡程序當中的一條數據當中存在一個 5 元組
src_ip,src_port,dest_ip,dest_port,protocol(協議)

字節序

大端字節序:低地址存高位
小端字節序:低地址存低位
網絡字節序:CPU 對內存當中的數據進行存取的順序,網絡的通信標準是大端字節序
主機字節序:當前計算機的字節序,一般情況下,x86_64 機器都是小端機器
大小端機器需要進行通信的時候,都是需要遵循網絡字節序(大端字節序),小端字節序在網絡通信時則需要進行大小端轉換

uint32_t htonl(uint32_t hostlong); 將32位的主機字節序的數據轉換成爲網絡字節序
h:host 主機
to:轉換
n:network
l:32位的整型數據
uint32_t ntohl(uint32_t netlong); 將32位的網絡字節序的數據轉換成爲主機字節序
uint16_t htonl(uint16_t hostlong); 將32位的主機字節序的數據轉換成爲網絡字節序
uint16_t ntohl(uint16_t netlong); 將32位的網絡字節序的數據轉換成爲主機字節序

傳輸層的兩個協議

TCP(Transmission Control Protocol)傳輸控制協議
面向連接、可靠傳輸、字節流服務
UDP(User Datagram Protocol) 用戶數據報協議
無連接、不可靠、面向數據報

UDP 套接字編程的流程

在這裏插入圖片描述
理解數據的緩衝區
在這裏插入圖片描述

UDP 編程的接口

  1. 創建套接字
int socket(int domain, int type, int protocol)
    domain:地址域,傳入的是協議的版本
        網絡層:AF_INEF --> ipv4 版本的 ip 協議
                      AF_INEF6 --> ipv6 版本的 ip 協議
    type:套接字的類型
        傳輸層:tcp / udp
            SOCK_STREAM:流式套接字 --> 默認對應的協議:tcp,不支持 udp
            SOCK_DGRAM:數據報套接字 --> 默認對應的協議:udp,不支持 tcp
    protocol:協議類型
        0:採用套接字對應的默認類型
        IPPROTO_TCP --> 6
        IPPROTO_UDP --> 17
        返回值:返回套接字的操作句柄,其實就是一個文件描述符,一般稱之爲套接字描述符
  1. 綁定地址信息(客戶端不推薦綁定地址信息)
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen)
    sockfd:套接字操作句柄,socket 函數的返回值
    addr:
        struct sockaddr
        {
            sa_family_t sa_family;//填充地址域的,當前協議版本,佔兩個字節
            char sa_data[14];//填充地址信息,14個字節
            //ipv4:port(2) + ip(4) + 填充信息(8)
        }

ipv4 結構體在這裏插入圖片描述
bind 函數可以兼容不同協議的地址信息,在使用 bind 函數的時候,進行強轉就可以,sa_family_t 表示哪一個協議填充了地址類型,方便內核去解析後面的 14 個字節

問題:爲什麼要傳入地址信息的長度?
答案:爲了兼容地址信息的結構體長度已經超過了 sockaddr 定義的 16 個字節,例如:ipv6 版本的 ip 協議,單單是 ip 地址的長度就佔 16 字節,把當前地址信息的長度傳遞給內核,內核在解析傳入的地址信息的時候,就會按照傳入的長度進行解析

  1. 發送數據
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
    sockfd:套接字的操作句柄
    buf:要發送什麼數據
    len:數據的長度
    flags:0:阻塞發送
    dest_addr:目標主機的地址信息(目的 ip + 目的 port)
    addrlen:地址信息長度
  1. 接收接口
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
      sockfd:套接字的操作句柄
      buf:從接收緩衝區拿到的數據存放位置
     len:接收 buf 定義的最大長度,預留 \0 的位置
     flags:0:阻塞接收
     src_addr:源主機的地址信息(ip + port)
     addrlen:地址信息長度,輸入輸出型參數,作爲入參,指定傳入源主機地址信息結構體的長度,作爲出參,將實際的地址信息長度返回
  1. 關閉套接字
close(int sockfd);

TCP 套接字編程的流程

在這裏插入圖片描述
連接流程
在這裏插入圖片描述

TCP 編程的接口

  1. 監聽接口
int listen(int sockfd, int backlog)
    sockfd:指定套接字操作句柄
    backlog:已完成連接隊列的大小
         同一時刻,服務端最大的併發連接數

監聽的時候,一旦有新的連接到來,OS 會對新的連接分配一個 socket,進行 1 對 1 服務

  1. 接收連接
int accept(int sockfd, struct sockaddr* addr, socklen_t addrlen);
    sockfd:偵聽 socket
    addr:客戶端地址信息
    addrlen:客戶端地址信息的長度,輸入輸出型參數
    返回值:返回的是操作系統內核創建的新的 socket 文件描述符
  1. 客戶端的發起連接的接口
int connect(int sockfd, const sockaddr* addr, socklen_t addrlen)
    sockfd:套接字描述符
    addr:服務器的地址信息(當前客戶端需要連接哪一個服務端的地址信息)
    addlen:地址信息長度
  1. 發送數據
ssize_t send(int sockfd, void* buf, size_t len, int flags);
    sockfd:accept函數返回的操作系統內核新創建的 socket 句柄
    buf:要給對端發送什麼數據
    len:發送數據的長度
    flags:0:阻塞發送
    dest_addr:目標主機的地址信息(目的 ip + 目的 port)
    addrlen:地址信息長度
  1. 接收接口
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
      sockfd:accept函數的返回值
      buf:從接收緩衝區拿到的數據存放位置
     len:接收 buf 定義的最大長度,預留 \0 的位置
     flags:0:阻塞接收
                MSG_PEEK:探測接收(不會將接收緩衝區當中的數據擦除,而是拷貝接收緩衝區的數據,接收緩衝區當中還是原有的數據)
  1. 關閉套接字
close(int sockfd);

解決 TCP 通信阻塞

創建執行流解決
一個執行流專門監聽,獲取連接,每新獲取一個連接,就爲該連接創建一個執行流去爲客戶端服務
多進程、多線程
在這裏插入圖片描述

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