?????????Socket 是網(wǎng)絡(luò)協(xié)議棧暴露給編程人員的 API,相比復(fù)雜的計(jì)算機(jī)網(wǎng)絡(luò)協(xié)議,API 對(duì)關(guān)鍵操作和配置數(shù)據(jù)進(jìn)行了抽象,簡(jiǎn)化了程序編程。
? ? ? ? 本文講述的 socket 內(nèi)容源自 Linux man。本文主要對(duì)各 API 進(jìn)行詳細(xì)介紹,從而更好的理解 socket 編程。
一.socket()?
遵循 POSIX.1 - 2001、POSIX.1-2008、4.4BSD
1.庫(kù)
標(biāo)準(zhǔn) c 庫(kù),libc, -lc
2.頭文件
<sys/socket.h>
3.接口定義
int socket(int domain, int type, int protocol);
4.接口描述
? ? ? ? socket() 創(chuàng)建一個(gè)通信端點(diǎn)并返回一個(gè)指向該端點(diǎn)的文件描述符。返回的文件描述符號(hào)是當(dāng)前進(jìn)程沒(méi)有打開(kāi)的號(hào)最小的文件描述符。
5.參數(shù)
- domain
? ? ? ? domain 參數(shù)指定了一個(gè)通信域,它選擇了用于通信的協(xié)議家族,這些協(xié)議家族在<sys/socket.h> 中定義,當(dāng)前 Linux 內(nèi)核能夠認(rèn)識(shí)的格式包括:
名稱 | 目的 |
AF_UNIX | 本地通信 |
AF_LOCAL | 和 AF_UNIX 同意 |
AF_INET | IPv4 網(wǎng)絡(luò)協(xié)議 |
AF_AX25 | 業(yè)余無(wú)線電 AX.25 協(xié)議 |
AF_IPX | Novell 分組交換協(xié)議 |
AF_APPLETALK | Appletalk 協(xié)議 |
AF_X25 | X25 分組交換網(wǎng)絡(luò) |
AF_INET6 | IPv6 網(wǎng)絡(luò)協(xié)議 |
AF_DECnet | DECnet 協(xié)議 socket |
AF_KEY | 密鑰管理協(xié)議? |
AF_NETLINK | 內(nèi)核用戶接口設(shè)備 |
AF_PACKET | 底層 packet 接口 |
AF_RDS | 可靠的數(shù)據(jù)報(bào)套接字協(xié)議 |
AF_PPPOX | 通用 PPP 傳輸層,用于設(shè)置 L2 層隧道(L2TP、PPPoE) |
AF_LLC | 邏輯鏈路控制協(xié)議(IEEE 802.2 LLC) |
AF_IB | InfiniBand 本地訪問(wèn) |
AF_MPLS | 多協(xié)議標(biāo)記切換 |
AF_CAN | 控制器局域網(wǎng)汽車總線協(xié)議 |
AF_TIPC | 集群域內(nèi)套接字 |
AF_BLUETOOTH | 藍(lán)牙底層套接字協(xié)議 |
AF_ALG | 內(nèi)核密碼學(xué) API 接口 |
AF_VSOCK | VSOCK 原來(lái)用于 VMWARE VSockets,hypervisor 和 guest 之間的通信協(xié)議 |
AF_KCM | 內(nèi)核連接多路復(fù)用器接口 |
AF_XDP | 快速數(shù)據(jù)路徑接口 |
? ? ? ? 更多關(guān)于地址家族的信息可以從 address_families(7) 中查看。
? ? ? ? socket 有一個(gè)指定的 type 類型,定義了雙方通信語(yǔ)義,目前定義的類型有:
? ? ? ? SOCK_STREAM
? ? ? ? 提供有序、可靠、雙向、面向連接的字節(jié)流,可以支持帶歪數(shù)據(jù)傳輸機(jī)制。
? ? ? ? SOCK_DGRAM
? ? ? ? 支持?jǐn)?shù)據(jù)報(bào)文(無(wú)連接、不可靠定長(zhǎng)消息)。?
? ? ? ? SOCK_SEQPACKET
? ? ? ? 提供了有序、可靠、雙向、面向連接的數(shù)據(jù)傳輸,傳輸?shù)膬?nèi)容不是字節(jié)流,而是固定長(zhǎng)度的數(shù)據(jù)報(bào)文。數(shù)據(jù)報(bào)消費(fèi)者每次通過(guò) read 系統(tǒng)調(diào)用讀取整個(gè)數(shù)據(jù)報(bào)文。
? ? ? ? SOCK_RAW
? ? ? ? 提供原始網(wǎng)絡(luò)協(xié)議訪問(wèn)。
? ? ? ? SOCK_RDM
? ? ? ? 提供可靠的數(shù)據(jù)報(bào)層,但是并不保證有序。
? ? ? ? SOCK_PACKET
? ? ? ? 已經(jīng)過(guò)時(shí)了,新應(yīng)用不應(yīng)該使用,參考 packet(7)。
? ? ? ?一些協(xié)議類型并不是被所有協(xié)議家族支持的。
? ? ? ? Linux 2.6.27 后,type 類型具有另外一個(gè)目的:除了指定 socket 類型,還包含了下面數(shù)值的位或值,來(lái)控制 socket 的行為:
? ? ? ? SOCK_NONBLOCK
? ? ? ? 在打開(kāi)新文件描述符指向的文件時(shí)設(shè)置文件狀態(tài)標(biāo)記 O_NONBLOCK,這就不需要額外使用 fcntl() 來(lái)進(jìn)行設(shè)置。
? ? ? ? SOCK_CLOEXEC
? ? ? ? 設(shè)置新文件描述符的 FD_CLOEXEC 標(biāo)記,可以參考 open() 來(lái)看為什么需要設(shè)置整個(gè)參數(shù)
? ? ? ? protocol 指定了 socket 使用的具體協(xié)議,通常一個(gè)指定的協(xié)議家族、協(xié)議類型中只有一種協(xié)議,這時(shí) protocol 可以指定為 0。然而,也可能存在多個(gè)協(xié)議,這種情況下就必須指定協(xié)議,而協(xié)議號(hào)是根據(jù)實(shí)際的通信域的不同而不同的。參考 protocol(5)。參考 getprotoent(3) 來(lái)查看如何將協(xié)議號(hào)映射到協(xié)議名字符串上。
? ? ? ? SOCK_STREAM ?socket 是全雙工字節(jié)流,它并沒(méi)有保留記錄邊界。流套接字必須處于連接狀態(tài)來(lái)進(jìn)行數(shù)據(jù)的發(fā)送和接收。連接到其他套接字是通過(guò) connect(2) 系統(tǒng)調(diào)用實(shí)現(xiàn)的。一旦連接上了,數(shù)據(jù)就可以通過(guò) read(2) 和 write(2) 調(diào)用來(lái)進(jìn)行傳輸,或者使用 send(2) 和 recv(2) 變體調(diào)用。會(huì)話結(jié)束后,應(yīng)該使用 close(2) 來(lái)關(guān)閉。帶外數(shù)據(jù)可以根據(jù) send(2) 和 recv(2) 的描述來(lái)進(jìn)行收發(fā)。
? ? ? ? 實(shí)現(xiàn) SOCK_STREAM 的通信協(xié)議需要保證數(shù)據(jù)不能丟失或者重復(fù)。對(duì)于緩存到底層協(xié)議中在規(guī)定時(shí)間內(nèi)無(wú)法傳輸完成的數(shù)據(jù)來(lái)講,該連接會(huì)被視為死掉了。當(dāng)套接字協(xié)議開(kāi)啟了?SO_KEEPALIVE 保活機(jī)制,協(xié)議會(huì)使用協(xié)議自己定義的方式來(lái)檢查對(duì)端是否還活著。當(dāng)我們?cè)谝粋€(gè)破損了的 pipe 上發(fā)送接收數(shù)據(jù),那么就會(huì)收到 SIGPIPE信號(hào),這會(huì)導(dǎo)致沒(méi)有處理這個(gè)信號(hào)的本地進(jìn)程直接退出。
? ? ? ? SOCK_QEQPACKET 套接字使用同樣的系統(tǒng)調(diào)用 SOCK_STREAM,唯一的不同 read(2) 調(diào)用返回指定請(qǐng)求數(shù)量的數(shù)據(jù),接收數(shù)據(jù)包中剩余的數(shù)據(jù)將會(huì)被丟棄。同時(shí),發(fā)過(guò)來(lái)的數(shù)據(jù)報(bào)文的所有邊界都保留著。
? ? ? ? SOCK_DGRAM 和 SOCK_RAW 套接字允許使用 sendto(2) 來(lái)發(fā)送數(shù)據(jù)報(bào)文給對(duì)端。數(shù)據(jù)報(bào)文通常使用 recvfrom(2) 來(lái)接收,這個(gè)接口會(huì)返回下一個(gè)數(shù)據(jù)報(bào)文以及發(fā)送者的地址。
? ? ? ? SOCK_PACKET 是一個(gè)過(guò)時(shí)的直接從對(duì)端接收原始數(shù)據(jù)報(bào)文的套接字類型,應(yīng)該使用 packet(7) 來(lái)替代。
? ? ? ? 我們可以使用 fcntl(2) 的 F_SETOWN 操作來(lái)指定進(jìn)程或者進(jìn)程組來(lái)接收帶外數(shù)據(jù)到達(dá)信號(hào) SIGURG 和連接異常中斷信號(hào) SIGPIPE。這個(gè)操作也可以用來(lái)接收 SIGIO 異步 I/O 通知事件。使用 F_SETOWN 等效于 ioctl() 調(diào)用的 FIOSETOWN 或者 SIOCSPGRP。
? ? ? ? 當(dāng)網(wǎng)絡(luò)給協(xié)議模塊發(fā)送了錯(cuò)誤指示信號(hào)時(shí)(比如 IP 層的 ICMP 消息),那么錯(cuò)誤標(biāo)記將會(huì)設(shè)置到套接字上,在套接字的下一次操作發(fā)生時(shí),會(huì)將掛起的錯(cuò)誤以錯(cuò)誤碼的形式返回。對(duì)于一些協(xié)議而言,也可以通過(guò)開(kāi)啟套接字特定的錯(cuò)誤隊(duì)列來(lái)獲得關(guān)于錯(cuò)誤的詳細(xì)信息,可以參考 ip(7) 中的?IP_RECVERR。
? ? ? ? 對(duì)于套接字的操作是由套接字層面的選項(xiàng)來(lái)控制的,這些選項(xiàng)定義在 <sys/socket.h> 中。函數(shù) setsockopt(2) 和 getsockopt(2) 用來(lái)設(shè)置和獲取對(duì)于的選項(xiàng)。
帶外數(shù)據(jù)傳輸指的是 TCP 在緊急情況下通過(guò)調(diào)整報(bào)文在發(fā)送/接收緩沖區(qū)的位置以及數(shù)據(jù)包中添加緊急標(biāo)記的邏輯。?
6.返回值
? ? ? ? 發(fā)生錯(cuò)誤時(shí)返回 -1,設(shè)置?errno 指示錯(cuò)誤碼,否則返回一個(gè)新創(chuàng)建的整型文件描述符。
? ? ? ? 可能的錯(cuò)誤碼包括:
錯(cuò)誤碼 | 含義 |
EACCES | 沒(méi)有權(quán)限創(chuàng)建對(duì)應(yīng)的 socket |
EAFNOSUPPORT | 實(shí)現(xiàn)不支持指定的 AF_ 地址家族 |
EINVAL | 未知的協(xié)議或者地址家族不可用 |
EINVAL | type 參數(shù)不合法 |
EMFILE | 進(jìn)程文件描述符到達(dá)最大限制 |
ENFILE | 系統(tǒng)文件描述符到達(dá)上限 |
ENOBUFS or ENOMEM | 內(nèi)存不足 |
EPROTONOSUPPORT | domain 不支持指定的協(xié)議類型 |
二、bind
遵循 POSIX.1-2008
1.庫(kù)
標(biāo)準(zhǔn) c 庫(kù),libc, -lc
2.頭文件
<sys/socket.h>
3.接口定義
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
4.接口描述
? ? ? ? 通過(guò) socket() 接口創(chuàng)建 socket 后,socket 只存在于名字空間中,并沒(méi)有實(shí)際的地址分配給它。bind 接口將 addr 指定的 IP 地址分配給由文件描述符 sockfd 指定的 socket。addrlen 指定了 addr 指針指向的地址結(jié)構(gòu)的字節(jié)長(zhǎng)度。以前我們將這個(gè)操作給 socket 分配名字。
? ? ? ? 通常在 TCP_STREAM socket 接收連接前需要將一個(gè)本地地址通過(guò) bind 分配給 socket。
? ? ? ? 名字綁定規(guī)則隨著地址家族的不同而不同。
? ? ? ? ?addr 的數(shù)據(jù)結(jié)構(gòu)也是隨著地址家族的變化而變化的。sockaddr 結(jié)構(gòu)的定義類似:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
? ? ? ? ?這個(gè)結(jié)構(gòu)定義主要是為了防止編譯器報(bào)錯(cuò),主要是將各種地址結(jié)構(gòu)做一個(gè)強(qiáng)制轉(zhuǎn)換。
5. 返回值
? ? ? ? 發(fā)生錯(cuò)誤時(shí)返回 -1,設(shè)置?errno 指示錯(cuò)誤碼,否則返回一個(gè)新創(chuàng)建的整型文件描述符。
? ? ? ? 可能的錯(cuò)誤碼包括:
錯(cuò)誤碼 | 含義 |
EACCES | 地址是保護(hù)地址,并且用戶不是超級(jí)用戶 |
EADDRINUSE | 指定的地址已經(jīng)使用 |
EADDRINUSE | 對(duì)于 domain socket,端口號(hào)在地址結(jié)構(gòu)體中 指定為 0,但在嘗試 bind 到臨時(shí)端口時(shí),臨時(shí)端口沒(méi)有空閑的了 |
EBADF | sockfd 不是可用的文件描述符 |
EINVAL | socket 已經(jīng)綁定到了一個(gè)地址 |
EINVAL | addrlen 錯(cuò)誤,或者 addr 不是一個(gè)可用的 domain 地址 |
ENOTSOCK | 文件描述符沒(méi)有指向任何 socket |
UNIX domain(AF_UNIX) 特定的錯(cuò)誤碼 | |
EACCESS | 在路徑前綴下無(wú)搜索權(quán)限 |
EADDRNOTAVAIL | 請(qǐng)求的接口不存在或者不是本地的接口 |
EFAULT | addr 指向了用戶無(wú)法訪問(wèn)的地址空間 |
ELOOP | 解析地址時(shí)遇到了太多的符號(hào)鏈接 |
ENAMETOOLONG | 地址太長(zhǎng) |
ENOENT | 指定路徑不存在 |
ENOMEM | 內(nèi)核內(nèi)存不足 |
ENOTDIR | 路徑前綴不是一個(gè)目錄 |
EROFS | socket inode 位于只讀文件系統(tǒng)中 |
6.示例代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define MY_SOCK_PATH "/somepath"
#define LISTEN_BACKLOG 50
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int
main(void)
{
int sfd, cfd;
socklen_t peer_addr_size;
struct sockaddr_un my_addr, peer_addr;
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd == -1)
handle_error("socket");
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sun_family = AF_UNIX;
strncpy(my_addr.sun_path, MY_SOCK_PATH,
sizeof(my_addr.sun_path) - 1);
if (bind(sfd, (struct sockaddr *) &my_addr,
sizeof(my_addr)) == -1)
handle_error("bind");
if (listen(sfd, LISTEN_BACKLOG) == -1)
handle_error("listen");
/* Now we can accept incoming connections one
at a time using accept(2). */
peer_addr_size = sizeof(peer_addr);
cfd = accept(sfd, (struct sockaddr *) &peer_addr,
&peer_addr_size);
if (cfd == -1)
handle_error("accept");
/* Code to deal with incoming connection(s)... */
if (close(sfd) == -1)
handle_error("close");
if (unlink(MY_SOCK_PATH) == -1)
handle_error("unlink");
}
?三、accept
1.庫(kù)
標(biāo)準(zhǔn) c 庫(kù),libc, -lc
2.頭文件
<sys/socket.h>
3.接口定義
int accept(int sockfd, struct sockaddr *_Nullable restrict addr,
socklen_t *_Nullable restrict addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/socket.h>
int accept4(int sockfd, struct sockaddr *_Nullable restrict addr,
socklen_t *_Nullable restrict addrlen, int flags);
4.接口描述
?? ? ? ?accept 系統(tǒng)調(diào)用用于面向連接的 socket (SOCK_STREAM、SOCK_SEQPACKET),它會(huì)從監(jiān)聽(tīng)的 socket(sockfd)的等待連接隊(duì)列里拿到第一個(gè)連接請(qǐng)求,創(chuàng)建一個(gè)新的連接的 socket,并返回一個(gè)新的文件描述指向這個(gè)新的 socket。新創(chuàng)建的 socket 并沒(méi)有處于監(jiān)聽(tīng)狀態(tài),原來(lái)的 socket(sockfd)并不會(huì)受到任何影響。
? ? ? ? sockfd 是由 sockect() 創(chuàng)建的,并通過(guò) bind 綁定到了本地地址上,并通過(guò) listen 監(jiān)聽(tīng)連接。
? ? ? ? addr 是一個(gè)指向 sockaddr 結(jié)構(gòu)的指針,這個(gè)地址由對(duì)端 socket 的地址填充。而返回的 addr 的結(jié)構(gòu)類型根據(jù) socket 地址家族的不同而不同。當(dāng) addr 是 NULL 時(shí),底層并不會(huì)對(duì)其填充,這種情況下 addrlen 也沒(méi)有用,也應(yīng)該是 NULL。
? ? ? ? addrlen 參數(shù)是一個(gè)輸入輸出參數(shù),調(diào)用者必須使用 addr 指向的結(jié)構(gòu)體的大小來(lái)初始化它,而在返回時(shí),則會(huì)使用對(duì)端地址的實(shí)際大小來(lái)填充。
? ? ? ? 如果提供的 buffer 太小,則返回的地址將會(huì)被截?cái)啵@種情況下,addrlen 會(huì)返回一個(gè)比提供值大的值。
? ? ? ? 如果當(dāng)前等待連接隊(duì)列中沒(méi)有待連接請(qǐng)求,并且 socket 沒(méi)有被設(shè)置成非阻塞,那么 accept() 將會(huì)一直阻塞。如果 socket 設(shè)置為非阻塞,那么 accept() 將報(bào)錯(cuò)為 EAGAIN 或者 EWOULDBLOCK。
? ? ? ? 為了獲取?socket 上有連接請(qǐng)求過(guò)來(lái),我們需要使用 select、poll、epoll。當(dāng)一個(gè)新連接來(lái)臨時(shí),會(huì)產(chǎn)生一個(gè)可讀事件,我們可以使用 accept 來(lái)繼續(xù)從連接上獲取一個(gè) socket。
? ? ? ? 我們也可以設(shè)置 socket 上有連接時(shí)發(fā)送 SIGIO 信號(hào)。
? ? ? ? 如果 flag 是 0,那么 accept4() 就等同于 accept()。flag 可以是下面配置的或起來(lái)的值,來(lái)實(shí)現(xiàn)不同的行為:
- SOCK_NONBLOCK
? ? ? ? ? ? ? ? ?設(shè)置新文件描述符的 O_NONBLOCK 屬性
- SOCK_CLOEXEC
? ? ? ? ? ? ? ? ?設(shè)置新文件描述符的 FD_CLEXEC 屬性。
5.返回值
? ? ? ? 成功時(shí),返回一個(gè)新接收的 socket 的文件描述符(非負(fù)值)。
? ? ? ? 出錯(cuò)時(shí),返回 -1,設(shè)置 errno 為錯(cuò)誤碼,addrlen 不會(huì)被修改。
- 錯(cuò)誤處理
? ? ? ? Linux 的 accept() 會(huì)將既存的網(wǎng)絡(luò)錯(cuò)誤也會(huì)給返回值,這個(gè)行為和其他 BSD socket 實(shí)現(xiàn)的行為不太一樣。為了可靠性,我們應(yīng)該處理 accept 返回網(wǎng)絡(luò)錯(cuò)誤,這些錯(cuò)誤是和協(xié)議相關(guān)的。比如 EAGAIN 表示重傳,在TCP/IP 的場(chǎng)景下,還有 ENETDOWN、?EPROTO、?ENOPROTOOPT, EHOSTDOWN、?ENONET、?EHOSTUNREACH、?EOPNOTSUPP、?ENETUNREACH等需要處理。
? ? ? ?可能的錯(cuò)誤碼包括:
錯(cuò)誤碼 | 含義 |
EAGAIN或EWOULDBLOCK | socket 設(shè)置為非阻塞,目前沒(méi)有可用連接。POSIX.1-2001 和?POSIX.1-2008 允許范圍任何一個(gè)錯(cuò)誤,同時(shí)并沒(méi)有他們有相同的值,所以為了實(shí)現(xiàn)移植性,需要分別判斷。 |
ECONNABORTED | 連接已中斷 |
EFAULT | addr 不是用戶地址空間可寫的地址 |
EBADF | sockfd 不是打開(kāi)的文件描述符 |
EINVAL | socket 沒(méi)有在監(jiān)聽(tīng)連接或者addrlen不合法 |
EINVAL | accept4,flags 值不合法 |
ENOTSOCK | 文件描述符沒(méi)有指向任何 socket |
EINTR | 在連接到達(dá)前,系統(tǒng)調(diào)用被信號(hào)打斷 |
EMFILE | 進(jìn)程描述符數(shù)達(dá)到上限 |
EFAULT | addr 指向了用戶無(wú)法訪問(wèn)的地址空間 |
ENFILE | 系統(tǒng)文件描述符達(dá)到上限 |
ENAMETOOLONG | 地址太長(zhǎng) |
ENOENT | 指定路徑不存在 |
ENOMEM或ENOBUFS | 內(nèi)核內(nèi)存不足 |
EOPNOTSUPP | socket 不 SOCK_STREAM 類型 |
EPERM | 防火墻禁止連接 |
EPROTO | 協(xié)議錯(cuò)誤 |
Linux 上,新創(chuàng)建的 socket 并不會(huì)從監(jiān)聽(tīng) socket 上繼承 O_NONBLOCK 和 O_AYSNC 屬性,這點(diǎn)和 canonical BSD socket 實(shí)現(xiàn)的行為不同。所以,實(shí)現(xiàn)可移植的程序不應(yīng)該依賴這些行為。
值得注意的是,有時(shí)在我們收到 SIGIO 或者通過(guò)select、poll、epoll 獲得到一個(gè)刻度的事件時(shí),并不一定就會(huì)有一個(gè)連接等待連接,這是因?yàn)檫B接很可能會(huì)被異步網(wǎng)絡(luò)錯(cuò)誤或者其他線程通過(guò) accept() 拿走了。在這種情況下,就會(huì)導(dǎo)致 accept 阻塞,直到下一個(gè)連接到達(dá)。為了保證 accept 永遠(yuǎn)不會(huì)阻塞,傳來(lái)的 socketfd 需要有 O_NONBLOCK 屬性。
在最初的 BSD socket 實(shí)現(xiàn)中,accept 的第三個(gè)參數(shù)是 int *,在 POSIX.1g 草稿版標(biāo)準(zhǔn)想把它改成 size_t *C,后來(lái) POSIX 標(biāo)準(zhǔn)和glibc 2.x 定為 socket_t *。
遵循:
accept() ? POSIX.1-2008
accept4 ? ?Linux?文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-698745.html
下一篇 【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(2)????????文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-698745.html
到了這里,關(guān)于【計(jì)算機(jī)網(wǎng)絡(luò)】網(wǎng)絡(luò)編程接口 Socket API 解讀(1)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!