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