read函数只是一个通用的读文件设备的接口。是否阻塞需要由设备的属性和设定所决定。一般来说,读字符终端、网络的socket描述字,管道文件等,这些文件的缺省read都是阻塞的方式。如果是读磁盘上的文件,一般不会是阻塞方式的。但使用锁和fcntl设置取消文件O_NOBLOCK状态,也会产生阻塞的read效果。
一、服务器端实现
int onRead(int fd, void *buffer, unsigned int length)
{
char *ptr = (char *)buffer;
unsigned int nLeft = length;
int nRead;
while (nLeft > 0) {
if((nRead = read(fd, ptr, nLeft)) < 0) {
if(errno == EINTR) {
nRead = 0;
} else {
return -1;
}
} else if (nRead == 0) {
break;
}
nLeft -= nRead;
ptr += nRead;
}
return length - nLeft;
}
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
using namespace std;
#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000
void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main(int argc, char* argv[])
{
int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
ssize_t n;
char line[MAXLINE];
socklen_t clilen;
if ( 2 == argc )
{
if( (portnumber = atoi(argv[1])) < 0 )
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
return 1;
}
}
else
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
return 1;
}
//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
struct epoll_event ev,events[20];
//生成用于处理accept的epoll专用的文件描述符
epfd=epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//把socket设置为非阻塞方式
//setnonblocking(listenfd);
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//ev.events=EPOLLIN;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr="127.0.0.1";
inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);
serveraddr.sin_port=htons(portnumber);
bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
for ( ; ; ) {
//等待epoll事件的发生
nfds=epoll_wait(epfd,events,20,500);
//处理所发生的所有事件
for(i=0;i<nfds;++i)
{
if(events[i].data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
if(connfd<0){
perror("connfd<0");
exit(1);
}
//setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
cout << "accapt a connection from " << str << endl;
//设置用于读操作的文件描述符
ev.data.fd=connfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//ev.events=EPOLLIN;
//注册ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if(events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
{
cout << "EPOLLIN" << endl;
if ( (sockfd = events[i].data.fd) < 0)
continue;
if ( (n = read(sockfd, line, MAXLINE)) < 0) {
if (errno == ECONNRESET) {
close(sockfd);
events[i].data.fd = -1;
} else
std::cout<<"readline error"<<std::endl;
} else if (n == 0) {
close(sockfd);
events[i].data.fd = -1;
}
line[n] = '/0';
cout << "read " << line << endl;
//设置用于写操作的文件描述符
ev.data.fd=sockfd;
//设置用于注测的写操作事件
ev.events=EPOLLOUT|EPOLLET;
//修改sockfd上要处理的事件为EPOLLOUT
//epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
else if(events[i].events&EPOLLOUT) // 如果有数据发送
{
sockfd = events[i].data.fd;
write(sockfd, line, n);
//设置用于读操作的文件描述符
ev.data.fd=sockfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//修改sockfd上要处理的事件为EPOLIN
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
}
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>
#define RELEASE
#define DEBUG
#define MYPORT 80 // the port users will be connecting to
#define BACKLOG 5 // how many pending connections queue will hold
#ifndef INFTIM
#define INFTIM -1
#endif
#define BUF_SIZE 500000
#define MAXLINE 100
#define MAXCLIENT 5
int fd_access[BACKLOG]; // accepted connection fd
int conn_amount; // current connection amount
int recv_number=0;
void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main(void)
{
int sock_fd, new_fd,len,listen_fd; // listen on sock_fd, new connection on new_fd
struct sockaddr_in server_addr; // server address information
struct sockaddr_in client_addr; // connector's address information
socklen_t sin_size;
char buf[BUF_SIZE];
int result;
int i,j;
int maxsock=0;
int conn_fd,epfd,n;
#ifdef DEBUG
printf("hello debug\n");
#endif
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
setnonblocking(listen_fd);
server_addr.sin_family = AF_INET; // host byte order
server_addr.sin_port = htons(MYPORT); // short, network byte order
server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
perror("bind");
exit(1);
}
if (listen(listen_fd, BACKLOG) == -1)
{
perror("listen");
exit(1);
}
#ifdef DEBUG
printf("listen port %d listen_fd %d\n", MYPORT,listen_fd);
#endif
//epoll
struct epoll_event ev,events[20];
epfd=epoll_create(1024);
printf("%d---%d\n",epfd,epoll_create(600));
ev.data.fd=listen_fd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&ev);
ev.data.fd=12;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&ev);
sin_size = sizeof(client_addr);
#ifdef DEBUG
printf("EPOLLIN---%d",EPOLLIN);
#endif
while (1)
{
result=epoll_wait(epfd,events,20,-1);
#ifdef DEBUG
printf("the total number is %d\n",recv_number);
printf("result is %d \n",result);
#endif
for(i = 0; i <result; i++)
{
#ifdef DEBUG
printf("--------------print events----------------\n");
for(j=0;j<result;j++)
{
printf("j events %d\n",events[j].events);
printf("j data fd %d\n",events[j].data.fd);
}
printf("--------------print events over-----------\n");
#endif
if(events[i].data.fd==listen_fd)
{
new_fd=accept(listen_fd,(struct sockaddr*)&client_addr,&sin_size);
while(new_fd>0)
{
#ifdef DEBUG
printf("new_fd -------%d\n",new_fd);
setnonblocking(new_fd);
#endif
ev.data.fd=new_fd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&ev);
new_fd=accept(listen_fd,(struct sockaddr*)&client_addr,&sin_size);
}
}
else if(events[i].events&EPOLLIN)
{
#ifdef DEBUG
printf("mark %d\n",events[i].data.fd);
#endif
if ( (sock_fd = events[i].data.fd) < 0)
{
printf("just do it \n");
continue;
}
#ifdef DEBUG
printf("sock_fd ------%d\n",sock_fd);
#endif
n = recv(sock_fd, buf,5,0);
recv_number+=n;
sleep(2);
#ifdef DEBUG
printf("recv n ----%d\n",n);
#endif
if(n< 0)
{
printf("mark1");
if (errno == ECONNRESET)
{
close(sock_fd);
events[i].data.fd = -1;
}
else
printf("sorry \n");
}
else if (n == 0)
{
close(sock_fd);
events[i].data.fd = -1;
}
buf[n] = '\0';
printf("read data %s\n",buf);
ev.data.fd=sock_fd;
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, sock_fd, &ev);
}
else if(events[i].events&EPOLLOUT)
{
sock_fd = events[i].data.fd;
write(sock_fd, buf, n);
ev.data.fd=sock_fd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sock_fd,&ev);
}
}
}
exit(0);
}
网络程序为了支持并发,可以采用select,多线程等技术.但是对于select,readhat linux系统只支持最大1024个描述符.因此要想同时并发超过1024,就无法使用select模式.而使用多线程,并发数达到1000时将严重影响系统的性能.而使用epoll可以避免以上的缺陷.
下面是一个使用epoll实现客户端UDP并发.是我为写压力测试程序而写的.
发送使用一个独立的线程,接收使用epoll调用.在程序开始要先设置系统能打开的最大描述符限制.即setrlimt调用.在linux readhat enterprise 4环境下测试通过。其它环境我没测过。
g++ -o udp_epoll_c udp_epoll_c.cpp -lpthread
/***************************************************************************
file: udp_epoll_c.cpp
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <errno.h>
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string>
#include <sys/resource.h>
#include <pthread.h>
#include <vector>
using namespace std;
int Read(int fd,void *buffer,unsigned int length)
{
unsigned int nleft;
int nread;
char *ptr;
ptr = (char *)buffer;
nleft = length;
while(nleft > 0)
{
if((nread = read(fd, ptr, nleft))< 0)
{
if(errno == EINTR)
nread = 0;
else
return -1;
}
else if(nread == 0)
{
break;
}
nleft -= nread;
ptr += nread;
}
return length - nleft;
}
int Write(int fd,const void *buffer,unsigned int length)
{
unsigned int nleft;
int nwritten;
const char *ptr;
ptr = (const char *)buffer;
nleft = length;
while(nleft > 0)
{
if((nwritten = write(fd, ptr, nleft))<=0)
{
if(errno == EINTR)
nwritten=0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return length;
}
int CreateThread(void *(*start_routine)(void *), void *arg = NULL, pthread_t *thread = NULL, pthread_attr_t *pAttr = NULL)
{
pthread_attr_t thr_attr;
if(pAttr == NULL)
{
pAttr = &thr_attr;
pthread_attr_init(pAttr);
pthread_attr_setstacksize(pAttr, 1024 * 1024); // 1 M的堆栈
pthread_attr_setdetachstate(pAttr, PTHREAD_CREATE_DETACHED);
}
pthread_t tid;
if(thread == NULL)
{
thread = &tid;
}
int r = pthread_create(thread, pAttr, start_routine, arg);
pthread_attr_destroy(pAttr);
return r;
}
static int SetRLimit()
{
struct rlimit rlim;
rlim.rlim_cur = 20480;
rlim.rlim_max = 20480;
if (setrlimit(RLIMIT_NOFILE, &rlim) != 0)
{
perror("setrlimit");
}
else
{
printf("setrlimit ok/n");
}
return 0;
}
int setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
return -1;
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
return -1;
}
return 0;
}
int ConnectToUdperver(const char *host, unsigned short port)
{
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock < 0)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(host);
if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("bind");
close(sock);
return -1;
}
return sock;
}
void *SendThread(void *arg)
{
vector<int> sockets;
sockets = *((vector<int> *)arg);
int n = 0;
char data[1024];
int i = 0;
while(1)
{
for(vector<int>::iterator itr = sockets.begin(), last = sockets.end(); itr != last; ++itr)
{
sprintf(data, "test data %d/n", i++);
n = Write(*itr, "hello", 5);
printf("socket %d write to server[ret = %d]:%s",*itr, n, data);
}
sleep(1);
}
}
int main(int argc, char **argv)
{
SetRLimit();
printf("FD_SETSIZE= %d/n", FD_SETSIZE);
if (argc != 3)
{
printf("usage: %s <IPaddress> <PORT>/n", argv[0]);
return 1;
}
int epfd = epoll_create(20480);
if(epfd < 0)
{
perror("epoll_create");
return 1;
}
struct epoll_event event;
struct epoll_event ev[20480];
vector<int> sockets;
for(int i = 0; i < 3000; i++)
{
int sockfd = ConnectToUdperver(argv[1], (unsigned short)(atoi(argv[2])));
if(sockfd < 0)
{
printf("Cannot connect udp server %s %s/n", argv[1], argv[2]);
return 1;
}
sockets.push_back(sockfd);
setnonblocking(sockfd);
event.data.fd = sockfd;
event.events = EPOLLIN|EPOLLET;
if(0 != epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event))
{
perror("epoll_ctl");
}
}
if(0 != CreateThread(SendThread, (void *)&sockets))
{
perror("CreateThread");
return 2;
}
int nfds = 0;
while(1)
{
nfds=epoll_wait(epfd,ev,20480,500);
if(nfds < 0)
{
perror("epoll_wait");
break;
}
else if(nfds == 0)
{
printf("epoll_wait timeout!/n");
continue;
}
for(int i = 0; i < nfds; i++)
{
if(ev[i].events & EPOLLIN)
{
printf("can read for %d now/n", ev[i].data.fd);
char data[1024] = {0};
int n = read(ev[i].data.fd, data, sizeof(data));
printf("Received %d bytes from server!/n", n);
}
}
}
for(vector<int>::iterator itr = sockets.begin(), last = sockets.end(); itr != last; itr++)
{
close(*itr);
}
close(epfd);
return 0;
}