分类: 嵌入式
2014-04-18 17:36:31
与多线程和多进程相比,I/O多路复用的最大优势是系统开销小,系统不需要建立新的进程或者线程,也不必维护这些线程和进程。
主要应用:
(1)客户程序需要同时处理交互式的输入和服务器之间的网络连接
(2)客户端需要对多个网络连接作出反应
(3)TCP服务器需要同时处理多个处于监听状态和多个连接状态的套接字
(4)服务器需要处理多个网络协议的套接字
(5)服务器需要同时处理不同的网络服务和协议
3. IO多路复用模式
4. select()函数
#include
int select(int nfds, fd_set *readfds, fd_set *wtitefds, fd_set *errnofds,
struct timeval *timeout)
注意:描述符不受限与套接字,任何描述符都行
nfds:select()函数监视的描述符数的最大值,一般取监视的描述符数的最大值+1,
其上限设置在sys/types.h中有定义
#define FD_SETSIZE 256
readfds:select()函数监视的可读描述符集合
wtitefds:select()函数监视的可写描述符集合
errnofds:select()函数监视的异常描述符集合
timeout:select()函数监视超时结束时间,取NULL表示永久等待
返回值:返回总的位数这些位对应已准备好的描述符,否则返回-1
相关宏操作:
FD_ZERO(fd_set *fdset):清空fdset与所有描述符的关系
FD_SET(int fd, d_set * fdset):建立描述符fd与fdset得关系
FD_CLR(int fd, d_set * fdset):撤销描述符fd与fdset得关系
FD_ISSET(int fd, d_set * fdset):检查与fdset联系的描述符fd是否可以读写,返回非零表示可以读写
5. select()函数实现IO多路复用的步骤
(1)清空描述符集合
(2)建立需要监视的描述符与描述符集合的关系
(3)调用select函数
(4)检查监视的描述符判断是否已经准备好
(5)对已经准备好的描述符进程IO操作
同步阻塞IO在等待数据就绪上花去太多时间,而传统的同步非阻塞IO虽然不会阻塞进程,但是结合轮询来判断数据是否就绪仍然会耗费大量的CPU时间。
多路IO复用提供了对大量文件描述符进行就绪检查的高性能方案。
select
select诞生于4.2BSD,在几乎所有平台上都支持,其良好的跨平台支持是它的主要的也是为数不多的优点之一。
select的缺点(1)单个进程能够监视的文件描述符的数量存在最大限制(2)select需要复制大量的句柄数据结构,产生巨大的开销 (3)select返回的是含有整个句柄的列表,应用程序需要遍历整个列表才能发现哪些句柄发生了事件(4)select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程。相对应方式的是边缘触发。
poll
poll 诞生于UNIX System V Release 3,那时AT&T已经停止了UNIX的源代码授权,所以显然也不会直接使用BSD的select,所以AT&T自己实现了一个和select没有多大差别的poll。
poll和select是名字不同的孪生兄弟,除了没有监视文件数量的限制,select后面3条缺点同样适用于poll。
面对select和poll的缺陷,不同的OS做出了不同的解决方案,可谓百花齐放。不过他们至少完成了下面两点超越,一是内核长期维护一个事件关注列表,我们只需要修改这个列表,而不需要将句柄数据结构复制到内核中;二是直接返回事件列表,而不是所有句柄列表。
/dev/poll
Sun在Solaris中提出了新的实现方案,它使用了虚拟的/dev/poll设备,开发者可以将要监视的文件描述符加入这个设备,然后通过ioctl()来等待事件通知。
/dev/epoll
名为/dev/epoll的设备以补丁的方式出现在Linux2.4中,它提供了类似/dev/poll的功能,并且在一定程度上使用mmap提高了性能。
kqueue
FreeBSD实现了kqueue,可以支持水平触发和边缘触发,性能和下面要提到的epoll非常接近。
epoll
epoll诞生于Linux 2.6内核,被公认为是Linux2.6下性能最好的多路IO复用方法。
int epoll_create(int size)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
|
epoll支持水平触发和边缘触发,理论上来说边缘触发性能更高,但是使用更加复杂,因为任何意外的丢失事件都会造成请求处理错误。Nginx就使用了epoll的边缘触发模型。
这里提一下水平触发和边缘触发就绪通知的区别,这两个词来源于计算机硬件设计。它们的区别是只要句柄满足某种状态,水平触发就会发出通知;而只有当句柄状态改变时,边缘触发才会发出通知。例如一个socket经过长时间等待后接收到一段100k的数据,两种触发方式都会向程序发出就绪通知。假设程序从这个socket中读取了50k数据,并再次调用监听函数,水平触发依然会发出就绪通知,而边缘触发会因为socket“有数据可读”这个状态没有发生变化而不发出通知且陷入长时间的等待。
因此在使用边缘触发的 api 时,要注意每次都要读到 socket返回 EWOULDBLOCK为止。
6.单线程并发服务器设计实例
功能:
服务器等候客户连接请求,一旦连接成功显示客户地址,接收该客户的名字并显示,然后接收来自用户的信息,每收到一个字符串则显示,并将字符串反转,再将反转的字符串发回客户端。
客户端首先与服务器相连,接着发送客户端名字,然后发送客户信息,接收到服务器信息并显示,之后等待用户输入Crtl+D,就关闭连接并退出。
//server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000
typedef struct CLIENT{
int fd;
char* name;
struct sockaddr_in addr;
char* data;
};
void process_cli(CLIENT *client, char* recvbuf, int len);
void savedata(char* recvbuf, int len, char* data);
main()
{
int i, maxi, maxfd,sockfd;
int nready;
ssize_t n;
fd_set rset, allset;
int listenfd, connectfd;
struct sockaddr_in server;
CLIENT client[FD_SETSIZE];
char recvbuf[MAXDATASIZE];
socklen_t sin_size;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Creating socket failed.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr = htonl (INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
perror("Bind error.");
exit(1);
}
if(listen(listenfd,BACKLOG) == -1){
perror("listen() error\n");
exit(1);
}
sin_size=sizeof(struct sockaddr_in);
maxfd = listenfd;
maxi = -1;
for (i = 0; i < FD_SETSIZE; i++) {
client[i].fd = -1;
}
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
while(1)
{
struct sockaddr_in addr;
rset = allset;
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (FD_ISSET(listenfd, &rset)) {
if ((connectfd = accept(listenfd,(struct sockaddr *)&addr,&sin_size))==-1) {
perror("accept() error\n");
continue;
}
for (i = 0; i < FD_SETSIZE; i++)
if (client[i].fd < 0) {
client[i].fd = connectfd;
client[i].name = new char[MAXDATASIZE];
client[i].addr = addr;
client[i].data = new char[MAXDATASIZE];
client[i].name[0] = '\0';
client[i].data[0] = '\0';
printf("You got a connection from %s. ",inet_ntoa(client[i].addr.sin_addr) );
break;
}
if (i == FD_SETSIZE) printf("too many clients\n");
FD_SET(connectfd, &allset);
if (connectfd > maxfd) maxfd = connectfd;
if (i > maxi) maxi = i;
if (--nready <= 0) continue;
}
for (i = 0; i <= maxi; i++) {
if ( (sockfd = client[i].fd) < 0) continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = recv(sockfd, recvbuf, MAXDATASIZE,0)) == 0) {
close(sockfd);
printf("Client( %s ) closed connection. User's data: %s\n",client[i].name,client[i].data);
FD_CLR(sockfd, &allset);
client[i].fd = -1;
delete client[i].name;
delete client[i].data;
} else
process_cli(&client[i], recvbuf, n);
if (--nready <= 0) break;
}
}
}
close(listenfd);
}
void process_cli(CLIENT *client, char* recvbuf, int len)
{
char sendbuf[MAXDATASIZE];
recvbuf[len-1] = '\0';
if (strlen(client->name) == 0) {
memcpy(client->name,recvbuf, len);
printf("Client's name is %s.\n",client->name);
return;
}
printf("Received client( %s ) message: %s\n",client->name, recvbuf);
savedata(recvbuf,len, client->data);
for (int i1 = 0; i1 < len - 1; i1++) {
sendbuf[i1] = recvbuf[len - i1 -2];
}
sendbuf[len - 1] = '\0';
send(client->fd,sendbuf,strlen(sendbuf),0);
}
void savedata(char* recvbuf, int len, char* data)
{
int start = strlen(data);
for (int i = 0; i < len; i++) {
data[start + i] = recvbuf[i];
}
}
// client.c
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define MAXDATASIZE 100
void process(FILE *fp, int sockfd);
char* getMessage(char* sendline,int len, FILE* fp);
int main(int argc, char *argv[])
{
int fd;
struct hostent *he;
struct sockaddr_in server;
if (argc !=2) {
printf("Usage: %s
exit(1);
}
if ((he=gethostbyname(argv[1]))==NULL){
printf("gethostbyname() error\n");
exit(1);
}
if ((fd=socket(AF_INET, SOCK_STREAM, 0))==-1){
printf("socket() error\n");
exit(1);
}
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = *((struct in_addr *)he->h_addr);
if(connect(fd, (struct sockaddr *)&server,sizeof(struct sockaddr))==-1){
printf("connect() error\n");
exit(1);
}
process(stdin,fd);
close(fd);
}
void process(FILE *fp, int sockfd)
{
char sendline[MAXDATASIZE], recvline[MAXDATASIZE];
int numbytes;
printf("Connected to server. \n");
printf("Input name : ");
if ( fgets(sendline, MAXDATASIZE, fp) == NULL) {
printf("\nExit.\n");
return;
}
send(sockfd, sendline, strlen(sendline),0);
while (getMessage(sendline, MAXDATASIZE, fp) != NULL) {
send(sockfd, sendline, strlen(sendline),0);
if ((numbytes = recv(sockfd, recvline, MAXDATASIZE,0)) == 0) {
printf("Server terminated.\n");
return;
}
recvline[numbytes]='\0';
printf("Server Message: %s\n",recvline);
}
printf("\nExit.\n");
}
char* getMessage(char* sendline,int len, FILE* fp)
{
printf("Input string to server:");
return(fgets(sendline, MAXDATASIZE, fp));
}