Unix套接字 - 核心函数



本章描述了编写完整的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协议
AF_INET6 IPv6协议
AF_LOCAL Unix域协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字

本章不涵盖IPv4以外的其他协议。

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

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

protocol − 参数应设置为下面给定的特定协议类型,或设置为0以选择系统为给定的family和type组合选择的默认值:

协议 描述
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 − 由socket函数返回的套接字描述符。

  • serv_addr − 指向包含目标IP地址和端口的sockaddr结构体的指针。

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

bind函数

bind函数将本地协议地址分配给套接字。对于互联网协议,协议地址是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 − 由socket函数返回的套接字描述符。

  • my_addr − 指向包含本地IP地址和端口的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 − 由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 − 指向包含客户端IP地址和端口的sockaddr结构体的指针。

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

send函数

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函数用于通过未连接的数据报套接字发送数据。其签名如下:

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 − 设置为sizeof(struct sockaddr)。

recvfrom函数

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

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函数相比,此函数提供了更多控制。以下是shutdown的语法:

int shutdown(int sockfd, int how);

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

参数

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

  • how − 放入以下数字之一:

    • 0 − 表示不允许接收,

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

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

select函数

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

当应用程序调用recvrecvfrom时,它会被阻塞,直到该套接字有数据到达。当传入数据流为空时,应用程序可以进行其他有用的处理。另一种情况是当应用程序从多个套接字接收数据时。

在输入队列中没有数据的套接字上调用recvrecvfrom会阻止立即从其他套接字接收数据。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操作的句柄数。如果超时字段指定的时间限制已到,select返回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 */
}
广告