注:本文章着重介绍select关于客户端代码不详细解释
select函数介绍
select是一个用于多路I/O复用的系统调用函数,常用于监听多个文件描述符上是否有读写事件发生。它的原型为:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- nfds表示监听的文件描述符集合中最大的文件描述符值加1。
- readfds、writefds和exceptfds分别表示待检查的读、写和异常事件集合,它们都是指向fd_set类型的指针。
- timeout用于指定select函数等待的最长时间,它也是一个结构体类型。
select函数的返回值表示发生事件的文件描述符的个数,如果返回值为0,则表示在指定时间内没有任何事件发生,如果出错则返回-1。
在使用select函数之前需要对文件描述符集合进行初始化。fd_set类型是一个位图,每一个位表示一个文件描述符。可以通过下面的宏来操作fd_set类型:
FD_ZERO(fd_set *set):将set清零,初始化为一个空集。
FD_SET(int fd, fd_set *set):将set集合中的第fd个描述符设为1。
FD_CLR(int fd, fd_set *set):将set集合中的第fd个描述符设为0。
FD_ISSET(int fd, fd_set *set):如果set集合中的第fd个描述符被设置,则返回1,否则返回0。
select使用流程
select函数的使用流程一般如下:
- 创建并初始化fd_set类型的读、写、异常事件集合;
- 将待检查的文件描述符添加到对应的集合中;
- 调用select函数,并设置超时时间;
- 检查返回值,如果出错则处理错误,否则根据FD_ISSET宏来检查哪些文件描述符发生了事件;
- 处理事件。
程序代码
server
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("use:fd\n");
return -1;
}
//1.socket
int socked = socket(AF_INET, SOCK_STREAM, 0);
if (socked < 0)
{
perror("socket err.");
return -1;
}
//2.listen_fd
struct sockaddr_in listen_addr, accept_addr;
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons(atoi(argv[1]));
listen_addr.sin_addr.s_addr = INADDR_ANY;
socklen_t len = sizeof(accept_addr);
//3.bind
if (bind(socked, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0)
{
perror("bind err.");
return -1;
}
//4.listen
listen(socked, 10);
//1>create fd_fet
fd_set readfds, tempfds;
FD_ZERO(&readfds);
FD_SET(0, &readfds);
FD_SET(socked, &readfds);
int max_fd = socked;
int recv_num;
int accept_num;
char buf[128];
while (1)
{
tempfds = readfds;
int ret = select(max_fd + 1, &tempfds, NULL, NULL, NULL);
if (ret < 0)
{
perror("select err.");
return -1;
}
if (FD_ISSET(0, &tempfds))
{
fgets(buf, sizeof(buf), stdin);
printf("key:%s\n", buf);
for (int i = 4; i <= max_fd; i++)
{
if (FD_ISSET(i, &readfds))
{
send(i, buf, sizeof(buf), 0);
}
}
}
if (FD_ISSET(socked, &tempfds))
{
accept_num = accept(socked, (struct sockaddr *)&accept_addr, &len);
if (accept_num < 0)
{
perror("accept err.");
return -1;
}
printf("duan:%d ip:%s talk:%s \n",
ntohs(accept_addr.sin_port), inet_ntoa(accept_addr.sin_addr),
buf);
FD_SET(accept_num, &readfds);
if (max_fd < accept_num)
{
max_fd = accept_num;
}
}
for (int i = 4; i <= max_fd; i++)
{
if (FD_ISSET(i, &tempfds))
{
recv_num = recv(i, buf, sizeof(buf), 0);
if (recv_num < 0)
{
perror("recv err.");
return -1;
}
else if (recv_num == 0)
{
printf("client is exit\n");
close(i);
FD_CLR(i, &readfds);
if (i == max_fd)
{
max_fd--;
}
break;
}
else
{
printf("%d client:%s\n", i, buf);
}
}
}
// close(accept_fd);这里无关闭
}
close(socked);
return 0;
}
代码分析
这是一个简单的TCP服务器程序,它监听指定的端口,当有客户端连接时,服务器会接受连接,并将客户端的套接字加入到fd_set集合中,然后继续等待其他客户端的连接或者客户端发送数据。
程序中使用了select函数来实现I/O多路复用,同时使用了fd_set类型来存储多个文件描述符。当某个文件描述符上有可读事件时,select会返回,然后程序会对所有处于读就绪状态的文件描述符进行处理。
下面是程序的主要流程:
- 首先检查参数个数是否正确,如果不正确则提示用法并退出程序。
- 创建套接字。
- 绑定端口。
- 监听端口。
- 使用fd_set类型创建一个文件描述符集合,并将标准输入文件描述符和服务器套接字文件描述符加入集合中。
- 进入无限循环,在循环中使用select函数检查文件描述符集合中是否有就绪的文件描述符,如果有则进行处理。
- 如果标准输入文件描述符就绪,表示有数据从键盘输入,将数据发送给所有客户端。
- 如果服务器套接字文件描述符就绪,表示有客户端连接,接受连接,并将客户端套接字文件描述符加入文件描述符集合中。
- 对于其他处于就绪状态的文件描述符,接收数据并输出。
- 重复步骤6到9。
为了验证代码这里分享一段客户端代码来进行验证
client(非本文重点)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("use:fd\n");
return -1;
}
//1.socket
int socked = socket(AF_INET, SOCK_STREAM, 0);
if (socked < 0)
{
perror("socket err.");
return -1;
}
//2.tianchong
struct sockaddr_in listen_addr;
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons(atoi(argv[1]));
listen_addr.sin_addr.s_addr = INADDR_ANY;
int con_val = connect(socked, (struct sockaddr *)&listen_addr, sizeof(listen_addr));
if (con_val < 0)
{
perror("con_val err.");
return -1;
}
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork err.");
}
else if (pid == 0)
{
char buf[128];
ssize_t ret;
while (1)
{
ret = recv(socked, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("recv err");
return -1;
}
else if (ret == 0)
{
printf("server is exit\n");
break;
}
else
{
printf("%s\n", buf);
}
}
}
else
{
char buf[128];
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
{
buf[strlen(buf) - 1] == '\0';
}
send(socked, buf, sizeof(buf), 0);//这里
}
}
return 0;
}