测试通过虚拟机:Ubuntu 16.04
温馨提示(必读):
1.下方是对I/O多路复用步步进阶之旅,包含的系统调用用select、poll、epoll及其它们的优化版本的总结
2.下方总结仅列出了服务端的演示代码,如需进行测试,可以在linux下开启多个终端,一个运行服务端,其余的先用ifconfig命令获取ip地址,然后用命令:(nc “获取的ip地址” +你设置的端口号 ) 来进行连接测试
3.测试功能:客户端输入小写字母,经过服务端处理变成大写字母,显示在客户端中。
4.下方总结中头文件包含的#include “wrap.h” 是自定义的错误处理包裹函数,测试时需要加上,头文件和实现代码下方附上:
wrap.h代码演示:
#ifndef __WRAP_H_
#define __WRAP_H_
#include <sys/socket.h>
#include <stdlib.h>
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int 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);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif
wrap.c代码演示:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
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;
}
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
if ((n = bind(fd, sa, salen)) < 0)
perr_exit("bind error");
return n;
}
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
n = connect(fd, sa, salen);
if (n < 0) {
perr_exit("connect error");
}
return n;
}
int Listen(int fd, int backlog)
{
int n;
if ((n = listen(fd, backlog)) < 0)
perr_exit("listen error");
return n;
}
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;
}
int Close(int fd)
{
int n;
if ((n = close(fd)) == -1)
perr_exit("close error");
return n;
}
/*参三: 应该读取的字节数*/ //socket 4096 readn(cfd, buf, 4096) nleft = 4096-1500
ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft; //usigned int 剩余未读取的字节数
ssize_t nread; //int 实际读到的字节数
char *ptr;
ptr = vptr;
nleft = n; //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; //nleft = 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) { //"hello\n"
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;
}
/*readline --- fgets*/
//传出参数 vptr
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[] = hello\n
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
*ptr = 0;
return n-1;
} else
return -1;
}
*ptr = 0;
return n;
}
多进程TCP通信:
server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include <ctype.h>
#include "wrap.h"
void do_wait(int signum)
{
while(waitpid(0, NULL, WNOHANG) > 0);
return;
}
int main(int argc, char* argv[])
{
//- 创建连接套接字 socket
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
//- 创建sockaddr_in结构体 进行bind
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(6666);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//- 设置监听
Listen(lfd, 128);
//- 设置循环
struct sockaddr_in clie_addr;
socklen_t clie_len = sizeof(clie_addr);
pid_t pid;
while(1)
{
//- 接收客户端建立连接 accept
int cfd = Accept(lfd,(struct sockaddr*)&clie_addr, &clie_len);
//- 创建子进程
pid = fork();
//判断进程操作
if(pid == 0)
{
//- 子进程
Close(lfd);
char buf[1024];
//- 业务处理 fork read write
while(1)
{
int res = Read(cfd, buf, sizeof(buf));
// - 如果读取用户关闭信息 结束业务处理
if(res == 0)
{
printf("客户端断开连接\n");
Close(cfd);
return 0;
}
//大小写转换
int i;
for(i = 0; i < res; i++)
{
buf[i] = toupper(buf[i]);
}
Write(cfd, buf, res);
}
}
else
{
//- 主进程
// - 回收子进程
Close(cfd);
signal(SIGCHLD, do_wait);
}
}
Close(lfd);
return 0;
}
多线程TCP通信:
server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
#include "wrap.h"
struct s_info
{
struct sockaddr_in clie_addr;
int cfd;
};
void* do_work(void* argv)
{
//参数强制类型转换
struct s_info* si = (struct s_info*)argv;
char ip[16];
inet_ntop(AF_INET,&(si->clie_addr).sin_addr.s_addr,ip,sizeof(ip));
int port = ntohs(si->clie_addr.sin_port);
printf("IP地址: %s 端口:%hu\n", ip,port);
char buf[100];
while(1)
{
int res = Read(si->cfd, buf, sizeof(buf));
//判断是否客户端退出
if(res == 0)
{
printf("客户端断开连接\n");
Close(si->cfd);
break;
}
int i;
for(i = 0; i< res; i++)
{
buf[i] = toupper(buf[i]);
}
Write(si->cfd, buf, res);
}
return NULL;
}
int main(int argc, char* argv[])
{
//- 创建连接套接字 socket
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
//- 创建sockaddr_in结构体 进行bind
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(6666);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//- 设置监听
Listen(lfd, 128);
//- 设置循环
struct sockaddr_in clie_addr;
socklen_t clie_len = sizeof(clie_addr);
while(1)
{
//接受客户端 建立连接 accept
int cfd = Accept(lfd,(struct sockaddr*)&clie_addr, &clie_len);
//为结构体s_info 进行赋值 为函数调用传参
struct s_info si;
si.clie_addr = clie_addr;
si.cfd = cfd;
pthread_t tid;
pthread_create(&tid,NULL, do_work,(void*)&si);
//分离态
pthread_detach(tid);
}
Close(lfd);
return 0;
}
select(跨平台、限制1024)
1.创建套接字lfd、绑定、监听
2.创建一个文件描述符总表allset并初始化(FD_ZERO(&allset)),再来一个临时文件描述符表rset和内核交互
3.将监听的lfd加入到总表allset中
4.死循环:
- 用总表给临时表赋值,用以下一步传入select,内核将获取rset中文件描述符状态并将结果返回rest中
- 调用select来委托内核进行检测
- 判断是不是监听的FD_ISSET(lfd,&rset)
- 接受新连接accept请求,获取新连接描述符cfd
- 将cfd加入到总表allset中FD_SET(cfd,&allset)
- 更新最大文件描述符信息maxfd(select第一个参数处使用maxfd+1)
- 处理客户端业务
- 循环处理lfd+1文件描述符到maxfd文件描述符的业务
- 判断是不是监听的FD_ISSET(文件描述符,&rset)
- 读取数据
- 判断数据读取为空时,从总表中除去这个文件描述符并关闭这个文件描述符
server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(6666);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
listen(lfd,128);
fd_set rset,allset;
FD_ZERO(&allset);
FD_SET(lfd,&allset);
int maxfd = lfd;
int i;
while(1)
{
rset =allset;
int ready = select(maxfd+1,&rset,NULL,NULL,NULL);
if(ready < 0){
perror("select err");
exit(-1);
}
if(FD_ISSET(lfd,&rset)){
struct sockaddr_in clie_addr;
socklen_t clie_len = sizeof(clie_addr);
int cfd = accept(lfd,(struct sockaddr*)&clie_addr,&clie_len);
FD_SET(cfd,&allset);
if(cfd > maxfd){
maxfd = cfd;
}
if(--ready == 0){
continue;
}
}
char buf[1024];
for(i = lfd+1;i <= maxfd; i++){
if(FD_ISSET(i,&rset)){
int res = read(i,buf,sizeof(buf));
if(res == 0){
printf("断开!\n");
close(i);
FD_CLR(i,&allset);
}
int k;
for(k = 0;k < res;k++){
buf[k] = toupper(buf[k]);
}
write(i,buf,res);
write(1,buf,res);
}
}
}
close(lfd);
return 0;
}
select优化版
server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
#include "wrap.h"
int main(int argc, char* argv[])
{
int i;
//- 创建连接套接字 socket
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof(opt));
//- 创建sockaddr_in结构体 进行bind
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(6666);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//- 设置监听
Listen(lfd, 128);
//创建连接文件描述符 数组 同时记录数组有效数据的个数
int maxi = -1;
int client[FD_SETSIZE];
for(i = 0; i < FD_SETSIZE;i++)
{
client[i] = -1;
}
//初始化位图
fd_set allset;
fd_set rset;
FD_ZERO(&allset);
//将lfd添加到位图
FD_SET(lfd, &allset);
//- 设置循环
int maxfd = lfd;
struct sockaddr_in clie_addr;
socklen_t clie_len = sizeof(clie_addr);
while(1)
{
rset = allset;
//设置select
int ready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if(ready < 0)
{
perr_exit("select error");
}
if(FD_ISSET(lfd,&rset))
{
//客户端连接
int cfd = Accept(lfd, (struct sockaddr*)&clie_addr, &clie_len);
//将连接文件描述符放在数据组
for(i = 0; i<FD_SETSIZE;i++)
{
if(client[i] == -1)
{
client[i]=cfd;
break;
}
}
//更新最大值个数
if(i > maxi)
{
maxi=i;
}
FD_SET(cfd, &allset);
if(cfd > maxfd)
{
maxfd = cfd;
}
if(--ready == 0)
{
continue;
}
}
//业务处理
char buf[1024];
for( i = 0; i <= maxi; i++)
{
int fd = client[i];
if(fd == -1)
{
continue;
}
//轮询 整个需要操作的文件描述符 判断是否有业务处理
if(FD_ISSET(fd,&rset))
{
int res = Read(fd, buf, sizeof(buf));
if(res==0)
{
printf("客户端断开连接\n");
Close(fd);
FD_CLR(fd,&allset);
//将连接文件描述符重置为-1
client[i] = -1;
}
int j;
for(j = 0; j < res; j++)
{
buf[j] = toupper(buf[j]);
}
Write(fd, buf, res);
}
}
}
Close(lfd);
return 0;
}
poll(不可跨平台、无连接限制【链表】)
1.创建套接字lfd、绑定、监听
2.创建struct pollfd结构体(成员:fd和events)数组,用变量maxi来记录下标(初始化0):从1开始全部赋值为-1,0将lfd写入
3.死循环:
- poll(pollfdarr,pollfdarr中结构体元素总量,-1(阻塞等待))函数返回满足响应事件的文件描述符个数。
- 判断pollfdarr数组0元素内revents是否接收到POLLIN常量(数据可读)
- accept()返回一个与客户端连接的描述符cfd
- 循环遍历1开始位的pollfdarr,将连接的文件描述符cfd添加到集合中,放在第一个结构体元素内成员fd为-1的位置,并初始化events成员为POLLIN后break结束添加操作
- 更新maxi值 if(i>maxi) maxi =i;
- 判断是否要处理其他事件 if(–ready == 0) continue;
- 循环进行业务处理cfd
- 依次判断事件是否满足条件,revents是否接收到POLLIN
- 进行业务处理:读取数据(判断返回0时关闭连接文件描述符,并在集合内将其元素中fd成员置为-1),处理数据后写出到连接文件描述符
- 依次判断事件是否满足条件,revents是否接收到POLLIN
- 关闭监听文件描述符lfd,结束所有操作!
server.c代码演示:
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>
#include <unistd.h>
#include <poll.h>
int main(int argc,char *argv[]){
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(6666);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
listen(lfd,128);
struct pollfd fdarr[2048];
int maxi = 0;
int i;
for(i=0;i < 2048;i++){
fdarr[i].fd = -1;
}
fdarr[0].fd = lfd;
fdarr[0].events = POLLIN;
while(1){
int ready = poll(fdarr,maxi+1,-1);
if(fdarr[0].revents & POLLIN){
struct sockaddr_in clie_addr;
socklen_t clie_len = sizeof(clie_addr);
int cfd = accept(fdarr[0].fd,(struct sockaddr*)&clie_addr,&clie_len);
for(i = 1;i <2048;i++){
if(fdarr[i].fd == -1){
fdarr[i].fd = cfd;
fdarr[i].events = POLLIN;
break;
}
}
if(i > maxi){
maxi = i;
}
if(--ready == 0){
continue;
}
}
char buf[2048];
for(i = 1;i< 2048;i++){
if(fdarr[i].revents & POLLIN){
int res = read(fdarr[i].fd,buf,sizeof(buf));
if(res == 0){
close(fdarr[i].fd);
printf("关闭!\n");
fdarr[i].fd = -1;
}
int j;
for(j = 0;j<res;j++){
buf[j] = toupper(buf[j]);
}
write(fdarr[i].fd,buf,res);
write(1,buf,res);
}
}
}
close(lfd);
return 0;
}
epoll(红黑树)
1.创建套接字lfd、绑定、监听
2.创建epoll树根节点epoll_create,返回一个文件描述符epfd
3.创建存储发生变化的fd对应信息的数组 struct epoll_event xxx[3000]
4.创建一个struct epoll_event 变量ev,在其中初始化lfd信息 ev.events = EPOLLIN; ev.data.fd = lfd;
5.将监听的lfd挂到epoll树上 epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev)
6.死循环:
- 委托内核检测事件 epoll_wait(epfd,xxx,3000,-1),返回ret(多少个文件描述符发生变化)
- 根据ret遍历xxx数组
- 判断数组元素的联合体对象下的fd成员是否为lfd
- 是lfd的话就有新接受连接请求- accept不阻塞,返回一个连接cfd
- 更新ev内部成员信息,ev.events = EPOLLIN; ev.data.fd = cfd;
- 让cfd上树 epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
- else不是lfd就是处理客户端数据业务
- 读取数据为空可以执行下树操作 epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)
- 是lfd的话就有新接受连接请求- accept不阻塞,返回一个连接cfd
- 判断数组元素的联合体对象下的fd成员是否为lfd
server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/epoll.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(6666);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
listen(lfd,128);
int epfd = epoll_create(2048);
struct epoll_event ev;
struct epoll_event eparr[2048];
ev.events = EPOLLIN;
ev.data.fd =lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
int maxi = 0;
int i = 0;
struct sockaddr_in clie_addr;
socklen_t clie_len;
while(1){
int ready = epoll_wait(epfd,eparr,maxi+1,-1);
for(i = 0;i < ready; i++){
if(eparr[i].data.fd == lfd && eparr[i].events & EPOLLIN){
int cfd = accept(lfd,(struct sockaddr*)&clie_addr,&clie_len);
ev.events = EPOLLIN;
ev.data.fd = cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
maxi++;
}else{
char buf[2048];
int res = read(eparr[i].data.fd,buf,sizeof(buf));
if(res == 0){
printf("关闭连接!\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,eparr[i].data.fd,NULL);
maxi--;
}
int j;
for(j = 0;j < res;j++){
buf[j] = toupper(buf[j]);
}
write(eparr[i].data.fd,buf,res);
write(1,buf,res);
}
}
}
close(lfd);
close(epfd);
return 0;
}
epoll对文件描述符的操作有两种模式
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默
认模式,LT模式与ET模式的区别如下:
LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处
理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理
该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
温馨提示:
- 对于监听的sockfd,最好使用水平触发模式,边缘触发模式会导致高并发情况下,有的客户端会连接不上。如果非要使用边缘触发,网上有的方案是用while来循环accept()。
- 对于读写的connfd,水平触发模式下,阻塞和非阻塞效果都一样,不过为了防止特殊情况,还是建议设置非阻塞。
- 对于读写的connfd,边缘触发模式下,必须使用非阻塞IO,并要一次性全部读写完数据
block_epoll_server.c代码演示(LT模式):
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAXLINE 10
#define SERV_PORT 9000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int efd;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(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));
listen(listenfd, 20);
struct epoll_event event;
struct epoll_event resevent[10];
int res, len;
efd = epoll_create(10);
//event.events = EPOLLIN | EPOLLET; /* ET 边沿触发 */
event.events = EPOLLIN; /* 默认 LT 水平触发 */
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
while (1) {
res = epoll_wait(efd, resevent, 10, -1);
printf("res %d\n", res);
if (resevent[0].data.fd == connfd) {
len = read(connfd, buf, MAXLINE/2); //readn(500)
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
noblock_epoll_server.c代码演示(ET模式):
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int efd, flag;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(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));
listen(listenfd, 20);
///
struct epoll_event event;
struct epoll_event resevent[10];
int res, len;
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; /* ET 边沿触发,默认是水平触发 */
//event.events = EPOLLIN;
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
flag = fcntl(connfd, F_GETFL); /* 修改connfd为非阻塞读 */
flag |= O_NONBLOCK;
fcntl(connfd, F_SETFL, flag);
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); //将connfd加入监听红黑树
while (1) {
printf("epoll_wait begin\n");
res = epoll_wait(efd, resevent, 10, -1); //最多10个, 阻塞监听
printf("epoll_wait end res %d\n", res);
if (resevent[0].data.fd == connfd) {
while ((len = read(connfd, buf, MAXLINE/2)) >0 ) //非阻塞读, 轮询
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
EPOLL中数据封装后函数回调使用:
server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <ctype.h>
#include "wrap.h"
int maxi = 0;
struct e_event
{
//文件描述符
int fd;
//事件
int event;
//数据
void* arg;
//回调函数
void (*call_back)(int epfd, int fd, void* arg);
};
//业务处理
void doWork(int epfd, int fd, void* arg)
{
//业务处理
char buf[1024];
int res = Read(fd, buf, sizeof(buf));
if (res == 0)
{
printf("断开连接\n");
maxi--;
//下树
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, (struct epoll_event*)arg);
//free(((struct epoll_event*)arg)->data.ptr);
Close(fd);
}
int j;
for (j = 0; j < res; j++)
buf[j] = toupper(buf[j]);
Write(fd, buf, res);
}
//创建链接
void doAccept(int epfd, int fd, void* arg)
{
//创建连接
struct sockaddr_in clie_addr;
socklen_t clie_len = sizeof(clie_addr);
struct epoll_event tep;
struct e_event* ev = (struct e_event*)malloc(sizeof(struct e_event));
int cfd = Accept(fd, (struct sockaddr*)&clie_addr, &clie_len);
ev->fd = cfd;
ev->event = EPOLLIN;
ev->call_back = doWork;
tep.events = EPOLLIN;
tep.data.ptr = (void*)ev;
//上树
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep);
maxi++;
}
int main(int argc, char* argv[])
{
int i;
//- 创建连接套接字 socket
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof(opt));
//- 创建sockaddr_in结构体 进行bind
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(6666);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//- 设置监听
Listen(lfd, 128);
struct e_event* ev = (struct e_event*)malloc(sizeof(struct e_event));
struct epoll_event tep;
struct epoll_event eparr[2048];
//创建红黑二叉树
int epfd = epoll_create(2048);
if(epfd < 0)
{
perr_exit("epoll error");
}
//设置lfd监听事件
tep.events = EPOLLIN;
ev->fd = lfd;
ev->event = EPOLLIN;
ev->call_back = doAccept;
//tep.data.fd = lfd;
tep.data.ptr = ev;//
//将lfd添加到树中
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep);
while(1)
{
int ready = epoll_wait(epfd, eparr, maxi + 1, -1);
if(ready < 0)
{
perr_exit("epoll wait error");
}
for(i = 0; i < ready; i++)
{
struct e_event* tev = (struct e_event*)eparr[i].data.ptr;
tev->call_back(epfd, tev->fd, &eparr[i]);
}
}
Close(lfd);
return 0;
}