Socket 核心函式


本教學將介紹寫一個完整的TCP用戶端和伺服器需要的通訊端核心函式

以下是完整的用戶端和伺服器的互動圖:

Socket Client Server

socket 函式:

要執行網路I/O,進程必須做的第一件事是呼叫socket函式,指定所需的通訊協定型別和協定族等。

#include <sys/types.h>
#include <sys/socket.h>

int socket (int family, int type, int protocol);

這個呼叫給一個通訊端描述符,可以用在以後的系統呼叫,-1為出錯。

引數:

協定族: 指定協定族,是一個常數如下所示:

Family 描述
AF_INET IPv4 protocols
AF_INET6 IPv6 protocols
AF_LOCAL Unix domain protocols
AF_ROUTE Routing Sockets
AF_KEY Ket socket

本教學不談論除IPv4協定之外的其他協定

型別: 指定類想要的通訊端。它可以取下列值之一:

型別 描述
SOCK_STREAM Stream socket
SOCK_DGRAM Datagram socket
SOCK_SEQPACKET Sequenced packet socket
SOCK_RAW Raw socket

協定: 引數應設定具體的協定型別如下,或低於0的系統的預設值

協定 描述
IPPROTO_TCP TCP transport protocol
IPPROTO_UDP UDP transport protocol
IPPROTO_SCTP SCTP transport protocol

connect 函式:

connect函式使用一個TCP用戶端,TCP伺服器建立連線。

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

這個呼叫返回0,則它成功地連線到伺服器,否則它給-1的錯誤。

引數:

  • sockfd: socket函式返回一個通訊端描述符.

  • serv_addr 是一個指向struct sockaddr的包含目的IP地址和埠.

  • addrlen 設定sizeof為(struct sockaddr).

bind 函式:

分配一個本地協定地址系結功能的通訊端。與網際網路協定的協定地址是一個32位元的IPv4地址或128位元的IPv6地址的組合,以及與一個16-bit的TCP或UDP埠號。僅由TCP伺服器呼叫此函式。

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr,int addrlen);

這個呼叫返回0,則表示它成功系結的地址,否則它給-1的錯誤。

引數:

  • sockfd: 是socket函式返回一個通訊端描述符。

  • my_addr 是一個指向struct sockaddr的包含本地IP地址和埠。

  • addrlen 設定sizeof為(struct sockaddr).

可以把IP地址和埠自動設定

埠號0值意味著系統將隨機選擇一個埠和IP地址INADDR_ANY值是指伺服器的IP地址將被自動分配。

server.sin_port = 0;  		     
server.sin_addr.s_addr = INADDR_ANY;

註: 不倫不類的埠和服務的教學,所有埠小於1024被保留。所以,可以設定1024以上的埠(但小於65535),同時設定埠不能正在被其他程式使用

listen 函式:

監聽listen函式被呼叫時,只能由一個TCP伺服器,它執行兩個動作:

  • 監聽函式將陷入被動通訊端未連線的通訊端,表明核心應該接受傳入的連線請求定向到該通訊端。

  • 這個函式的第二個引數指定連線的核心應此通訊端佇列的最大數目。

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd,int backlog);

這個呼叫成功返回0,否則它返回-1的錯誤。

引數:

  • sockfd: socket函式返回一個通訊端描述符。

  • backlog 允許的連線數。

accept 函式:

由TCP伺服器呼叫accept函式返回下一個已完成連線,從完整的連線佇列的前面。以下是呼叫的簽名:

#include <sys/types.h>
#include <sys/socket.h>

int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

這個呼叫返回非負描述符成功,否則 -1 為出錯。返回的描述符被假定為一個用戶端的通訊端描述符,描述的所有讀寫操作的工作在用戶端通訊。

引數:

  • sockfd: socket函式返回一個通訊端描述符。

  • cliaddr 是一個指向struct sockaddr,包含用戶端的IP地址和埠。

  • addrlen 它設定於sizeof(struct sockaddr).

send 函式:

傳送功能是用來傳送資料流通訊端或連線的資料報通訊端。如果想在未連線的資料報通訊端傳送資料,必須使用sendto()函式。

可以使用write()系統呼叫傳送資料。此呼叫解釋在輔助功能的教學。

int send(int sockfd, const void *msg, int len, int flags);

這個呼叫返回傳送出去的位元組數,否則將返回-1錯誤.

引數:

  • sockfd: 是socket函式返回一個通訊端描述符。

  • msg 要傳送的資料是一個指標。

  • len 是要傳送的資料(以位元組為單位)長度。

  • flags 設定為 0.

recv 函式:

recv函式是用來接收資料流通訊端或連線資料報通訊端。如果想在未連線的資料報通訊端接收資料,必須使用recvfrom()函式.

可以使用read()系統呼叫來讀取資料。此呼叫解釋在輔助功能的教學。

int recv(int sockfd, void *buf, int len, unsigned int flags);

這個呼叫返回讀入緩衝區的位元組數,否則將返回-1錯誤。

引數:

  • sockfd: socket函式返回一個通訊端描述符。

  • buf 緩衝區讀取資訊。

  • len 最大的緩衝區的長度。

  • flags 設定為 0.

sendto 函式:

sendto函式用於未連線的資料報通訊端傳送資料。簡單地說,當使用SCOKET型別為SOCK_DGRAM

