1.概述
socket 是一種IPC方法,它允許位於同一主機(計算機)或使用網路連線起來的不同主機上的應用程式之間交換資料。
2.建立一個socket
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
//return file descriptor on success or -1 on error
複製程式碼
2.1 通訊domain
- 識別出一個socket的方法(即socket“地址”的格式);
- 通訊範圍(即是在位於同一主機上的應用程式之間還是位於使用一個網路連線起來的不同主機上的應用程式之間)。
2.2 socket 型別
socket主要有兩種型別:流(SOCK_STREAM)和資料包(SOCK_DGRAM)。這兩種socket型別在UNIX和Internet domain中都得到了支援。
流(SOCK_STREAM)是一個雙向位元組流通訊通道
資料包(SOCK_DGRAM)允許資料以被稱為資料包的訊息的形式進行交換。
2.3 protocol
protocol引數應設某個協議型別常值,或者設為0,以選擇給定family和type組合的系統預設值。
3.將socket繫結到地址: bind()
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
複製程式碼
- sockfd 引數是在上一個socket()呼叫中獲得的檔案描述符。
- addr引數是一個指標,它指向了一個指定該socket繫結到的地址的結構。傳入這個引數的結構的型別取決於socket domain。
- addrlen引數指定了地址結構的大小。
一般來講,會將一個伺服器的socket繫結到一個眾所周知的地址——即一個固定的與伺服器進行通訊的客戶端應用程式提前就知道的地址。
3.1 通用socket地址結構:struct sockaddr
這個型別的唯一用途是將各種domain特定的地址結構轉換成單個型別以供socket系統呼叫中的各個引數使用。
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr`. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
/* Structure describing the address of an AF_LOCAL (aka AF_UNIX) socket. */
struct sockaddr_un
{
__SOCKADDR_COMMON (sun_);
char sun_path[108]; /* Path name. */
};
複製程式碼
4.流socket
4.1 監聽接入連線:listen()
#include<sys/socket.h>
int listen(int sockfd,int backlog);
//return 0 on success,or -1 on error
複製程式碼
listen()系統呼叫將檔案描述符sockfd引用的流socket標記為被動。這個socket後面會被用來接受來自其他(主動的)socket連線。
無法在一個已連線的socket(即已經成功執行connect()的socket或由accept()呼叫返回的socket)上執行listen().
- backlog:允許限制未決連線的數量。在這個限制之內的連線請求會立即成功。(TCP socket會有點複雜)而在之外的連線請求就會阻塞直到一個未決的連線被接受(通過accept()),並從未決連線佇列刪除為止。
4.2接受連線:accept()
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
//return file descriptor on success, or -1 on error
複製程式碼
acceot()系統呼叫在檔案描述符sockfd引用的監聽流socket上接受一個接入連線。如果在呼叫accept()時不存在未決的連線,那麼呼叫就會阻塞直到有連線請求到達為止。
accept() 呼叫返回的函式結果是已連線的socket的檔案描述符。監聽socket(sockfd)會保持開啟狀態,並且可以被用來接受後續的連線。
- addr: 傳入accept()的剩餘引數會返回對端socket的地址。addr引數指向了一個用來返回socket地址的結構。這個引數的型別取決與socket domain(與bind()一樣)。
- addrlen: 它指向一個整數,在呼叫執行之前必須要將這個整數初始化為addr指向的緩衝區的大小,這樣核心就知道有多少空間可用於返回socket地址了。
如果不關心對等socket的地址,那麼可以將addr和addrlen分別指定為NULL和0。(也可以在後面某個時刻使用getpeername()系統呼叫來獲取對端的地址。)
4.3連線到對等socket:connect()
#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socketlen_t addrlen);
//return 0 on success,or -1 on error
複製程式碼
connect() 系統呼叫將檔案描述符sockfd引用的主動socket連線到地址通過addr和addrlen指定的監聽socket上。
addr和addrlen引數的指定方式與bind()呼叫中對應引數的指定方式相同。
4.4 流socket I/O
- 要執行I/O需要使用read()和write()系統呼叫。由於socket是雙向的,因此在連線的兩端都可以使用這兩個呼叫。
- 一個socket可以使用close()系統呼叫來關閉或在應用程式終止之後關閉。之後當對等應用程式試圖從連線的另一端讀取資料時將會收到檔案結束(當所有緩衝資料都被讀取之後)。如果對等應用程式試圖向其socket寫如資料,那麼它就會收到一個SIGPIPE訊號,並且系統呼叫會返回EPIPE錯誤。
4.5 連線終止:close()
終止一個流socket連線的常見方式是呼叫close()。如果多個檔案描述符引用了同一個socket,那麼當所有描述符被關閉之後連線就會終止。 假設在關閉一個連線之後,對等應用程式崩潰或沒有讀取或錯誤處理了之前發生給它的資料。在這種情況下就無法知道已經發生了一個錯誤。如果需要確保資料被成功地讀取和處理,那麼就必須要在應用程式中構建某種確認協議。這通常由一個從對等應用程式傳過來的顯式的確認訊息構成。
5.資料包 socket
5.1 交換資料包:recvfrom() 和 sendto()
#include<sys/socket.h>
ssize_t recvfrom(int sockfd,void* buffer,size_t length,int flags,struct sockaddr *src_addr,sockeln_t *addrlen);
ssize_t sendto(int sockfd,const void* buffer,size_t length,int flags,const struct sockaddr *dest_addr,socklen_t addrlen);
複製程式碼
這兩個系統呼叫的返回值和前三個引數與read()和write()中的返回值和相應引數是一樣的。
sockaddr和socklen_t引數被用來獲取或指定與之通訊的對等的地址。
對於recvfrom(),src_addr和addrlen引數會返回用來傳送資料包的遠端socket的地址。
對於sendto(),dest_addr和addrlen引數指定了資料包傳送到的socket。