linux 網絡編程 基礎


 網絡編程基礎

  • 套接字編程需要指定套接字地址作為參數,不同的協議族有不同的地址結構,比如以太網其結構為sockaddr_in。
  • 通用套接字:
    struct sockaddr {
        sa_family_t    sa_family;    /* address family, AF_xxx 16Bytes  */
        char        sa_data[14];    /* 14 bytes of protocol address    */
    };
    
  • 實際使用的套接字結構

  • 以bind函數為例:

    bind(int  sockfd, //套接字文件描述符

       struct sockaddr *uaddr,//套接字結構地址

       int addr_len)//套接字地址結構長度  

        使用struct    sockaddr  為通用結構體,在以太網中,一般使用結構 sockaddr_in

 

  • 以太網套接字
/* Internet address. */
struct in_addr {
	__be32	s_addr;
};
/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ __be16 sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; };
  • 結構 sockaddr 和結構 sockaddr_in的關

第二章:TCP網絡編程流程

tcp網絡編程主要采取C/S模式,即客戶端(C)、服務器(S)模式

  • 創建網路套接字接口函數socket

int socket (int family, int type, int protocol)

int family

  • AF_UNIX : Sockets for interprocess communication in the local computer.
  • AF_INET : Sockets of the TCP/IP protocol family based on the Internet Protocol Version 4
  • AF_INET6 : TCP/IP protocol family based on the new Internet Protocol, Version 6.
  • AF_IPX : IPX protocol family.
  •  

int type

  • SOCK_STREAM (stream socket) specifies a stream-oriented, reliable, in-order full duplex connection between two sockets.
  • SOCK_DGRAM (datagram socket) specifies a connectionless, unreliable datagram service, where packets may be transported out of order.
  • SOCK_RAW (raw socket).
  •  

int protocol

  • TCP is always selected for the SOCK_STREAM socket type, and UDP is always used as the transport protocol for  SOCK_DGRAM
  •  

int bind(int sockfd,  struct sockaddr *uaddr,  socketlen_t  uaddrlen)

  • sockfd為 socket()函數創建返回的fd
  • uaddr 指向一個包含了ip地址 端口等信息
  • uaddrlen 是sockaddr的長度
  • bind 可以指定Ip地址或者端口  可以都指定
  •  

int listen(int sockfd, int backlog)

  • sockf為socket創建成功返回的fd
  • backlog 表示在accept 函數處理之前在等待隊列中允許最多的客戶端個數

int accept(int sockfd,  struct sockaddr *addr,  socketlen_t * addrlen)

  • accept 函數可以得到成功連接客戶端的ip地址、端口信息和協議族等信息
  • accpet返回值是新連接客戶端套接字的描述符

 

數據的IO和復用

常用的數據I/O函數有recv/send()  readv/writev  recvmsg/sendmsg

int recv(int sockfd, void *buf, size_t len, int flag)

  • recv 函數的參數flag用於設置接收數據的方式
  • recv函數返回成功接收到的字節數,錯誤時返回-1

int send(int sockfd,  const void *buf,  size_t len,  int flags)

  • send函數成功發送的字節數,發生錯誤是返回-1

int recvmsg (int sockfd, struct msghdr *msg, int flags)

  • recvmsg 表示從sockd 中接收數據放在緩沖區,其操作方式由flags指定
  • 其返回值表示成功接收到的字節數,-1時表示發生錯誤
  • 當對端使用正常方式關閉連接時,返回值為0,如調用close

  • flags含義:

I/O模型

I/O復用

 select函數簡介

int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

  • maxfdp1:指定待測試的描述符個數,它的值是待測試的最大描述符加1
  • readset、writeset、exceptset:指定讓內核測試讀、寫、異常條件的描述符
  • timeout:最長等待時間
  • timeout參數的三種可能:
    a.設為空指針:永遠等待下去,僅在有描述符就緒時才返回
    b.正常設置timeout,在不超過timeout設置的時間內,在有描述符就緒時返回
    c.將timeout.tv_sec和timeout.tv_usec都設為0:檢查描述符后立即返回(輪詢)

非阻塞I/O

  • 非阻塞connect 以及非阻塞accept

  • 以及調用select 的非阻塞I/O

進程間通信

  •  Unix域協議

  

