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 */
}