Linux 下 select() 实现 socket 多路复用IO模型

通常情况下 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并未加锁,谨慎参考。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目简介: 采用I/O复用技术select实现socket通信,采用多线程负责每个客户操作处理,完成Linux下的多客户聊天室! OS:Ubuntu 15.04 IDE:vim gcc make DB:Sqlite 3 Time:2015-12-09 ~ 2012-12-21 项目功能架构: 1. 采用client/server结构; 2. 给出客户操作主界面(注册、登录、帮助和退出)、登录后主界面(查看在线列表、私聊、群聊、查看聊天记录、退出); 3. 多客户可同时连接服务器进行自己操作; ##服务器端## 1. server.c:服务器端主程序代码文件; 2. config.h:服务器端配置文件(包含需要的头文件、常量、数据结构及函数声明); 3. config.c:服务器端公共函数的实现文件; 4. list.c:链表实现文件,用于维护在线用户链表的添加、更新、删除操作; 5. register.c:服务器端实现用户注册; 6. login.c:服务器端实现用户登录; 7. chat.c:服务器端实现用户的聊天互动操作; 8. Makefile:服务器端make文件,控制台执行make命令可直接生成可执行文件server ##客户端## 1. client.c:客户端主程序代码文件; 2. config.h:客户端配置文件(包含需要的头文件、常量、数据结构及函数声明); 3. config.c:客户端公共函数的实现文件; 4. register.c:客户端实现用户注册; 5. login.c:客户端实现用户登录; 6. chat.c:客户端实现用户的聊天互动操作; 7. Makefile:客户端make文件,控制台执行make命令可直接生成可执行文件client;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值