首先,使用cat /proc/sys/fs/file-max
命令查看一个进程可以打开文件描述符的最大数目,如下图:
然后,使用sudo vi /etc/security/limits.conf
命令查看最大打开文件描述符的限制,并在该文件中写入以下配置:(如下图最后两行,即soft软限制和hard硬限制)
#include
int epoll_create(int size)
size: 告诉内核监听的数目
创建一个epoll句柄,参数size用来告诉内核监听的文件描述符个数,跟内存大小有关
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd: 为epoll_creat的句柄
op: 表示动作,用3个宏来表示:
event: 告诉内核需要监听的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
Epoll_events操作模式:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT:表示对应的文件描述符可以写
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(表示有带外数据到来)
EPOLLERR:表示对应的文件描述符发生错误
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(ET模式是相对于水平触发(LT)来说的)
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
#include
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
events: 从内核得到事件的集合,
maxevents: 告知内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
timeout: 超时时间,其中:
-1:阻塞
0:立即返回,非阻塞
>0:指定微秒
返回值: 成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1
wrap.h
#ifndef WRAP_H
#define WRAP_H
size_t mystrlen(const char *s);
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
void Bind(int fd, const struct sockaddr *sa, socklen_t salen);
void Connect(int fd, const struct sockaddr *sa, socklen_t salen);
void Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
void Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
static ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif
wrap.c
#include
#include
#include
#include
#include
#include "wrap.h"
size_t mystrlen(const char *s)
{
size_t len = 0;
if (s == NULL)
{
return 0;
}
while (*s != '\n')
{
s++;
len++;
}
return len;
}
void perr_exit(const char *s)
{
perror(s);
exit(1);
}
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
int n;
again:
if ( (n = accept(fd, sa, salenptr)) < 0) {
if ((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
if (bind(fd, sa, salen) < 0)
perr_exit("bind error");
}
void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
if (connect(fd, sa, salen) < 0)
perr_exit("connect error");
}
void Listen(int fd, int backlog)
{
if (listen(fd, backlog) < 0)
perr_exit("listen error");
}
int Socket(int family, int type, int protocol)
{
int n;
if ( (n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ( (n = read(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ( (n = write(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
void Close(int fd)
{
if (close(fd) == -1)
perr_exit("close error");
}
ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
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 n - nleft;
}
ssize_t Writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = Write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
static ssize_t my_read(int fd, char *ptr)
{
static int read_cnt;
static char *read_ptr;
static char read_buf[100];
if (read_cnt <= 0) {
again:
if ( (read_cnt = Read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return -1;
} else if (read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = my_read(fd, &c)) == 1) {
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
*ptr = 0;
return n - 1;
} else
return -1;
}
*ptr = 0;
return n;
}
server.h
#include
#include
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
#define OPEN_MAX 1024
int main(int argc, char *argv[])
{
int i, j, maxi, listenfd, connfd, sockfd;//listenfd监听描述符,connfd连接描述符,sockfd通信描述符
int nready, efd, res;
ssize_t n; //用来表示可被执读写操作的数据块大小
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen; //客户端地址长度
int client[OPEN_MAX]; //用于查看客户端连接状态的数组
struct sockaddr_in servaddr, cliaddr; //定义sockaddr_in结构体类型变量
struct epoll_event tep, ep[OPEN_MAX]; //定义epoll_event结构体类型变量
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建监听套接字描述符
bzero(&servaddr, sizeof (servaddr)); //清零整个servaddr结构体
servaddr.sin_family = AF_INET; //设置地址类型
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //设置网络地址
servaddr.sin_port = htons(SERV_PORT); //设置端口号
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof (servaddr)); //绑定listenfd和servaddr
Listen(listenfd, 20); //设置监听成功则返回一个新的socket文件描述符,用于和客户端通信,失败返回-1
for(i = 0; i< OPEN_MAX; i++)
{
client[i] = -1; //初始化client[]下标为-1
}
maxi = -1;
efd = epoll_create(OPEN_MAX); //创建一个epoll句柄,efd指向红黑树根节点,参数size用来告诉内核监听的文件描述符个数
if(efd == -1) //出错处理
{
perr_exit("epoll_create failed.");
}
tep.events = EPOLLIN; //指定监听读事件,默认为水平触发LT
tep.data.fd = listenfd; //一般的epoll在此存放fd
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); //将listenfd和对应的epoll_event结构体设置到树上,即控制某个epoll监控的文件描述符上的事件:注册、修改、删除
if(res == -1) //出错处理
{
perr_exit("epoll_ctl failed.");
}
for( ; ; )
{
nready = epoll_wait(efd, ep, OPEN_MAX, -1); //为server阻塞(默认)监听事件,等待所监听的文件描述符上有事件的发生。ep中存放满足条件后的所有事件结构体
if(nready == -1) //出错处理
{
perr_exit("epoll_wait failed.");
}
for(i = 0; i < nready; i++) //遍历就绪的文件描述符
{
if(!(ep[i].events & EPOLLIN)) //该事件的文件描述符不可读,则跳过
{
continue;
}
if(ep[i].data.fd == listenfd) //ep数据域存放的描述符即红黑树上节点的值和监听描述符相同,说明有新连接到来
{
clilen = sizeof (servaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); //接收客户端连接
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof (str)), ntohs(cliaddr.sin_port)); //打印客户端IP及端口号
for(j = 0; j < OPEN_MAX; j++) //将接入的客户端套接字描述符加入client数组
{
if(client[j] < 0)
{
client[j] = connfd; //保存描述符
break;
}
}
if(j == OPEN_MAX) //出错处理
{
perr_exit("too many clients.");
}
if(j > maxi) //更新maxi的值(maxi代表最后一个连接的客户端的套接字)
{
maxi = j; //client[]数组中的最大索引
}
tep.events = EPOLLIN; //指定监听读事件,默认为水平触发
tep.data.fd = connfd; //存放连接描述符
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); //注册新的connfd到efd,即将新连接的客户端套接字加入红黑树并继续监听
if(res == -1) //出错处理,新客户端加入失败
{
perr_exit("epoll_ctl failed.");
}
}
else //红黑树上节点的值不为listenfd,说明有客户端想要读/写数据
{
sockfd = ep[i].data.fd; //从红黑树中读取通信套接字描述符给sockfd
n = Read(sockfd, buf, MAXLINE); //读取缓冲区数据到buf
if(n == 0) //客户端关闭或无发送数据
{
for(j = 0; j <= maxi; j++)
{
if(client[i] == sockfd) //如果关闭的客户端通信套接字还在client数组中,则将其置为-1
{
client[j] = -1;
break;
}
}
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); //将sockfd从efd中删除,也就是将关闭连接的客户端通信套接字从红黑树中删除
if(res == -1) //出错处理,下树失败
{
perr_exit("epoll_ctl failed.");
}
Close(sockfd);
printf("client[%d] closed connection.\n", j);
}
else //处理在缓冲区读到的数据
{
for(j = 0; j < n; j++)
{
buf[j] = toupper(buf[j]);
}
Writen(sockfd, buf, n); //将字节数为n的数据字符串buf写入sockfd中
}
}
}
}
Close(listenfd);
Close(efd);
return 0;
}
client.c
#include
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
int main()
{
struct sockaddr_in servaddr; //定义sockaddr_in结构体类型变量
char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0); //打开一个网络通讯端口,成功返回一个文件描述符
//初始化sockaddr_in结构体
bzero(&servaddr, sizeof (servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof (servaddr)); //与服务器建立连接
while(fgets(buf, MAXLINE, stdin) != NULL)
{
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if(n == 0)
{
printf("the other side has been closed.\n");
}
else
{
Write(STDIN_FILENO, buf, n);
}
}
Close(sockfd);
return 0;
}
(此epoll模型的功能为将输入的小写字符串,转换为大写字符串)