int sendto(int sockfd, const void *msg, int len, unsigned int flags,
		   const struct sockaddr *to, int tolen);

這個呼叫返回傳送的位元組數否則將返回-1錯誤。

引數:

  • sockfd: socket函式返回一個通訊端描述符。

  • msg 要傳送的資料是一個指標。

  • len 是要傳送的資料(以位元組為單位)的長度。

  • flags 設定為 0.

  • to 是一個指向結構sockaddr的主機要傳送資料。

  • tolen is set it to sizeof(struct sockaddr).

recvfrom 函式:

recvfrom函式用於未連線的資料報通訊端接收資料。簡單地說,當使用SCOKET型別為SOCK_DGRAM時適用。

int recvfrom(int sockfd, void *buf, int len, unsigned int flags
		     struct sockaddr *from, int *fromlen);

這個呼叫返回讀入緩衝區的位元組數,否則將返回-1錯誤。

引數:

  • sockfd: socket函式返回一個通訊端描述符。

  • buf 緩衝區讀取資訊。

  • len 最大的緩衝區的長度。

  • flags 被設定為0。

  • from 是一個指向結構sockaddr的資料的主機被讀取。

  • fromlen 設定為sizeof(struct sockaddr)

close 函式:

close函式是用來關閉用戶端和伺服器之間的通訊。

int close( int sockfd );

這個呼叫成功返回0,否則返回-1錯誤。

引數:

  • sockfd: socket函式返回一個通訊端描述符。

shutdown 函式:

shutdown函式用於正常關閉用戶端和伺服器之間的通訊。此函式提供了更多的控制在比較close函式。

int shutdown(int sockfd, int how);

這個呼叫成功返回0,否則返回-1錯誤。

引數:

  • sockfd: socket函式返回一個通訊端描述符。

  • how: 放入一個數位:

    • 0 表示接收不允許的,

    • 1 表明傳送不允許

    • 2 表明禁止傳送和接收。如果設定為2,它與close()同樣。

select 函式:

select函式顯示指定檔案的描述符是以待準備就緒讀取,準備寫入或有一個錯誤條件。

當應用程式呼叫recv或recvfrom被阻塞,直到資料到達該通訊端。一個應用程式可以做其他有用的處理,而輸入的資料流是空的。另一種情況是,當應用程式從多個通訊端接收資料。

呼叫recv或recvfrom防止立即接收資料與其他Socket上,它的輸入佇列中沒有資料。 select函式呼叫來解決這個問題,允許程式輪詢所有的通訊端手柄,看看他們是否有無阻塞讀取和寫入操作。

 int select(int  nfds,  fd_set  *readfds,  fd_set  *writefds,
     fd_set *errorfds, struct timeval *timeout);

這個呼叫成功返回0,否則返回-1錯誤。

引數:

  • nfds: specifies the range of file descriptors to be tested. The select() function tests file descriptors in the range of 0 to nfds-1

  • readfds:points to an object of type fd_set that on input specifies the file descriptors to be checked for being ready to read, and on output indicates which file descriptors are ready to read. Can be NULL to indicate an empty set.

  • writefds:points to an object of type fd_set that on input specifies the file descriptors to be checked for being ready to write, and on output indicates which file descriptors are ready to write Can be NULL to indicate an empty set.

  • exceptfds :points to an object of type fd_set that on input specifies the file descriptors to be checked for error conditions pending, and on output indicates which file descriptors have error conditions pending. Can be NULL to indicate an empty set.

  • timeout :poins to a timeval struct that specifies how long the select call should poll the descriptors for an available I/O operation. If the timeout value is 0, then select will return immediately. If the timeout argument is NULL, then select will block until at least one file/socket handle is ready for an available I/O operation. Otherwise select will return after the amount of time in the timeout has elapsed OR when at least one file/socket descriptor is ready for an I/O operation.

返回值選擇多少檔案描述符集指定的控制代碼,選擇返回0,準備就緒I/O如果超時欄位指定的時限到達時。下面的巨集存在操縱一個檔案描述符集:

  • FD_CLR(fd, &fdset): 清除位檔案描述符fd檔案描述符集fdset。

  • FD_ISSET(fd, &fdset): 返回一個非零值,如果該位被設定為檔案描述符fd檔案描述符集fdset指向,否則返回0。

  • FD_SET(fd, &fdset): 位設定檔案描述符fd檔案描述符集fdset。

  • FD_ZERO(&fdset): 初始化檔案描述符集fdset所有檔案描述符的零位。

這些巨集的行為是不確定的,如果引數fd小於0或大於或等於FD_SETSIZE。

例如:

fd_set fds;

struct timeval tv;

/* do socket initialization etc.

tv.tv_sec = 1;
tv.tv_usec = 500000;

/* tv now represents 1.5 seconds */
 
FD_ZERO(&fds);

/* adds sock to the file descriptor set */
FD_SET(sock, &fds); 

/* wait 1.5 seconds for any data to be read 
   from any single socket */

select(sock+1, &fds, NULL, NULL, &tv);
if (FD_ISSET(sock, &fds))
{
   recvfrom(s, buffer, buffer_len, 0, &sa, &sa_len);
   /* do something */
}
else
{
   /* do something else */
}