#define UNIX_PATH_MAX 128
struct sockaddr_un{
sa_family_t sun_family; /* AF_UNIX 或者 AF_LOCAL */
char sun_path[UNIX_PATH_MAX]; /* path name */
};
使用流程分析:
一、服務器端通信過程分析
服務器端基本遵循面向連接的socket數據流通信過程。
1、調用socket()函數,建立socket對象,指定通信協議為AF_UNIX。
2、調用bind()函數,將創建的socket對象與bind()函數產生的那個socket類型的文件server_socket P綁定。
UNIX Domain Socket與網絡socket編程最明顯的不同在於地址格式不同,其地址用結構體sockaddr_un表示, 網絡編程的socket地址是IP地址加端口號,而UNIX Domain Socket的地址是一個socket類型的文件在文件系統中的路徑,這個socket文件由bind()調用創建,如果調用bind()時該文件已存在,且已被link,則bind()錯誤返回。一個套接字只能綁定到一個路徑上,同樣的,一個路徑也只能被一個套接字綁定。
sockaddr_un結構的sun_path成員包含一路徑名,當我們將一地址綁定至UNIX域套接字時,系統用該路徑名創建一類型為S_IFSOCK的文件。該文件僅用於向客戶端進程告知套接字名字,該文件不能打開,也不能由應用程序用於通信,當關閉套接字時,並不自動刪除該文件,所以我們必須確保在應用程序終止前,對該文件執行解除鏈接操作(unlink(path)),或刪除該文件。
struct sockaddr_un結構有兩個參數:sun_family、sun_path。sun_family只能是AF_LOCAL或AF_UNIX;而sun_path就是本地文件的路徑。存放文件路徑的sun_path數組必須以空字符(即’\0’字符)結尾
3、調用listen()函數,使socket對象處於監聽狀態,並設置監聽隊列大小。
4、服務器端監聽到該請求,在客戶端發出請求后,accept()函數接收請求,返回新文件描述符,從而建立連接。
5、服務器端調用read()函數接收數據(開始處於阻塞狀態,等待客戶端發送數據,因此,客戶端在編程是需要首先發送數據,接收到數據后,輸出接收到的數據)。
6、調用write()函數發送數據到客戶端。
7、通信完成后,調用close()函數關閉socket對象;unlink(sockaddr_un.sun_path)。
 
二、客戶端通信過程分析
客戶端基本遵循面向連接的socket數據流通信過程。
1、調用socket()函數,建立socket對象,指定相同通信協議。
2、客戶端調用connect()函數,向服務器端發起連接請求。
3、在得到服務器端允許后,首先調用write()函數向服務器端發送消息(因服務器端循環體中首先是接收數據)。
4、調用read()函數接收數據。
5、通信完成后,調用close()函數關閉socket對象。
 

管道

#include <unistd.h>
int pipe(int pipefd[2]);

成功調用 pipe 函數之后,可以對寫入端描述符 pipefd[1] 調用 write ,向管道里面寫入數據,比如

write(pipefd[1],wbuf,count);

一旦向管道的寫入端寫入數據后,就可以對讀取端描述符 pipefd[0] 調用 read

管道有如下三條性質:
· 只有當所有的寫入端描述符都已關閉,且管道中的數據都被讀出,對讀取端描述符調用 read 函數
才會返回 0 (即讀到 EOF 標志)。
· 如果所有讀取端描述符都已關閉,此時進程再次往管道里面寫入數據,寫操作會失敗, errno 被設
置為 EPIPE ,同時內核會向寫入進程發送一個 SIGPIPE 的信號。
· 當所有的讀取端和寫入端都關閉后,管道才能被銷毀

這種管道因為沒有實體文件與之關聯,適用於有親緣關系的任意兩個進程之間通信

命名管道 FIFO

命名管道就是為了解決無名管道的這個問題而引入的。 FIFO 與管道類似,最大的差別就是有實體
文件與之關聯。由於存在實體文件,不相關的沒有親緣關系的進程也可以通過使用 FIFO 來實現進程之
間的通信;

從外表看,我是一個 FIFO 文件,有文件名,任何進程通過文件名都可以打開我

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

一旦 FIFO 文件創建好了,就可以把它用於進程間的通信了。一般的文件操作函數如 open 、 read 、 write 、 close 、 unlink 等都可以用在 FIFO 文件
上; 對 FIFO 文件推薦的使用方法是,兩個進程一個以只讀模式( O_RDONLY )打開 FIFO 文件,另一個以只寫模式( O_WRONLY )打開 FIFO 文
件。這樣負責寫入的進程寫入 FIFO 的內容就可以被負責讀取的進程讀到,從而達到通信的目的

 System V 消息隊列 信號量 共享內存

 

管道和 FIFO 都是字節流的模型,這種模型不存在記錄邊界,如果從管道里面讀出 100
個字節,你無法確認這 100 個字節是單次寫入的 100 字節,還是分 10 次每次 10 字節寫入的,你也無法知
曉這 100 個字節是幾個消息。管道或 FIFO 里的數據如何解讀,完全取決於寫入進程和讀取進程之間的約
定;System V 消息隊列是優於管道和 FIFO 的。原因是消息隊列機
制中,雙方是通過消息來通信的,無需花費精力從字節流中解析出完整的消息;

System V 消息隊列比管道或 FIFO 優越的第二個地方在於每條消息都有 type 字段,消息的讀取進程可
以通過 type 字段來選擇自己感興趣的消息,也可以根據 type 字段來實現按消息的優先級進行讀取,而不
一定要按照消息生成的順序來依次讀取

 

 

