Unix 套接字 - 核心函数

本章介绍编写完整的 TCP 客户端和服务器所需的核心套接字函数。

下图显示了完整的客户端和服务器交互 −

Socket Client Server

套接字函数

要执行网络 I/O,进程必须做的第一件事是调用套接字函数,指定所需的通信协议类型和协议系列等。

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

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

此调用返回一个套接字描述符,您可以在以后的系统调用中使用它,如果出错则返回 -1。

参数

family −它指定协议系列,是下面显示的常量之一 −

系列 描述
AF_INET IPv4 协议
AF_INET6 IPv6 协议
AF_LOCAL Unix 域协议
AF_ROUTE 路由套接字
AF_KEY Ket socket

本章不涉及除 IPv4 之外的其他协议。

type − 它指定您想要的套接字类型。它可以采用以下值之一 −

类型 描述
SOCK_STREAM 流套接字
SOCK_DGRAM 数据报套接字
SOCK_SEQPACKET 顺序数据包套接字
SOCK_RAW 原始套接字

协议 −参数应设置为下面给出的特定协议类型,或设置为 0 以选择系统针对给定系列和类型组合的默认值 −

协议 描述
IPPROTO_TCP TCP 传输协议
IPPROTO_UDP UDP 传输协议
IPPROTO_SCTP SCTP 传输协议

connect 函数

TCP 客户端使用 connect 函数与 TCP 建立连接服务器。

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

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

如果成功连接到服务器,此调用将返回 0,否则在出错时返回 -1。

参数

  • sockfd − 它是套接字函数返回的套接字描述符。

  • serv_addr − 它是指向包含目标 IP 地址和端口的 struct sockaddr 的指针。

  • addrlen − 将其设置为 sizeof(struct sockaddr)。

bind 函数

bind 函数将本地协议地址分配给套接字。对于 Internet 协议,协议地址是 32 位 IPv4 地址或 128 位 IPv6 地址与 16 位 TCP 或 UDP 端口号的组合。此函数仅由 TCP 服务器调用。

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

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

如果成功绑定到地址,此调用将返回 0,否则在出错时返回 -1。

参数

  • sockfd − 它是套接字函数返回的套接字描述符。

  • my_addr − 它是指向包含本地 IP 地址和端口的 struct sockaddr 的指针。

  • 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 服务器调用,它执行两个操作 −

  • listen 函数将未连接的套接字转换为被动套接字,指示内核应接受指向此套接字的传入连接请求。

  • 此函数的第二个参数指定内核应为此套接字排队的最大连接数。

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

int listen(int sockfd,int backlog);

此调用成功时返回 0,否则错误时返回 -1。

参数

  • sockfd − 它是套接字函数返回的套接字描述符。

  • backlog − 它是允许的连接数。

accept 函数

accept 函数由 TCP 服务器调用,以从已完成连接队列的前面返回下一个已完成的连接。调用的签名如下 −

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

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

此调用成功时返回一个非负描述符,否则出错时返回 -1。返回的描述符被认为是客户端套接字描述符,所有读写操作都将在此描述符上完成以与客户端通信。

参数

  • sockfd − 它是套接字函数返回的套接字描述符。

  • cliaddr − 它是指向包含客户端 IP 地址和端口的 struct sockaddr 的指针。

  • addrlen −将其设置为sizeof(struct sockaddr)。

send函数

send函数用于通过流套接字或CONNECTED数据报套接字发送数据。如果要通过UNCONNECTED数据报套接字发送数据,则必须使用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 函数用于通过未连接的数据报套接字发送数据。其签名如下 −

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

此调用返回发送的字节数,否则在出错时返回 -1。

参数

  • sockfd − 它是套接字函数返回的套接字描述符。

  • msg − 它是指向要发送的数据的指针。

  • len − 它是要发送的数据的长度(以字节为单位)。

  • flags − 它设置为 0。

  • to − 它是指向要发送数据的主机的 struct sockaddr 的指针。

  • tolen −它被设置为sizeof(struct sockaddr)。

