Linux网络编程 | socket选项设定 及 网络信息API
文章目錄
- 讀取和設置 socket 選項
- SO_REUSEADDR
- SO_RCVBUF 和 SO_SNDBUF
- SO_RCVLOWAT 和 SO_SNDLOWAT
- SO_LINGER 選項
- 網絡信息API
- gethostbyname 和 gethostbyaddr
- getservbyname 和 getservbyport
- getaddrinfo
- getnameinfo
讀取和設置 socket 選項
正如 fcntl 系統調用是控制文件描述符屬性的通用 POSIX 方法;socket文件描述符的屬性也有兩個系統調用專門 讀取 和 設置 :
#include<sys/socket.h> int getsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len); int setsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len); // sockfd 指定被操作的socket。 // level 指定操作哪個協議的選項(屬性),如:IPv4、IPv6、TCP等。 // option_name 指定選項的名字。 // option_value 和 option_len 參數分別指定操作選項值和長度。 // 成功返回0,失敗返回-1并置errno。 socket 選項| SOL_SOCKET | SO_DEBUG | int | 打開調試信息 | YES |
| (通用 socket 選項,與協議無關) | SO_REUSEADDR | int | 重用本地地址。如:可以令服務器處于 TIME_WAIT 的端口立刻被使用。 | |
| SO_TYPE | int | 獲取 socket 類型 | ||
| SO_ERROR | int | 獲取并清除 socket 錯誤狀態 | ||
| SO_DONTROUTE | int | 不查看路由表,直接將數據發送給本地局域網內的主機。類同 send 系統調用的 MSG_DONTROUTE。 | YES | |
| SO_RCVBUF | int | TCP接收緩沖區大小(最小值是256字節) | YES | |
| SO_SNDBUF | int | TCP發送緩沖區大小(最小值是2048字節) | YES | |
| SO_RCVLOWAT | int | TCP接收緩沖區低水位標記 | YES | |
| SO_SNDLOWAT | int | TCP發送緩沖區低水位標記 | YES | |
| SO_RCVLOWAT | int | 接收數據超時 | ||
| SO_SNDLOWAT | int | 發送數據超時 | ||
| SO_KEEPALIVE | int | 發送周期性保活報文以維持連接 | YES | |
| SO_OBBINLINE | int | 接收到的帶外數據將 在線存留 在普通數據的輸入隊列中,此時我們不能使用帶 MSG_OOB 的讀操作來讀取帶外數據,而應該像讀取普通數據那樣讀取帶外數據。 | YES | |
| SO_LINGER | intger | 若有數據待發送,則延遲關閉。 | YES | |
| IPPROTO_IP | IP_TOS | int | 服務類型 | |
| (IPv4選項) | IP_TTL | int | 存活時間 | |
| IPPROTO_IPV6 | IPV6_NEXTHOP | sockaddr_in6 | 下一跳IP地址 | |
| (IPv6選項) | IPV6_RECVPKTINFO | int | 接受分組信息 | |
| IPV6_DONTFRAG | int | 禁止分片 | ||
| IPV6_RECVTCLASS | int | 接受通信類型 | ||
| IPPROTO_TCP | TCP_MAXSEG | int | TCP最大報文段大小 | YES |
| (TCP選項) | TCP_NODELAY | int | 禁止Nagle算法 | YES |
對 服務器 而言,部分socket選項 只能在調用 listen 系統調用前 針對 socket 設置才有效。這是因為連接 socket 只能由 accept 調用返回,而 accept 從 listen 監聽隊列 中接受的連接至少是個 半連接 ,這說明 服務器 已經往 客戶端 上發送出了 TCP同步報文段(執行完了三次握手中的前兩次)。但 部分 socket 選項只能在 TCP同步報文中設置 ,如:TCP最大報文段選項。對此有兩種解決方案:
- 對于服務器而言,執行 listen系統調用 時設置這些 socket選項,那么 accept 返回的 連接socket 將自動繼承這些選項(注1)。
- 對于客戶端而言,這些 socket選項 應該在調用 connect函數 之前設置,因為 connect 調用成功之后三次握手已完成。
注1
這些選項包括:SO_DEBUG、SO_DONTROUTE、SO_KEEPALIVE、SO_LINGER、SO_OBBINLINE、SO_RCVBUF、SO_RCVLOWAT、TCP_MAXSEG、TCP_NODELAY 。
SO_REUSEADDR
服務器程序可以通過設置本選項來強制使用被處于 TIME_WAIT 狀態的連接占用的 socket 地址。此外,我們也可以通過修改內核參數 /proc/sys/net/ipv4/tcp_tw_recycle 來快速回收被關閉的 socket,甚至使 TCP 連接根本就不進入 TIME_WAIT 狀態。
int sock = socket( PF_INET, SOCK_STREAM, 0); // TCP協議,IPv4版本,基于流服務,0:使用默認協議 assert( sock >= 0); // 檢測創建sock是否成功 int reuse = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse) ); // 操作描述符為sock的socket套接字,操作的屬性是通用socket選項 // 選項類型為SO_REUSEADDR,SO_REUSEADDR的數據類型為int // 1即為啟用SO_REUSEADDR選項SO_RCVBUF 和 SO_SNDBUF
這兩個選項分別用來表示 TCP 接收緩沖區大小和發送緩沖區大小。不過,當我們 通過setsockopt 來設置 TCP 的接收、發送緩沖區大小時,系統都會將其值加倍,并且不小于某個值。
一般來講,TCP 接收緩沖區的最小值是 256 字節,發送緩沖區的最小值是 2048 字節(不同操作系統可能有差異)。我們可以直接修改內核參數 /proc/sys/net/ipv4/tcp_rmem 和 /proc/sys/net/ipv4/tcp_wmem 來強制緩沖區沒有最小值限制。
/* 先設置 TCP 接收緩沖區的大小,然后立即讀取 */ setsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof(recvbuf) ); getsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, ( socklen_t* )&len ); // sock 為目標套接字的文件描述符 // recvbuf 為設定的緩沖區大小 // len=sizeof( recvbuf );SO_RCVLOWAT 和 SO_SNDLOWAT
該兩個選項分別表示接收、發送緩沖區的低水位標記。一般被 I/O復用系統調用 用來判斷 socket 是否可讀或可寫:
- 當 接收緩沖區中 可讀數據的總數大于其低水位標記時,I/O復用系統調用 將通知應用程序可以從對應的 socket 上讀取數據
- 當 發送緩沖區 中的空閑空間大于其低水位標記時,I/O復用系統調用 將通知應用程序可以往對應的 socket 上寫入數據。
默認情況下,兩者均為 1字節 。
SO_LINGER 選項
該選項用于控制 close系統調用 在關閉 TCP 連接時的行為。默認情況下,當我們使用 close系統調用 來關閉一個 socket 時,close 將立即返回,TCP 模塊負責把該 socket 對應的 TCP 發送緩沖區中殘留的數據發送給對方。
設置(獲取)SO_LINGER 選項的值時,我們需要給 setsockopt(getsockopt)系統調用傳遞一個 linger 類型的結構體:
#include <sys/socket.h>struct linger{int l_onoff; // 開啟(非0)/關閉(0)該選項int l_linger; // 滯留時間 };根據 linger 結構體中兩個成員變量的不同值,close 系統調用可能產生如下行為:
- l_onoff 等于 0。 此時 SO_LINGER 選項不起作用,close 用默認行為來關閉 socket。
- l_onoff 不為 0,l_linger 等于 0。 此時 close系統調用 立即返回,TCP模塊 將丟棄被關閉的 socket 對應的 TCP發送緩沖區 中殘留的數據,同時給對方發送一個 復位報文段。這為服務器提供了異常終止一個連接的方法。
- l_onoff 不為 0,l_linger 大于 0。 此時 close 的行為取決于兩個條件:
- 被關閉的 socket 對應的 發送緩沖區 中是否還有殘留的數據;
- 該 socket 是阻塞的還是非阻塞的,阻塞: close 將等待一段長為 l_linger 的時間,直到 TCP模板 發送完所有殘留數據并得到對方的確認。如果沒發送完并得到確認,則 close 返回 -1 并設置 errno 為 EWOULDBLOCK。非阻塞: close 將立即返回,此時需要根據其 返回值 和 errno 來判斷殘留數據是否發送完畢。
網絡信息API
gethostbyname 和 gethostbyaddr
- gethostbyname: 根據主機名稱獲取主機的完整信息。通常先在本地的 /etc/hosts 配置文件中查找主機,沒有找到再去訪問 DNS 服務器。
- gethostbyaddr: 根據IP地址獲取主機的完整信息。
兩者的返回類型都是 hostent 結構體類型的指針:
#include<netdb.h> struct hostent{char* h_name; // 主機名char** h_aliases; // 主機別名列表,可有多個int h_addrtype; // 地址類型(地址族)int h_length; // 地址長度char** h_addr_list; // 按網絡字節序列出的主機IP地址列表 };getservbyname 和 getservbyport
根據名稱/端口號獲得某個服務的完整信息。實際上都是通過讀取 /etc/services 文件來獲取服務的信息的。
#include<netdb.h> struct servent* getservbyname( const char* name, const char* proto ); struct servent* getservbyport( int port, const char* proto ); // name 目標服務的名字 // port 目標服務對應的端口號 // proto 服務類型,tcp表流服務、udp表數據報服務、NULL表獲取所有類型的服務兩者的返回類型都是 servent 結構體類型的指針:
#include<netdb.h> struct servent{char* s_name; // 服務名稱char** s_aliases; // 服務別名列表,可有多個int s_port; // 端口號char* s_proto; // 服務類型,通常是 tcp 或 udp };不可重入
gethostbyname、gethostbyaddr、getservbyname 和 getservbyport 都是不可重入的,即非線程安全的。但是 netdb.h 頭文件給出了它們的可重入版本:在原函數名尾部加上 _r(re-entrant)。
getaddrinfo
既能通過主機名獲取 IP 地址(內部使用的是 gethostbyname),也能通過服務名獲取端口號(內部使用的是 getservbyname),是否可重入取決于內部調用的函數( gethostbyname、 getservbyname )是否是它們的可重入版本。
#include<netdb.h> int getaddrinfo( const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result ); // hostname:可以接收主機名(服務名)/字符串表示的IP地址【IPv4使用點分十進制字符串、IPv6使用十六進制字符串】 // service:可以接收服務名/字符串表示的十進制端口號 // hints:可以為NULL,表示允許反饋任何可用的結果。 // result:指向一個用于存儲反饋結果的鏈表getaddrinfo 將隱式分配堆內存,因此調用結束后,必須釋放這塊內存:
#include <netdb.h> void freeaddrinfo( struct addrinfo* res );getaddrinfo 反饋的每一條結果都是 addinfo 結構體類型的對象:
struct addrinfo {int ai_flags; // 標志int ai_family; // 地址族int ai_socktype; // 服務類型,SOCK_STREAM或SOCK_DGRAMint ai_protocol; // 具體的網絡協議,等同于socket系統調用的第三個參數,常被設為0以表自動匹配對應協議。socklen_t ai_addrlen; // socket地址ai_addr的長度char* ai_canonname; // 主機的別名struct sockaddr* ai_addr; // 指向socket地址struct addrinfo* ai_next; // 指向下一個 sockinfo 結構的對象 };ai_flags成員可以取下表中的標志的按位或:
| AI_PASSIVE | 套接字地址將用于調用 bind 函數,服務器通常需要設置以表接受任何本地 socket 地址上的服務請求。客戶端不能設置。 |
| AI_CANONNAME | 返回主機的別名 |
| AI_NUMERICHOST | hostname 參數必須是IP地址字符串,避免了DNS查詢。 |
| AI_NUMERICSERV | 強制 service 參數必須是十進制端口號字符串,不能是服務名。 |
| AI_V4MAPPED | 如果對 IPv6 地址的 getaddrinfo 請求失敗,則將 IPv4 映射為 IPv6 地址格式。 |
| AI_ALL | 必須和 AI_V4MAPPED 同時使用,否則將被忽略。同時返回 符合條件 和 由IPv4轉換而來 的 IPv6地址。 |
| AI_ADDRCONFIG | 只有至少配置了一個IPv4/IPv6地址(除了回路地址)后,getaddrinfo 才會解析。和 AI_V4MAPPED 互斥。 |
當我們使用 hints 參數時,可以設置 addrinfo 中前四個成員,其他成員必須設置為 NULL。
getnameinfo
內部使用 gethostbyaddr 和 getservbyport,是否可重入取決于內部調用的函數版本是否可重入:
#include<netdb.h> int getnameinfo( const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags ); // 將返回的主機名(服務名)存儲在 host(serv) 參數指向的緩存中 // flags控制getnameinfo的行為getaddrinfo 和 getnameinfo 成功時返回0,失敗返回錯誤碼。Linux下 strerror 函數能將數值錯誤碼 error 轉換為易讀的字符串形式:
#include<netdb.h> const char* gai_strerror( int error );總結
以上是生活随笔為你收集整理的Linux网络编程 | socket选项设定 及 网络信息API的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最优二叉树(赫夫曼树、赫夫曼树和赫夫曼编
- 下一篇: linux安装下载中文包,linux下安