通常情况下 recv() 函数是阻塞的,发起接收请求就会一直等待,直到数据返回。当recv()阻塞时,当前线程就会进入休眠状态,这意味着线程不能做其它事情。举个例子,当线程想退出时,就只能等数据返回,线程被唤醒才有机会执行代码。
使用 select() 函数可以监视 socket, 当 I/O 操作准备就绪时立即返回,I/O 未就绪时进入休眠等待。虽然 select() 也会进入等待,但 select() 提供了一个超时机制,当指定的超时时间超时也会立即返回,这就给了线程执行代码的机会。select() 也可同时监控多个 socket, 使得通常每个socket使用一个线程进行 I/O 操作改进成多个 socket 复用一个线程进行 I/O 操作。
示例程序:
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define MAX_CLIENTS 1000
bool bexit = false;
void *thread_proc(void *arg)
{
int remote_fds[MAX_CLIENTS] = { 0 };
int remote_fds_cnt = 0;
int maxfd = 0;
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket()");
return NULL;
}
maxfd = fd;
struct sockaddr_in local_addr;
bzero(&local_addr, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY;
local_addr.sin_port = htons(4000);
int ret = bind(fd, (const struct sockaddr *)&local_addr, sizeof(local_addr));
if (ret < 0) {
perror("bind()");
close(fd);
return NULL;
}
ret = listen(fd, 5);
if (ret < 0) {
perror("listen()");
close(fd);
return NULL;
}
struct sockaddr_in remote_addr;
bzero(&remote_addr, sizeof(remote_addr));
socklen_t addrlen = sizeof(remote_addr);
printf("listen ...\n");
fd_set fds;
struct timeval tv = { 3, 0 };
while (!bexit) {
FD_ZERO(&fds);
FD_SET(fd, &fds);
for (int i = 0; i < remote_fds_cnt; i++) {
if (remote_fds[i] != -1) {
FD_SET(remote_fds[i], &fds);
}
}
tv.tv_sec = 3;
tv.tv_usec = 0;
ret = select(maxfd + 1, &fds, NULL, NULL, &tv);
if (ret < 0) {
perror("select()");
return NULL;
} else if (ret == 0) {
printf("timeout.\n");
continue;
}
if (FD_ISSET(fd, &fds)) {
int remotefd = accept(fd, (struct sockaddr *)&remote_addr, &addrlen);
if (remotefd == -1) {
perror("accept()");
close(fd);
return NULL;
}
remote_fds[remote_fds_cnt++] = remotefd;
maxfd = remotefd;
printf("client %s:%d is connected.\n", inet_ntoa(remote_addr.sin_addr), remote_addr.sin_port);
}
for (int i = 0; i < remote_fds_cnt; i++) {
int remotefd = remote_fds[i];
if (FD_ISSET(remotefd, &fds)) {
char buf[1024];
ssize_t bytes = recv(remotefd, buf, sizeof(buf), 0);
if (bytes < 0) {
printf("%ld\n", bytes);
perror("recv()");
close(remotefd);
remote_fds[i] = -1;
break;
} else if (bytes == 0) {
printf("%ld\n", bytes);
perror("recv()");
close(remotefd);
remote_fds[i] = -1;
break;
} else {
printf("%ld %.*s\n", bytes, (int)bytes, buf);
}
}
}
}
close(fd);
return NULL;
}
int main()
{
pthread_t thread;
pthread_create(&thread, NULL, thread_proc, NULL);
printf("Enter key 'q' to exit.\n");
while (getchar() != 'q');
bexit = true;
pthread_join(thread, NULL);
return 0;
}
gcc app.c -lpthread -o app
可以使用网络调试助手进行调试。
此程序属实验性质程序,并不完整,对共享变量bexit并未加锁,谨慎参考。