recvfrom 函数

recvfrom 函数用于从未连接的数据报套接字接收数据。

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

此调用返回读入缓冲区的字节数,否则在出错时返回 -1。

参数

  • sockfd − 它是套接字函数返回的套接字描述符。

  • buf −它是将信息读入的缓冲区。

  • len − 它是缓冲区的最大长度。

  • flags − 它设置为 0。

  • from − 它是指向要读取数据的主机的 struct sockaddr 的指针。

  • fromlen − 它设置为 sizeof(struct sockaddr)。

close 函数

close 函数用于关闭客户端与服务器之间的通信。其语法如下 −

int close( int sockfd );

此调用成功时返回 0,否则出错时返回 -1。

参数

  • sockfd − 它是套接字函数返回的套接字描述符。

shutdown 函数

shutdown 函数用于正常关闭客户端与服务器之间的通信。与 close 函数相比,此函数提供更多控制。下面给出了 shutdown 的语法 −

int shutdown(int sockfd, int how);

此调用成功时返回 0,否则错误时返回 -1。

参数

  • sockfd − 它是 socket 函数返回的套接字描述符。

  • how − 放入其中一个数字 −

    • 0 − 表示不允许接收,

    • 1 − 表示不允许发送,

    • 2 − 表示不允许发送和接收。当 how 设置为 2 时,它与 close() 相同。

select 函数

select 函数指示指定的哪个文件描述符已准备好读取、已准备好写入或有待处理的错误条件。

当应用程序调用 recv 或 recvfrom 时,它会被阻止,直到数据到达该套接字。当传入数据流为空时,应用程序可能正在执行其他有用的处理。另一种情况是应用程序从多个套接字接收数据。

在输入队列中没有数据的套接字上调用 recv 或 recvfrom 会阻止立即从其他套接字接收数据。 select 函数调用解决了这个问题,它允许程序轮询所有套接字句柄,以查看它们是否可用于非阻塞读写操作。

下面给出了 select

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

此调用在成功时返回 0,否则在出错时返回 -1。

参数

  • nfds − 它指定要测试的文件描述符的范围。 select() 函数测试 0 到 nfds-1 范围内的文件描述符

  • readfds − 它指向一个 fd_set 类型的对象,该对象在输入时指定要检查的文件描述符是否准备好读取,在输出时指示哪些文件描述符已准备好读取。它可以为 NULL,表示为空集。

  • writefds − 它指向一个 fd_set 类型的对象,该对象在输入时指定要检查的文件描述符是否准备好写入,在输出时指示哪些文件描述符已准备好写入。它可以为 NULL,表示为空集。

  • exceptfds −它指向一个 fd_set 类型的对象,该对象在输入时指定要检查的文件描述符是否存在待处理的错误条件,并在输出时指示哪些文件描述符存在待处理的错误条件。它可以为 NULL,表示为空集。

  • timeout − 它指向一个 timeval 结构,该结构指定 select 调用应轮询描述符以进行可用 I/O 操作的时间。如果超时值为 0,则 select 将立即返回。如果超时参数为 NULL,则 select 将阻塞,直到至少一个文件/套接字句柄已准备好进行可用的 I/O 操作。否则,select 将在超时时间过后或至少一个文件/套接字描述符已准备好进行 I/O 操作后返回。

select 的返回值是文件描述符集中指定的已准备好进行 I/O 的句柄数。如果达到超时字段指定的时间限制,则选择返回 0。存在以下宏用于操作文件描述符集 −

  • FD_CLR(fd, &fdset) − 清除文件描述符集合 fdset 中文件描述符 fd 的位。>

  • FD_ISSET(fd, &fdset) − 如果文件描述符 fd 的位在 fdset 指向的文件描述符集合中设置,则返回非零值,否则返回 0。

  • FD_SET(fd, &fdset) − 设置文件描述符集合 fdset 中文件描述符 fd 的位。

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