一般來說,信號量是和某種預先定義的資源相關聯的。信號量元素的值,表示與之關聯的資源的個數

一旦將信號量和某種資源關聯起來,就起到了同步使用某種資源的功效

 

共享內存是所有 IPC 手段中最快的一種。它之所以快是因為共享內存一旦映射到進程的地址空間,
進程之間數據的傳遞就不須要涉及內核了。
回顧一下前面已經討論過的管道、 FIFO 和消息隊列,任意兩個進程之間想要交換信息,都必須通
過內核,內核在其中發揮了中轉站的作用:
· 發送信息的一方,通過系統調用( write 或 msgsnd )將信息從用戶層拷貝到內核層,由內核暫存這
部分信息。
· 提取信息的一方,通過系統調用( read 或 msgrcv )將信息從內核層提取到應用層

 

 

 

經驗:

  • epoll 或者 select 處理事件時,可讀事件時,read返回值-1,如果errno不為EAGAIN,可以認為失敗,並關閉fd。read返回0,說明對方斷開連接,此時也需要關閉fd。如果鏈路斷了,如拔掉網線,需要是用keepalive來觸發可寫事件
  • 本地UDP發送過快也是會丟包的。非阻塞情況下的unix domain socket哪怕是STREAM的也是會丟包的
  • 使用unix socket通信相比於本地udp通信減少了校驗和的計算。使用阻塞函數時,unix domain socket可以保證不丟包不亂序,但是當發送緩沖區滿了的話則會阻塞。使用非阻塞操作時經測試會丟包
  • 使用setsockopt設置發送緩沖時,SO_RCVBUF和SO_SNDBUF的最大值受系統設置限制,可以使用SO_RCVBUFFORCE和SO_SNDBUFFORCE來無視系統設置
  • SIGPIPE信號,網絡編程時一定要處理該信號。同樣一般要設置的還有SO_REUSEADDR。當客戶端close連接時,若server繼續發送數據,會收到RST,繼續寫就會SIGPIPE
  • 網絡編程對事件進行封裝,提供注冊回調函數,在可讀、可寫時進行函數調用。一般用法,針對非阻塞情況,初始化時將可讀事件注冊,需要寫的時候先寫,寫不下去的時候(errno=EAGAIN)再掛上可寫事件,只要發送緩沖區還有空間,就是可寫的
  • 基於事件的編程框架,需要記錄最后一次成功read或write的時間,如果idletime大於閾值,直接close
    • 服務器編程可以設置最大的fd個數,然后一次性申請FileEvent數組,之后由fd到事件查詢代價O(1)
      • 針對非阻塞socket,connect返回EINPROGRESS時需要將fd加到可寫事件監視集合中,當select()或者poll()返回可寫事件時,需要用getsockopt去讀SOL_SOCKET層面的SO_ERROR選項,SO_ERROR為0表示連接成功,否則為連接失敗
      • epoll ET模式的處理方式。讀:只要可讀就一直讀,一直讀到返回0,或者error = EAGAIN。寫:只要可寫就一直寫,知道數據發送完,或者errno = EAGAIN
      • socket read緩沖區最大值TCP可查看”/proc/sys/net/ipv4/tcp_rmem”, udp 65536
      • 實現定時器時通常辦法是select/poll/epoll接口,精度毫秒級;還有就是新增的系統調用timerfd_create 把時間變成了一個文件描述符,該“文件”在定時器超時的那一刻變得可讀,高於poll的精度
      • 在主動關閉連接時,可以先shutdown(fd, SHUT_WR)關閉寫端,等對方close時再關閉讀端。這樣子的好處是如果對方已經發送了一些數據,這些數據不會漏收。這就要求對端在read返回0之后關閉連接或者shutdown寫端
      • 網絡編程一種比較好的模型是“one loop per thread”,如果事件庫不是線程安全的,則需要使用pipe或者
      • socketpair通知,子線程接受到通知(fd可讀)后處理,kernel 2.6.22加入了eventfd,是更好的通知方法
      • TCP Nagle算法和TCP Delayed Ack機制可能會導致網絡延時(Linux 40ms, Windows 200ms),最容易產生問題的就是"Write-Write-Read”這種模型,發送端的Nagle算法和接收端的Delayed Ack會導致一直等到接收端delayed ack超時后數據才發送出去
      • accept返回EMFILE,進程描述符用完了,無法創建新的socket,也無法close連接,會導致不斷通知該可讀事件,程序busy loop,cpu 100%,解決方法是事先准備一個nullfd=open(“/dev/null”),close該fd,accept,close socket,然后再nullfd=open(“/dev/null”),缺點是該方法線程不安全,多線程accept可能導致nullfd用於新socket創建,然后又處於busy loop中

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM