Unix Sockets 简明教程

Unix Socket - Core Functions

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

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

socket client server

The socket Function

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

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

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

此调用会返回一个套接字描述符,可以在后续的系统调用中使用,或在出错时返回 -1。

Parameters

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

Family

Description

AF_INET

IPv4 protocols

AF_INET6

IPv6 protocols

AF_LOCAL

Unix domain protocols

AF_ROUTE

Routing Sockets

AF_KEY

Ket socket

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

type − 指定你想要哪种套接字。可以采用以下值之一 −

Type

Description

SOCK_STREAM

Stream socket

SOCK_DGRAM

Datagram socket

SOCK_SEQPACKET

Sequenced packet socket

SOCK_RAW

Raw socket

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

Protocol

Description

IPPROTO_TCP

TCP transport protocol

IPPROTO_UDP

UDP transport protocol

IPPROTO_SCTP

SCTP transport protocol

The connect Function

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

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

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

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

Parameters

  1. sockfd − 由 socket 函数返回的套接字描述符。

  2. serv_addr − 指向包含目标 IP 地址和端口的 struct sockaddr。

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

The bind Function

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。

Parameters

  1. sockfd − 由 socket 函数返回的套接字描述符。

  2. my_addr − 指向包含本地 IP 地址和端口的 struct sockaddr。

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

你可以自动放入你的 IP 地址和端口

端口号的 0 值表示系统将选择一个随机端口,并且 IP 地址的 INADDR_ANY 值表示将自动分配服务器的 IP 地址。

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

NOTE − 低于 1024 的所有端口都是保留的。你可以设置 1024 以上和 65535 以下的端口,除非它们被其他程序使用。

The listen Function

listen 函数仅由 TCP 服务器调用,并且它执行两个操作 −

  1. listen 函数将未连接的套接字转换为一个被动套接字,表示内核应该接受定向到此套接字的传入连接请求。

  2. 传递给此函数的第二个参数指定内核应该为该套接字排队的最大连接数。

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

int listen(int sockfd,int backlog);

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

Parameters

  1. sockfd − 由 socket 函数返回的套接字描述符。

  2. backlog − 为允许的连接数。

The accept Function

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

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

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

此调用成功时返回非负描述符,否则在错误时返回 -1。假定返回的描述符为客户端套接字描述符,并且所有读写操作都将在此描述符上执行以与客户端进行通信。

Parameters

  1. sockfd − 由 socket 函数返回的套接字描述符。

  2. cliaddr − 为指向包含客户端 IP 地址和端口的 struct sockaddr 的指针。

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

The send Function

send 函数用于通过流套接字或已连接的数据报套接字发送数据。如果您想通过未连接的数据报套接字发送数据,则必须使用 sendto() 函数。

您可以使用 write() 系统调用来发送数据。其签名如下:

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

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

Parameters

  1. sockfd − 由 socket 函数返回的套接字描述符。

  2. msg − 为指向要发送的数据的指针。

  3. len − 为要发送的数据的长度(以字节为单位)。

  4. flags − 设置为 0。

The recv Function

recv 函数用于通过流套接字或已连接的数据报套接字接收数据。如果您想通过未连接的数据报套接字接收数据,则必须使用 recvfrom()。

您可以使用 read() 系统调用来读取数据。此调用在帮助程序函数章节中进行了解释。

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

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

Parameters

  1. sockfd − 由 socket 函数返回的套接字描述符。

  2. buf − 为读取信息的缓冲区。

  3. len − 为缓冲区的最大长度。

  4. flags − 设置为 0。

The sendto Function

sendto 函数用于通过未连接的数据报套接字发送数据。其签名如下:

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

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

Parameters

  1. sockfd − 由 socket 函数返回的套接字描述符。

  2. msg − 为指向要发送的数据的指针。

  3. len − 为要发送的数据的长度(以字节为单位)。

  4. flags − 设置为 0。

  5. to − 为指向数据将发送到的主机的 struct sockaddr。

  6. tolen − 将其设置为 sizeof(struct sockaddr)。

The recvfrom Function

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

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

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

Parameters

  1. sockfd − 由 socket 函数返回的套接字描述符。

  2. buf − 为读取信息的缓冲区。

  3. len − 为缓冲区的最大长度。

  4. flags − 设置为 0。

  5. from − 这是数据必须从中读取的主机的 struct sockaddr 的指针。

  6. fromlen − 将其设置为 sizeof(struct sockaddr)。

The close Function

close 函数用来关闭客户端和服务器之间的通信。其语法如下:

int close( int sockfd );

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

Parameters

  1. sockfd − 由 socket 函数返回的套接字描述符。

The shutdown Function

shutdown 函数用来优雅地关闭客户端和服务器之间的通信。与 close 函数相比,此函数提供更多控制。以下是 shutdown 的语法:

int shutdown(int sockfd, int how);

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

Parameters

  1. sockfd − 由 socket 函数返回的套接字描述符。

  2. how − 放入以下某个数字: 0 − 表示不允许接收, 1 − 表示不允许发送, 2 − 表示不允许发送和接收。当 how 设置为 2 时,它与 close() 相同。

The select Function

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。

Parameters

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

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

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

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

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

来自 select 的返回值是文件描述符集中指定的文件句柄数,这些句柄已准备好进行 I/O。如果 timeout 字段指定的时间限制达到,select 返回 0。以下宏用于操作文件描述符集:

  1. FD_CLR(fd, &amp;fdset) − 清除文件描述符集 fdset 中文件描述符 fd 的位。

  2. FD_ISSET(fd, &amp;fdset) − 如果文件描述符集中由fdset 指向的文件描述符的位被设置,则返回一个非零值;否则返回 0。

  3. FD_SET(fd, &amp;fdset) − 在文件描述符集中 fdset 中设置文件描述符 fd 的位。

  4. FD_ZERO(&amp;fdset) − 初始化文件描述符集 fdset 以便所有文件描述符的位都为零。

如果 fd 参数小于 0 或大于或等于 FD_SETSIZE,则这些宏的行为是未定义的。

Example

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