目錄
前言
11. 套接字
11.1 Socket簡介
11.2 socket()
11.3 bind()
11.4 connect()
11.5 listen()
11.6 accept()
11.7 read()
11.8 recv()
11.9 write()
11.10 send()
11.11 sendto()
11.12 close()
11.13 ioctlsocket()
11.14 getsockopt()、setsockopt()
參考:
李柱明部落格:https://www.cnblogs.com/lizhuming/
本文連結:https://www.cnblogs.com/lizhuming/p/14309823.html
前面介紹的管道、信号量、消息隊列共享記憶體等等都是用于單個計算機的程序間通信
基于套接字的程序間通信機制,可實作跨主機的程序間通信
套接字(socket)是一種通信機制,憑借這種機制, <code>用戶端<->伺服器</code> 模型的通信方式既可以在本地裝置上進行,也可以跨網絡進行。
套接字機制可實作 多用戶端到一個伺服器。
在Socket中,它使用一個套接字來記錄網絡的一個連接配接,套接字是一個整數。
在網絡中,可以對socket進行網絡連接配接、讀取資料、發送資料和終止連接配接等操作。
相關頭檔案
套接字本身隻是使用者程式與核心互動資訊的樞紐,沒有網絡協定位址和端口号等資訊,是以在使用時需要用bind()綁定一下
概念上注意套接字與端口号的差別。套接字可以了解為IP+端口号。
個人輔助了解:一個端口号對應一個應用程式,一個應用程式内,理論上可以多個套接字綁定一個端口号,但是大多數系統不允許這麼幹。 肚臍
一個伺服器一般隻建立一個監聽套接字,它在該伺服器的生命周期内是一直存在的。同時,伺服器也會為每個已連接配接的用戶端建立一個已連接配接套接字(在accept()連接配接成功後,核心會自動生成一個全新的套接字)。
socket()函數用于建立一個socket描述符,用于辨別唯一一個socket。
函數原型:<code>int socket(int domain, int type, int protocol);</code>
domain:表示該套接字使用的協定族(對于TCP/IP,一般選擇AF_INET就可以了)
AF_UNIX, AF_LOCAL: 本地通信
AF_INET : IPv4
AF_INET6 : IPv6
AF_IPX : IPX - Novell 協定
AF_NETLINK : 核心使用者界面裝置
AF_X25 : ITU-T X.25 / ISO-8208 協定
AF_AX25 : 業餘無線電 AX.25 協定
AF_ATMPVC : 通路原始ATM PVC
AF_APPLETALK : AppleTalk
AF_PACKET : 底層資料包接口
AF_ALG : 核心加密API的AF_ALG接口
type:服務類型
SOCK_STREAM:提供可靠的(即能保證資料正确傳送到對方)面向連接配接的Socket服務,多用于資料(如檔案)傳輸,如TCP協定。
SOCK_DGRAM:是提供無保障的面向消息的Socket 服務,主要用于在網絡上發廣播資訊,如UDP協定,提供無連接配接不可靠的資料報傳遞服務。
SOCK_SEQPACKET:為固定最大長度的資料報提供有序的,可靠的,基于雙向連接配接的資料傳輸路徑。
SOCK_RAW:表示原始套接字,它允許應用程式通路網絡層的原始資料包,這個套接字用得比較少,暫時可忽略。
SOCK_RDM:提供不保證排序的可靠資料報層。
protocol:套接字使用的協定。當protocol為0時,會自動選擇type類型對應的預設協定。
如:當在IPv4,隻有TCP協定提供SOCK_STREAM這種可靠的服務,此時,protocol為0即可。
傳回
成功:傳回一個大于 0 的套接字描述符
失敗:傳回-1
bind()函數用于将一個 IP 位址或端口号與一個套接字進行綁定,許多時候核心會幫我們自動綁定一個IP位址與端口号,但是也可以手動綁定。
函數原型:<code>int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);</code>
sockfd:sockfd是由socket()函數傳回的套接字描述符。
my_addr:my_addr是一個指向套接字位址結構的指針。
addrlen:addrlen指定了以addr所指向的位址結構體的位元組長度。
傳回:
成功:傳回 0
sockaddr結構體:
一般不使用 sockaddr 結構體,因為操作不友善,而是使用 sockaddr_in。因為兩者占用相同的空間。可以代替。(賦參數時進行強制類型轉換即可)
sockaddr_in結構體:
connect()函數用于用戶端,将socket與遠端的IP位址、端口号綁定。如在TCP用戶端中調用該函數,将會發生握手過程。
函數原型:<code>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);</code>
參數參考 bind() 函數
成功:傳回0
失敗:傳回-1,錯誤存在于errno中
listen()函數用于伺服器端,使伺服器端進入監聽狀态,等待用戶端請求連接配接。
函數原型:<code>int listen(int sockfd, int backlog);</code>
sockfd:sockfd是套接字描述符。
backlog:表示sockfd的等待連接配接隊列能夠達到的最大值。
當多個用戶端同時嘗試連接配接本伺服器時,核心會在自己的程序空間中維護一個隊列來儲存這些請求,當隊列滿時,後面進來的請求,伺服器端會将其丢棄,用戶端會收到連接配接失敗的錯誤。
accept()函數用于伺服器端,主要是處理來自用戶端的連接配接請求。
函數原型:<code>int accept(int s, struct sockaddr *addr, socklen_t *addrlen);</code>
成功:如果連接配接成功,會建立一個和參數sockfd相同屬性的連接配接套接字,并為該連接配接套接字配置設定一個檔案描述符。最終傳回一個socket描述符(非負值)。
當套接字标記為阻塞模式,隊列中沒有未完成處理的連接配接請求時,調用 accept() 函數會一直阻塞,直至與遠端建立連接配接。
當套接字标記為非阻塞模式,隊列中沒有未完成處理的連接配接請求時,調用 accept() 函數會立即傳回EAGAIN。
當用戶端與伺服器端建立好TCP連接配接之後,我們就可以通過sockfd套接字描述符(已連接配接套接字描述符,由sccept()産生)來收發資料。
接收網絡中的資料可以使用read()、recv()、recvfrom()等。
函數原型:<code>ssize_t read(int fd, void *buf, size_t count);</code>
fd:可以是檔案描述符,也可以是套接字描述符。在本章節中為套接字描述符。
buf:接收資料的緩沖區。
count:需要讀取的位元組數。
成功:實際讀取到的位元組數。(情況一:檔案剩餘位元組數小于count,則傳回的位元組數是小于count的)
失敗:傳回-1,錯誤存在于errno中:
EINTR:在讀取到資料前被信号所中斷。
EAGAIN:使用 O_NONBLOCK 标志指定了非阻塞式輸入輸出,但目前沒有資料可讀。
EIO:輸入輸出錯誤,可能是正處于背景程序組程序試圖讀取其控制終端,但讀操作無效,或者被信号SIGTTIN所阻塞, 或者其程序組是孤兒程序組,也可能執行的是讀磁盤或者錄音帶機這樣的底層輸入輸出錯誤。
EISDIR:fd 指向一個目錄。
EBADF:fd 不是一個合法的套接字描述符,或者不是為讀操作而打開。
EINVAL:fd 所連接配接的對象不可讀。
EFAULT:buf 超出使用者可通路的位址空間。
recv()函數功能和read()函數功能差不多,用戶端和伺服器端都可以使用該函數來接收另一端的資料。
recv()實際上是拷貝資料,接收資料是由協定完成的。recv()函數會先檢查套接字的接收緩沖區,若緩沖區中沒有資料或正在接收資料,recv()函數會一直等待,直至協定接收資料完畢,recv()才能把緩沖區的資料讀走。
函數原型:<code>ssize_t recv(int sockfd, void *buf, size_t len, int flags);</code>
sockfd:指定接收端套接字描述符。
buf:指定一個接收資料的緩沖區,該緩沖區用來存放recv()函數接收到的資料。
len:指定recv()函數拷貝的資料長度。
flags:
0:一般置為0即可
MSG_OOB:接收以out-of-band送出的資料。
MSG_PEEK:保持原有資料,就是說接收到的資料在緩沖區中并不會被删除, 如果再調用recv()函數還會拷貝相同的資料到buf中。
MSG_WAITALL:強迫接收到指定len大小的資料後才能傳回, 除非有錯誤或信号産生。
MSG_NOSIGNAL:recv()函數不會被SIGPIPE信号中斷。
成功:實際讀取到的位元組數。
ENOTSOCK:參數 s 為一檔案描述詞, 非socket。
EAGAIN:此動作會令程序阻塞, 但參數s的 socket 為不可阻塞。
ENOBUFS:buf記憶體空間不足。
ENOMEM:記憶體不足。
EINVAL:傳入的參數不正确。
write()函數一般用于處于穩定的TCP連接配接中傳輸資料。UDP協定也可以使用。
write()函數在寫入資料完成後并不是立即發送的,至于什麼時候發送則由TCP/IP協定棧決定。
函數原型:<code>ssize_t write(int fd, void *buf, size_t count);</code>
buf:發送資料的緩沖區。
count:需要發送位元組數。
成功:傳回實際寫入的位元組數
注意,網絡程式設計中write()是不負責将全部資料寫完之後再傳回的,或許中途就傳回了。若想保證資料全部寫入,必須循環運作write()函數,可自行封裝。代碼如下:(copy野火)
無論是用戶端還是伺服器應用程式都可以用write()函數來向TCP連接配接的另一端發送資料。
當使用send()發送資料時,send()會先比較需要發送的長度len和套接字sockfd的發送緩沖區長度,若len大,則傳回SOCKET_ERROR。若len小于等于,send()函數會先檢查協定是否正在發送套接字sockfd的發送緩沖區的資料,如果是,就等待發送完畢,如果還沒有開始發送,則send()函數會繼續比較len和發送緩沖區中剩餘空間長度,若len大,則等待發送緩沖區發送完畢,若len小于等于,則把資料copy到發送緩沖區。
copy成功後,send()函數就馬上傳回,但是不一定馬上發送,什麼時候發送取決于TCP/IP協定。
函數原型:<code>int send(int sockfd, const void *msg, size_t len, int flags);</code>
sockfd:指定發送端套接字描述符。
msg:指定要發送資料的緩沖區。
flags:一般為0即可。
成功:傳回實際copy的位元組數
失敗:傳回SOCKET_ERROR
sendto()函數與send()函數相似,但是它會通過 struct sockaddr 指向的 to 結構體指定要發送給哪個遠端主機,在to參數中需要指定遠端主機的IP位址、端口号等,而tolen參數則是指定to 結構體的位元組長度。
sendto()适用于已連接配接的資料報或流式套接口發送資料。
函數原型:<code>int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);</code>
s:一個辨別套接口的描述字。
buf:包含待發送資料的緩沖區。
len:buf緩沖區中資料的長度。
flags:調用方式标志位。
to:(可選)指針,指向目的套接口的位址。
tolen:to所指位址的長度。
close()函數是用于關閉一個指定的套接字。
函數原型:<code>int close(int fd);</code>
ioctlsocket()函數用于擷取與設定套接字相關的操作參數。
函數原型<code>int ioctlsocket( int s, long cmd, u_long *argp);</code>
s:指定要操作的套接字描述符。
cmd:對套接字s的操作指令。
FIONBIO:允許或禁止套接口s的非阻塞模式。
FIONREAD:确定套接口s自動讀入的資料量。argp指向一個無符号長整型,其中存有ioctlsocket()的傳回值。
SIOCATMARK:确實是否所有的帶外資料都已被讀入。
argp:指向cmd指令所帶參數的指針。argp指向一個無符号長整型。如允許非阻塞模式則非零,如禁止非阻塞模式則為零。
成功:ioctlsocket()傳回0。
失敗:傳回SOCKET_ERROR錯誤,應用程式可通過WSAGetLastError()擷取相應錯誤代碼:
WSANOTINITIALISED:在使用此API之前應首先成功地調用WSAStartup()。
WSAENETDOWN:WINDOWS套接口實作檢測到網絡子系統失效。
WSAEINVAL:cmd為非法指令,或者argp所指參數不适用于該cmd指令,或者該指令不适用于此種類型的套接口。
WSAEINPROGRESS:一個阻塞的WINDOWS套接口調用正在運作中。
WSAENOTSOCK:描述字不是一個套接口。
getsockopt()、setsockopt()分别是擷取和設定套接字。
函數原型:
<code>int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);</code>
<code>int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);</code>
sockfd:指定要操作的套接字描述符。
level:
SOL_SOCKET:表示在Socket層。
IPPROTO_TCP:表示在TCP層。
IPPROTO_IP: 表示在IP層。
optname:該層的具體選項,如:
對于SOL_SOCKET選項
SO_REUSEADDR(允許重用本地位址和端口)
SO_SNDTIMEO(設定發送資料逾時時間)
SO_SNDTIMEO(設定接收資料逾時時間)
SO_RCVBUF(設定發送資料緩沖區大小)等等。
對于IPPROTO_TCP選項
TCP_NODELAY(不使用Nagle算法)
TCP_KEEPALIVE(設定TCP保活時間)等等。
對于IPPROTO_IP選項
IP_TTL(設定生存時間)
IP_TOS(設定服務類型)等等。