windows下IOCP, linux下 epoll。

epoll模型其实也是一个同步模型,ET是epoll里面的一种模式,叫 边缘触发。 个人理解,类似于 windows下的事件选择模型。代码如下:

#include <unistd.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <fcntl.h>
#include <netinet/in.h> #include <errno.h> #define MAX_BACKLOG 256
#define MAX_EVENTS 1024 // 如EPOLL的作者Davide Libenzi所说,如果你对一fd同时注册EPOLLIN | EPOLLOUT事件,
// 即使发送缓冲区并非由满变空,也会触发EPOLLOUT事件
// 使用epoll的最好是用ATM模式,当真正需要用到EPOLLOUT时才注册。我理解的ATM模式就是读、写、读、写这样的循环。 // ET模式下的读写
// 只要可读, 就一直读, 直到返回 0, 或者 errno = EAGAIN
// 只要可写, 就一直写, 直到数据发送完, 或者 errno = EAGAIN // 保证对非阻塞的套接字写够请求的字节数才返回
ssize_t socket_write(int sockfd, const char* buffer, size_t buflen)
{
ssize_t tmp = ;
size_t total = buflen;
const char* p = buffer;
while ()
{
tmp = write(sockfd, p, total);
if(tmp < )
{
// 当send收到信号时,可以继续写,但这里返回-1.
if(errno == EINTR)
{
return -;
}
// 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,在这里做延时后再重试.
if(errno == EAGAIN)
{
usleep();
continue;
} return -;
} if((size_t)tmp == total)
{
return buflen;
} total -= tmp;
p += tmp;
} return tmp; // 返回已写的字节数
} // 设定socket句柄为非阻塞
static int SetNonBlock(int nFd)
{
int nOldOpt = fcntl(nFd, F_GETFL, );
int nNewOpt = nOldOpt | O_NONBLOCK; return fcntl(nFd, F_SETFL, nNewOpt);
} // 为套接字追加事件
//static void AddEvent(int nEpfd, int nFd)
//{
// struct epoll_event event;
// event.data.fd = nFd;
// //event.events = EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLET;
// event.events = EPOLLIN | EPOLLOUT | EPOLLET;
// epoll_ctl(nEpfd, EPOLL_CTL_ADD, nFd, &event);
//} static void add_event(int epollfd,int fd,int state)
{
struct epoll_event ev;
ev.events = state | EPOLLET;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
} static void delete_event(int epollfd,int fd,int state)
{
struct epoll_event ev;
ev.events = state | EPOLLET;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);
} static void modify_event(int epollfd,int fd,int state)
{
struct epoll_event ev;
ev.events = state | EPOLLET;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);
} // 主函数
int main(int argc, char const *argv[])
{
if (argc != )
{
printf("usage: CMD Port\n");
exit(-);
} int nPort = atoi(argv[]);
if (nPort < )
{
printf("Port Invalid\n");
exit(-);
} int nSvrFd = socket(AF_INET, SOCK_STREAM, );
// 设置非阻塞
SetNonBlock(nSvrFd); // 绑定地址
struct sockaddr_in addr;
//bzero(&addr,sizeof(addr));
memset(&addr,0x00, sizeof(addr));
addr.sin_port = htons(nPort);
inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
addr.sin_family = (AF_INET); if ( != bind(nSvrFd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("Bind Failure:");
exit(-);
} //监听端口
if ( != listen(nSvrFd, MAX_BACKLOG))
{
perror("Listen Failure:");
exit(-);
}
// 创建epoll句柄
int nEpfd = epoll_create(); struct epoll_event events[MAX_EVENTS];
// AddEvent(nEpfd, nSvrFd); add_event(nEpfd,nSvrFd,EPOLLIN); while ()
{
//等待事件到来
int nReadyNums = epoll_wait(nEpfd, events, MAX_EVENTS, -);
int nClientFd = -; struct sockaddr_in cliaddr;
socklen_t cliaddrlen = sizeof(cliaddr); for (int i = ; i < nReadyNums; ++i)
{
if (events[i].data.fd == nSvrFd)
{
// accept放到线程中,可以使用while循环
// 多个连接同时到达,服务器的 TCP 就绪队列瞬间积累多个就绪
// 连接,由于是边缘触发模式,epoll 只会通知一次,accept 只处理一个连接,导致 TCP 就绪队列中剩下的连接都得不到处理。
// 解决办法是用 while 循环抱住 accept 调用,处理完 TCP 就绪队列中的所有连接后再退出循环。
while ((nClientFd = accept(nSvrFd,(struct sockaddr*)&cliaddr,&cliaddrlen)) > )
// if ((nClientFd = accept(nSvrFd,(struct sockaddr*)&cliaddr,&cliaddrlen)) > 0)
{
printf("建立连接\n");
//设置为非阻塞
SetNonBlock(nClientFd);
//添加事件监听
// AddEvent(nEpfd, nClientFd);
add_event(nEpfd,nClientFd,EPOLLIN);
} }
else
{
// 处理FD事件
if (events[i].events & EPOLLIN)
{
int n = ;
int nread = ;
char buf[BUFSIZ];
memset(buf, 0x00, sizeof(buf));
while ((nread = read(events[i].data.fd, buf + n, BUFSIZ-)) > )
{
n += nread;
} if (nread == - && errno != EAGAIN)
{
perror("read error");
}
printf("read----%s\n", buf); // 直接去写,不需要EPOLLOUT事件
const char* p = "epoll_server.cpp answer....\n";
socket_write(events[i].data.fd, p, strlen(p) + ); // 后面代码无效,ET模式下,EPOLLOUT事件不响应或者出错,没搞通,实际代码中也不会用到,忽略。
continue; // modify_event(nEpfd,nClientFd,EPOLLOUT); // continue;
// 强制触发一次
struct epoll_event ev;
int fd = events[i].data.fd;
ev.events = events[i].events | EPOLLOUT;
if(- == epoll_ctl(nEpfd, EPOLL_CTL_MOD, fd, &ev))
{
perror("epoll_ctl: mod");
}
}
// 可写
if (events[i].events & EPOLLOUT)
{
const char* p = "epoll_server.cpp answer....";
socket_write(events[i].data.fd, p, strlen(p) + ); //modify_event(nEpfd,nClientFd,EPOLLIN);
// 强制触发一次
// struct epoll_event ev;
// int fd = events[i].data.fd;
// ev.events = events[i].events | EPOLLIN;
// if(-1 == epoll_ctl(nEpfd, EPOLL_CTL_MOD, fd, &ev))
// {
// perror("epoll_ctl: mod");
// }
}
} }
} }

使用telnet 命令模拟客户端进行测试,结果如下:

服务端:

客户端:

比较好的linux 网络编程文章 http://www.cnblogs.com/Anker/p/3265058.html

epoll 服务端 ET模式的更多相关文章

  1. 数据同步canal服务端配置mysql多主

    canal服务端HA模式,本人并未使用过,为保证文章的完整性,从以下地址摘抄该部分内容,待以后验证及使用 https://github.com/alibaba/canal/wiki/AdminGuid ...

  2. 数据同步canal服务端HA配置

    canal服务端HA模式,本人并未使用过,为保证文章的完整性,从以下地址摘抄该部分内容,待以后验证及使用 https://github.com/alibaba/canal/wiki/AdminGuid ...

  3. Redis源代码分析(三十五)--- redis.c服务端的实现分析(2)

    在Redis服务端的代码量真的是比較大,假设一个一个API的学习怎么实现,无疑是一种效率非常低的做法,所以我今天对服务端的实现代码的学习,重在他的运行流程上.而对于他的模块设计在上一篇中我已经分析过了 ...

  4. ZooKeeper单机服务端的启动源码阅读

    程序的入口QuorumPeerMain public static void main(String[] args) { // QuorumPeerMain main = new QuorumPeer ...

  5. http服务端架构演进

    摘要 在详解http报文相关文章中我们介绍了http协议是如何工作的,那么构建一个真实的网站还需要引入组件呢?一些常见的名词到底是什么含义呢? 什么叫正向代理,什么叫反向代理 服务代理与负载均衡的差别 ...

  6. react基础学习和react服务端渲染框架next.js踩坑

    说明 React作为Facebook 内部开发 Instagram 的项目中,是一个用来构建用户界面的优秀 JS 库,于 2013 年 5 月开源.作为前端的三大框架之一,React的应用可以说是非常 ...

  7. [Next] 服务端渲染知识补充

    渲染 渲染:就是将数据和模版组装成 html 客户端渲染 客户端渲染模式下,服务端把渲染的静态文件给到客户端,客户端拿到服务端发送过来的文件自己跑一遍 js,根据 JS 运行结果,生成相应 DOM,然 ...

  8. Service系统服务(六):rsync基本用法、rsync+SSH同步、配置rsync服务端、访问rsync共享资源、使用inotifywait工具、配置Web镜像同步、配置并验证Split分离解析

    一.rsync基本用法 目标: 本例要求掌握远程同步的基本操作,使用rsync命令完成下列任务: 1> 将目录 /boot 同步到目录 /todir 下   2> 将目录 /boot 下的 ...

  9. c++ 网络编程(八) LINUX-epoll/windows-IOCP下 socket opoll函数用法 优于select方法的epoll 以及windows下IOCP 解决多进程服务端创建进程资源浪费问题

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9622548.html 锲子:关于并发服务器中的I/O复用实现方式,前面在网络编程系列四还是五来 ...

随机推荐

  1. Grunt 使用(二)uglify插件压缩javascript代码

    本文在配置grunt基本环境的基础下,讲解如何使用grunt-contrib-uglify进行javascript压缩 本文只介绍了grunt-contrib-uglify插件的一种压缩方式适用于大部 ...

  2. css如何制作八边形

    随着技术的发展,css也越发强大,css可以制作很多有趣的图形,让我们一起来看一下如何使用css制作一个八边形吧.   方法/步骤     1新建一个html文件.如图:   在html文件上创建一个 ...

  3. r.js压缩打包(require + backbone)项目开发文件

    最近项目稳定了一点,之前一直没空关注的开发文件压缩打包问题也有时间来解决了 AMD模块化开发中的代码压缩打包工具——r.js 环境搭建基于nodejs:用于AMD模块化开发中的项目文件压缩打包,不是A ...

  4. 035server端并发聊天

    import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): # 里面是每个客户端连接执 ...

  5. html style标签

    <div id="d1">或者<div class="d1">在css下定义id的函数前缀是#,定义class前缀是.css代码就可以这 ...

  6. vue中调用地图

    一. vue-amap,一个基于 Vue 2.x 和高德地图的地图组件 这个就不细说了,按照其文档,就能够安装下来. 二. 按照官方提供的方法引入 1.修改webpac.base.conf.js文件 ...

  7. js 实现商品放大镜效果

    知识点,需熟悉下面属性及含义: offsetLeft           //获取元素相对左侧的距离 (计算是从最左侧边框外开始) offsetTop           //获取元素相对顶部的距离 ...

  8. Linux脚本开头#!/bin/bash和#!/bin/sh是什么意思以及区别

    一.意思 #!/bin/sh是指此脚本使用/bin/sh来解释执行,#!是特殊的表示符,其后面根的是此解释此脚本的shell的路径. 其实第一句的#!是对脚本的解释器程序路径,脚本的内容是由解释器解释 ...

  9. Redis(RedisTemplate)使用list链表

    RedisTemplate配置:https://www.cnblogs.com/weibanggang/p/10188682.html package com.wbg.springRedis.test ...

  10. 使用C#的AssemblyResolve事件和TypeResolve事件动态解析加载失败的程序集

    我们知道反射是 依赖注入 模式的基础,依赖注入要求只在项目中引用定义接口的程序集,而不引用接口实现类的程序集,因为接口实现类的程序集应该是通过反射来动态加载的,这样才能保证接口与其实现类之间的松耦合. ...