Unix Socket - 核心功能


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

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

套接字客户端服务器

套接字功能

要执行网络 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_路由 路由套接字
AF_KEY 凯特插座

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

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

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

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

协议 描述
IP协议_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议

连接函数_

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

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

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

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

参数

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

  • serv_addr - 它是一个指向 struct sockaddr 的指针,其中包含目标 IP 地址和端口。

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

绑定函数_

绑定函数本地协议地址分配给套接字。对于 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,除非它们被其他程序使用。

监听功能_

监听函数仅由 TCP 服务器调用,它执行两个操作-

  • 监听函数将未连接的套接字转换为被动套接字,表明内核应该接受指向该套接字的传入连接请求。

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

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

int listen(int sockfd,int backlog);

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

参数

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

  • backlog - 这是允许的连接数。

接受函数_

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

#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函数用于通过流套接字或 CONNECTED 数据报套接字发送数据如果要通过未连接的数据报套接字发送数据,则必须使用 sendto() 函数。

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

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

该调用返回发送出去的字节数,否则错误时返回-1。

参数

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

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

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

  • flags - 设置为 0。

接收函数_

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

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

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

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

参数

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

  • buf - 读取信息的缓冲区。

  • len - 缓冲区的最大长度。

  • flags - 设置为 0。

发送函数

sendto函数用于通过 UNCONNECTED 数据报套接字发送数据。其签名如下 -

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函数用于从 UNCONNECTED 数据报套接字接收数据

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函数用于关闭客户端和服务器之间的通信。其语法如下 -

int close( int sockfd );

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

参数

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

关机功能_

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

int shutdown(int sockfd, int how);

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

参数

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

  • 如何- 输入其中一个数字 -

    • 0 - 表示不允许接收,

    • 1 - 表示不允许发送,并且

    • 2 - 表示不允许发送和接收。当how设置为2时,与close()是一样的。

选择功能_

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

当应用程序调用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) - 如果在fdset指向的文件描述符集中设置了文件描述符fd的位,则返回非零值,否则返回 0。

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

  • FD_ZERO(&fdset) - 初始化文件描述符集 fdset,使所有文件描述符都具有零位。

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

例子

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