📅  最后修改于: 2020-11-05 04:53:49             🧑  作者: Mango
本章介绍编写完整的TCP客户端和服务器所需的核心套接字功能。
下图显示了客户端和服务器的完整交互-
要执行网络I / O,进程必须做的第一件事是调用套接字函数,指定所需的通信协议类型和协议族等。
#include
#include
int socket (int family, int type, int protocol);
该调用返回一个套接字描述符,您可以在以后的系统调用中使用它,如果出错则返回-1。
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 | Description |
---|---|
SOCK_STREAM | Stream socket |
SOCK_DGRAM | Datagram socket |
SOCK_SEQPACKET | Sequenced packet socket |
SOCK_RAW | Raw socket |
protocol-参数应设置为以下给定的特定协议类型,或设置为0以选择给定的family和type组合的系统默认值-
Protocol | Description |
---|---|
IPPROTO_TCP | TCP transport protocol |
IPPROTO_UDP | UDP transport protocol |
IPPROTO_SCTP | SCTP transport protocol |
TCP客户端使用connect函数建立与TCP服务器的连接。
#include
#include
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
如果成功连接到服务器,则此调用返回0,否则返回-1。
sockfd-它是套接字函数返回的套接字描述符。
serv_addr-它是指向结构sockaddr的指针,其中包含目标IP地址和端口。
addrlen-将其设置为sizeof(struct sockaddr)。
绑定函数将本地协议地址分配给套接字。对于Internet协议,协议地址是32位IPv4地址或128位IPv6地址以及16位TCP或UDP端口号的组合。此函数仅由TCP服务器调用。
#include
#include
int bind(int sockfd, struct sockaddr *my_addr,int addrlen);
如果成功绑定到该地址,则此调用返回0,否则返回-1。
sockfd-它是套接字函数返回的套接字描述符。
my_addr-它是指向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以下。
侦听函数仅由TCP服务器调用,它执行两个操作-
listen函数将未连接的套接字转换为被动套接字,指示内核应接受针对该套接字的传入连接请求。
此函数的第二个参数指定内核应为此套接字排队的最大连接数。
#include
#include
int listen(int sockfd,int backlog);
该调用成功返回0,否则返回-1。
sockfd-它是套接字函数返回的套接字描述符。
待办事项-这是允许的连接数。
TCP服务器调用accept函数,以从完成的连接队列的开头返回下一个完成的连接。呼叫的签名如下-
#include
#include
int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
此调用在成功时返回非负描述符,否则在错误时返回-1。假定返回的描述符是客户端套接字描述符,并且所有读写操作都将在此描述符上进行以与客户端进行通信。
sockfd-它是套接字函数返回的套接字描述符。
cliaddr-它是指向结构sockaddr的指针,其中包含客户端IP地址和端口。
addrlen-将其设置为sizeof(struct sockaddr)。
send函数用于通过流套接字或CONNECTED数据报套接字发送数据。如果要通过UNCONNECTED数据报套接字发送数据,则必须使用sendto()函数。
您可以使用write()系统调用来发送数据。其签名如下-
int send(int sockfd, const void *msg, int len, int flags);
此调用返回发送的字节数,否则将在错误时返回-1。
sockfd-它是套接字函数返回的套接字描述符。
msg-它是您要发送的数据的指针。
len-这是您要发送的数据长度(以字节为单位)。
标志-设置为0。
recv函数用于通过流套接字或CONNECTED数据报套接字接收数据。如果要通过UNCONNECTED数据报套接字接收数据,则必须使用recvfrom()。
您可以使用read()系统调用来读取数据。该调用在辅助函数章节中进行了说明。
int recv(int sockfd, void *buf, int len, unsigned int flags);
此调用返回读入缓冲区的字节数,否则将在错误时返回-1。
sockfd-它是套接字函数返回的套接字描述符。
buf-它是读取信息的缓冲区。
len-这是缓冲区的最大长度。
标志-设置为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-这是您要发送的数据长度(以字节为单位)。
标志-设置为0。
到-它是指向要向其发送数据的主机的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-这是缓冲区的最大长度。
标志-设置为0。
从-它是一个指向结构sockaddr对于其中数据必须被读出的主机。
fromlen-将其设置为sizeof(struct sockaddr)。
关闭函数用于关闭客户端和服务器之间的通信。它的语法如下-
int close( int sockfd );
该调用成功返回0,否则返回-1。
sockfd-它是套接字函数返回的套接字描述符。
关闭函数用于正常关闭客户端和服务器之间的通信。与close函数相比,此函数提供了更多控制。下面给出的是shutdown的语法-
int shutdown(int sockfd, int how);
该调用成功返回0,否则返回-1。
sockfd-它是套接字函数返回的套接字描述符。
如何-输入数字之一-
0-表示不允许接收,
1-表示不允许发送,并且
2-表示不允许发送和接收。将how设置为2时,它与close()相同。
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表示空集。
excludefds-指向类型为fd_set的对象,该对象在输入时指定要检查的文件描述符是否存在错误条件,而在输出时指出哪些文件描述符具有错误条件尚未处理。可以为NULL表示空集。
timeout-它指向一个timeval结构,该结构指定select调用应为可用的I / O操作轮询描述符的时间。如果超时值为0,则select将立即返回。如果timeout参数为NULL,则select将阻塞直到至少一个文件/套接字句柄为可用的I / O操作准备就绪。否则,将在超时时间过后或至少有一个文件/套接字描述符为I / O操作准备就绪时返回select 。
select的返回值是在文件描述符集中指定的可用于I / O的句柄数。如果达到了由超时字段指定的时间限制,请选择return0。存在以下用于处理文件描述符集的宏-
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,则这些宏的行为是不确定的。
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 */
}