参考文章:http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html
IO多路复用:内核一旦发现进程指定的一个或者多个IO条件准备读取,就通知该进程。优势是系统不需要创建进程/线程,也不必维护,减少开销
适用场景:
1)当处理多个描述符是(一般是交互式输入和网络socket接口),必须使用IO复用
2)当同时处理多个socket接口
3)TCP服务器既要监听连接请求,又要处理已连接socket
4)既要处理TCP,又要处理UDP
5)服务器需要处理多个服务或者协议
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就绪描述符的数目,超时返回0,出错返回-1
Server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <assert.h>
#define IPADDR "127.0.0.1"
#define PORT 8787
#define MAXLINE 1024 //接收buffer的最大长度
#define LISTENQ 5 //指定能同时处理的最大连接数
#define SIZE 10 //限制客户端个数
typedef struct server_context_st{
int cli_cnt; // 客户端个数
int clifds[SIZE]; // 客户端,保存客户端FDS
fd_set allfds; // 句柄集合
int maxfd; // 句柄最大值
}server_context_st;
static server_context_st *s_srv_ctx = NULL;
//client连接请求
static int accept_client_proc(int srvfd)
{
int clifd = -1;
int i;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
cliaddrlen = sizeof(cliaddr);
// 接受client连接请求
clifd = accept(srvfd, (struct sockaddr*)&cliaddr, &cliaddrlen);
if(clifd == -1){
printf("accept failed ,errno = %d, %s\r\n", errno, strerror(errno));
return -1;
}
printf("accept new client: %s %d\r\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
// 将客户端描述符保存,在select函数中使用
for(i = 0 ; i < SIZE; i++){
if(s_srv_ctx->clifds[i] == -1){
s_srv_ctx->clifds[i] = clifd;
s_srv_ctx->cli_cnt++;
break;
}
}
if(i == SIZE){
printf("client full up\r\n");
return -2;
}
return 0;
}
//接收client消息
static void recv_client_msg(fd_set *readfds)
{
int i;
char recv_buf[MAXLINE];
int len;
int clifd;
for(i = 0 ; i < s_srv_ctx->cli_cnt; i++){
clifd = s_srv_ctx->clifds[i];
if(clifd < 0)
continue;
// 判断是哪个client 描述符 发送消息
if(FD_ISSET(clifd, readfds)){
//读
len = read(clifd, recv_buf, MAXLINE);
if(len <= 0){
// n=0 可能client连接已中断,将对应client 描述符从集合中删除
FD_CLR(clifd, &s_srv_ctx->allfds);
s_srv_ctx->clifds[i] = -1;
// 关闭TCP连接
close(clifd);
printf("close connection ID = %d\r\n", i);
continue;
}
else{
// 将接收到的消息重新返回client
printf("server recv %dByte msg : %s\r\n", len, recv_buf);
write(clifd, recv_buf, len);
}
}
}
}
int main(int agrc , char **agrv)
{
int i;
int srvfd;
int ret;
/* Step1 初始化 s_srv_ctx 结构体,将所有client成员设置为无效-1*/
s_srv_ctx = (server_context_st *)malloc(sizeof(server_context_st));
if(s_srv_ctx == NULL){
return -1;
}
memset(s_srv_ctx, 0, sizeof(server_context_st));
for(i = 0 ; i < SIZE; i++){
s_srv_ctx->clifds[i] = -1;
}
/* Step2 创建server socket bind ip和port 监听客户端连接 */
srvfd = socket(AF_INET, SOCK_STREAM, 0);
if(srvfd == -1){
printf("creat socket failed, errno = %d, reason: %s\r\n", errno, strerror(errno));
goto s_exit;
}
// SO_REUSEADDR 设置端口释放后可以立即再次使用
int reuse = 1;
if(setsockopt(srvfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1){
printf("setsockopt failed \r\n");
goto s_exit;
}
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(struct sockaddr_in));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, IPADDR, &servaddr.sin_addr);
servaddr.sin_port = htons(PORT);
if(bind(srvfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf("bind error\r\n");
goto s_exit;
}
listen(srvfd, LISTENQ);
/* Step3 接收并处理客户端请求 */
struct timeval tv;
while(1){
// 每次调用select之前都要重新设置文件描述符和时间,因为事件发生后文件描述符和时间被系统修改
// 添加描述符srvfd,用于监听是否有客户端连接
FD_ZERO(&s_srv_ctx->allfds);
FD_SET(srvfd, &s_srv_ctx->allfds);
s_srv_ctx->maxfd = srvfd;
// 轮询client列表,判断客户端是否有效,并添加有效的客户端描述符clifds[i]
for(i = 0 ; i < s_srv_ctx->cli_cnt; i++){
if(s_srv_ctx->clifds[i] != -1){
FD_SET(s_srv_ctx->clifds[i], &s_srv_ctx->allfds);
}
//设置最大描述符
if(s_srv_ctx->clifds[i] > s_srv_ctx->maxfd){
s_srv_ctx->maxfd = s_srv_ctx->clifds[i];
}
}
tv.tv_sec = 20;
tv.tv_usec = 0;
ret = select(s_srv_ctx->maxfd+1, &s_srv_ctx->allfds, NULL, NULL, &tv);
if(ret == -1){
printf("select error = %d %s\r\n", errno, strerror(errno));
break;
}else if(ret == 0){
printf("time out\r\n");
continue;
}
if(FD_ISSET(srvfd, &s_srv_ctx->allfds)){
//printf("client request\r\n");
accept_client_proc(srvfd); // 处理client请求连接
}else{
//printf("client msg proc\r\n");
recv_client_msg(&s_srv_ctx->allfds); // 处理client发送消息
}
}
// 退出,释放资源
s_exit:
free(s_srv_ctx);
s_srv_ctx = NULL;
}
Client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include <assert.h>
#define MAXLINE 1024
#define IPADDRESS "127.0.0.1"
#define SERV_PORT 8787
static char recv_buf[MAXLINE];
static char hello_server[] = {"hello server"};
int main(int argc , char **argv)
{
int sockfd;
int ret;
struct sockaddr_in servaddr;
// Step1 创建client socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(struct sockaddr_in));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, IPADDRESS, &servaddr.sin_addr);
// Step2 请求连接server
ret = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if(ret < 0){
printf("connect failed ,errno = %d, %s\r\n", errno, strerror(errno));
return -1;
}
// 发送消息 hello
write(sockfd, hello_server, sizeof(hello_server));
int len;
fd_set readfds;
struct timeval tv;
while(1){
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
//
tv.tv_sec = 5;
tv.tv_usec = 0;
ret = select(sockfd+1, &readfds, NULL, NULL, &tv);
if(ret == -1){
printf("client select error = %d, %s\r\n", errno, strerror(errno));
return -2;
}else if(ret == 0){
printf("client time out\r\n");
continue;
}
if(FD_ISSET(sockfd, &readfds)){
len = read(sockfd, recv_buf, MAXLINE);
if(len <= 0){
printf("server connection closed\r\n");
goto c_exit;
}
else{
// 将server发送的消息返回server ,其实是第一次从client发送的hello
printf("client recv %dByte msg : %s\r\n", len, recv_buf);
sleep(5);
write(sockfd, recv_buf, len);
}
}
}
c_exit:
close(sockfd);
FD_CLR(sockfd, &readfds);
return 0;
}