天天看點

【linux】系統程式設計-8-Socket

目錄

前言

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>用戶端&lt;-&gt;伺服器</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(設定服務類型)等等。

繼續閱讀