1,并发服务器,通过多路IO复用,能使得一个进程同时处理多路IO,提升服务器吞吐量。 在Linux支持epoll模型之前,都使用select/poll模型来实现IO多路复用。
Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、 accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发 生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。
可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返 回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高) 方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况–读写或是异常。
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
select 返回值
负值:select错误
正值:某些文件可读写或出错
0:等待超时,没有可读写或错误的文件
服务端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#define PORT 12345
typedef struct client_st {
int fd;
struct sockaddr_in caddr;
}Client;
char *s = "10.9.0.152";
int main(int argc, char *argv[])
{
int listenfd,maxfd;
int newfd;
int ret,i,j,maxi,n;
Client clients[1024]; //定义select支持的最多的套接字
struct sockaddr_in seraddr;
int ser_len = sizeof(struct sockaddr_in);
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd < 0) {
perror("listenfd socket error");
exit(-1);
}
printf("listenfd = %d\n",listenfd);
memset(&seraddr,0,sizeof(struct sockaddr_in));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(PORT);
seraddr.sin_addr.s_addr = inet_addr(s);
if(bind(listenfd,(struct sockaddr *)&seraddr,ser_len) == -1) {
perror("bind error");
exit(-1);
}
if(listen(listenfd, 5) == -1) { //监听队列最多容纳5个
perror("listen error");
exit(-1);
}
char buff[1024]; //数据接收缓冲区
fd_set allset; //套接字操作符
fd_set fdsr;
FD_ZERO(&allset);
FD_SET(listenfd,&allset); //将监听套接字加入轮询
maxfd = listenfd;
maxi = -1;
for(i = 0; i < 1024; i++) {
clients[i].fd = -1;
}
while(1) {
struct sockaddr_in cusaddr;
fdsr = allset;
ret = select(maxfd + 1, &fdsr,NULL,NULL,0); //无限期阻塞,并测试文件描述符变动
if(ret < 1) {
perror("select error");
}
printf("Select() break and the return num is %d. \n", ret);
if(FD_ISSET(listenfd,&fdsr)) { //有新的连接到来
printf("Accept a connection.\n");
int cus_len = sizeof(struct sockaddr_in);
newfd = accept(listenfd,(struct sockaddr *)&cusaddr,&cus_len);
printf("newfd %d\n",newfd);
if(newfd < 0) {
perror("accept error");
continue;
}
for(j = 0; j < 256; j++) { //将新客户端加入到数组
if(clients[j].fd < 0) {
clients[j].fd = newfd;
clients[j].caddr = cusaddr;
break;
}
}
FD_SET(newfd,&allset); //将client的套接字加入轮询
if(newfd > maxfd)
maxfd = newfd;
if(j > maxi)
maxi = j;
if (--ret <= 0)
continue; //如果没有新客户端连接,继续循环
}
for(i = 0; i <(maxi + 1);i++)
{
if(clients[i].fd < 0)
continue;
if(FD_ISSET(clients[i].fd,&fdsr)) {
printf("Receive from connect fd[%d].\n", i);
memset(buff,0,sizeof(buff));
if ((n = recv(clients[i].fd, buff, sizeof(buff),0)) == 0)
{ //从客户端socket读数据,等于0表示网络中断
close(clients[i].fd); //关闭socket连接
FD_CLR(clients[i].fd, &fdsr); //从监听集合中删除此socket连接
clients[i].fd = -1; //数组元素设初始值,表示没客户端连接
}
else
printf("recv buff is %s\n",buff); //打印
if (--ret <= 0)
break; //如果没有新客户端有数据,跳出for循环回到while循环
}
}
}
close(newfd);
close(listenfd);
return 0;
}
客户端:可以使用Windows 网络助手
结果: