kinchen


  • 首页

  • 归档

  • 关于

  • 标签

  • 分类

未命名

发表于 2018-06-29

#【HTTP】HTTPS 原理详解


title: HTTPS 原理详解

HTTPS(全称:HyperText Transfer Protocol over Secure Socket Layer),其实 HTTPS 并不是一个新鲜协议,Google 很早就开始启用了,初衷是为了保证数据安全。 近两年,Google、Baidu、Facebook 等这样的互联网巨头,不谋而合地开始大力推行 HTTPS, 国内外的大型互联网公司很多也都已经启用了全站 HTTPS,这也是未来互联网发展的趋势。

为鼓励全球网站的 HTTPS 实现,一些互联网公司都提出了自己的要求:

1)Google 已调整搜索引擎算法,让采用 HTTPS 的网站在搜索中排名更靠前;

2)从 2017 年开始,Chrome 浏览器已把采用 HTTP 协议的网站标记为不安全网站;

3)苹果要求 2017 年App Store 中的所有应用都必须使用 HTTPS 加密连接;

4)当前国内炒的很火热的微信小程序也要求必须使用 HTTPS 协议;

5)新一代的 HTTP/2 协议的支持需以 HTTPS 为基础。

等等,因此想必在不久的将来,全网 HTTPS 势在必行。

###概念

协议

####1、HTTP 协议(HyperText Transfer Protocol,超文本传输协议):是客户端浏览器或其他程序与Web服务器之间的应用层通信协议 。

####2、HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):可以理解为HTTP+SSL/TLS, 即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL,用于安全的 HTTP 数据传输。

此处输入图片的描述

如上图所示 HTTPS 相比 HTTP 多了一层 SSL/TLS

SSL(Secure Socket Layer,安全套接字层):1994年为 Netscape 所研发,SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。

TLS(Transport Layer Security,传输层安全):其前身是 SSL,它最初的几个版本(SSL 1.0、SSL 2.0、SSL 3.0)由网景公司开发,1999年从 3.1 开始被 IETF 标准化并改名,发展至今已经有 TLS 1.0、TLS 1.1、TLS 1.2 三个版本。SSL3.0和TLS1.0由于存在安全漏洞,已经很少被使用到。TLS 1.3 改动会比较大,目前还在草案阶段,目前使用最广泛的是TLS 1.1、TLS 1.2。

加密算法:

据记载,公元前400年,古希腊人就发明了置换密码;在第二次世界大战期间,德国军方启用了“恩尼格玛”密码机,所以密码学在社会发展中有着广泛的用途。

1、对称加密

有流式、分组两种,加密和解密都是使用的同一个密钥。

例如:DES、AES-GCM、ChaCha20-Poly1305等

2、非对称加密

加密使用的密钥和解密使用的密钥是不相同的,分别称为:公钥、私钥,公钥和算法都是公开的,私钥是保密的。非对称加密算法性能较低,但是安全性超强,由于其加密特性,非对称加密算法能加密的数据长度也是有限的。

例如:RSA、DSA、ECDSA、 DH、ECDHE

3、哈希算法

将任意长度的信息转换为较短的固定长度的值,通常其长度要比信息小得多,且算法不可逆。

例如:MD5、SHA-1、SHA-2、SHA-256 等

4、数字签名

签名就是在信息的后面再加上一段内容(信息经过hash后的值),可以证明信息没有被修改过。hash值一般都会加密后(也就是签名)再和信息一起发送,以保证这个hash值不被修改。

详解

一、HTTP访问过程

此处输入图片的描述

抓包如下:

此处输入图片的描述

如上图所示,HTTP请求过程中,客户端与服务器之间没有任何身份确认的过程,数据全部明文传输,“裸奔”在互联网上,所以很容易遭到黑客的攻击,如下:
此处输入图片的描述

可以看到,客户端发出的请求很容易被黑客截获,如果此时黑客冒充服务器,则其可返回任意信息给客户端,而不被客户端察觉,所以我们经常会听到一词“劫持”,现象如下:
此处输入图片的描述

下面两图中,浏览器中填入的是相同的URL,左边是正确响应,而右边则是被劫持后的响应

所以 HTTP 传输面临的风险有:

(1) 窃听风险:黑客可以获知通信内容。

(2) 篡改风险:黑客可以修改通信内容。

(3) 冒充风险:黑客可以冒充他人身份参与通信。

二、HTTP 向 HTTPS 演化的过程

第一步:为了防止上述现象的发生,人们想到一个办法:对传输的信息加密(即使黑客截获,也无法破解)

此处输入图片的描述

如上图所示,此种方式属于对称加密,双方拥有相同的密钥,信息得到安全传输,但此种方式的缺点是:

(1)不同的客户端、服务器数量庞大,所以双方都需要维护大量的密钥,维护成本很高

(2)因每个客户端、服务器的安全级别不同,密钥极易泄露

第二步:既然使用对称加密时,密钥维护这么繁琐,那我们就用非对称加密试试
此处输入图片的描述

如上图所示,客户端用公钥对请求内容加密,服务器使用私钥对内容解密,反之亦然,但上述过程也存在缺点:

(1)公钥是公开的(也就是黑客也会有公钥),所以第 ④ 步私钥加密的信息,如果被黑客截获,其可以使用公钥进行解密,获取其中的内容

第三步:非对称加密既然也有缺陷,那我们就将对称加密,非对称加密两者结合起来,取其精华、去其糟粕,发挥两者的各自的优势
此处输入图片的描述

如上图所示

(1)第 ③ 步时,客户端说:(咱们后续回话采用对称加密吧,这是对称加密的算法和对称密钥)这段话用公钥进行加密,然后传给服务器

(2)服务器收到信息后,用私钥解密,提取出对称加密算法和对称密钥后,服务器说:(好的)对称密钥加密

(3)后续两者之间信息的传输就可以使用对称加密的方式了

遇到的问题:

(1)客户端如何获得公钥

(2)如何确认服务器是真实的而不是黑客

第四步:获取公钥与确认服务器身份
此处输入图片的描述

1、获取公钥

(1)提供一个下载公钥的地址,回话前让客户端去下载。(缺点:下载地址有可能是假的;客户端每次在回话前都先去下载公钥也很麻烦)

(2)回话开始时,服务器把公钥发给客户端(缺点:黑客冒充服务器,发送给客户端假的公钥)

2、那有木有一种方式既可以安全的获取公钥,又能防止黑客冒充呢? 那就需要用到终极武器了:SSL 证书(申购)
此处输入图片的描述

如上图所示,在第 ② 步时服务器发送了一个SSL证书给客户端,SSL 证书中包含的具体内容有:

(1)证书的发布机构CA

(2)证书的有效期

(3)公钥

(4)证书所有者

(5)签名

………

3、客户端在接受到服务端发来的SSL证书时,会对证书的真伪进行校验,以浏览器为例说明如下:

(1)首先浏览器读取证书中的证书所有者、有效期等信息进行一一校验

(2)浏览器开始查找操作系统中已内置的受信任的证书发布机构CA,与服务器发来的证书中的颁发者CA比对,用于校验证书是否为合法机构颁发

(3)如果找不到,浏览器就会报错,说明服务器发来的证书是不可信任的。

(4)如果找到,那么浏览器就会从操作系统中取出 颁发者CA 的公钥,然后对服务器发来的证书里面的签名进行解密

(5)浏览器使用相同的hash算法计算出服务器发来的证书的hash值,将这个计算的hash值与证书中签名做对比

(6)对比结果一致,则证明服务器发来的证书合法,没有被冒充

(7)此时浏览器就可以读取证书中的公钥,用于后续加密了

4、所以通过发送SSL证书的形式,既解决了公钥获取问题,又解决了黑客冒充问题,一箭双雕,HTTPS加密过程也就此形成

所以相比HTTP,HTTPS 传输更加安全

(1) 所有信息都是加密传播,黑客无法窃听。

(2) 具有校验机制,一旦被篡改,通信双方会立刻发现。

(3) 配备身份证书,防止身份被冒充。

总结

综上所述,相比 HTTP 协议,HTTPS 协议增加了很多握手、加密解密等流程,虽然过程很复杂,但其可以保证数据传输的安全。所以在这个互联网膨胀的时代,其中隐藏着各种看不见的危机,为了保证数据的安全,维护网络稳定,建议大家多多推广HTTPS。

HTTPS 缺点:

(1)SSL 证书费用很高,以及其在服务器上的部署、更新维护非常繁琐

(2)HTTPS 降低用户访问速度(多次握手)

(3)网站改用HTTPS 以后,由HTTP 跳转到 HTTPS 的方式增加了用户访问耗时(多数网站采用302跳转)

(4)HTTPS 涉及到的安全算法会消耗 CPU 资源,需要增加大量机器(https访问过程需要加解密)

linux-epoll原理详解

发表于 2018-06-29

Linux网络编程–epoll 模型原理详解以及实例

###1.简介
Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数。Linux 2.6内核中有提高网络I/O性能的新方法,即epoll 。
epoll是什么?按照man手册的说法是为处理大批量句柄而作了改进的poll。要使用epoll只需要以下的三个系统函数调用: epoll_create,epoll_ctl,epoll_wait。

###2.select模型的缺陷

####(1) 在Linux内核中,select所用到的FD_SET是有限的
内核中有个参数FD_SETSIZE定义了每个FD_SET的句柄个数:`#define FD_SETSIZE 1024`。也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的;或者同时检测1025个句柄的可写状态也是不可能的。

####(2) 内核中实现select是使用轮询方法
每次检测都会遍历所有FD_SET中的句柄,显然select函数的执行时间与FD_SET中句柄的个数有一个比例关系,即select要检测的句柄数越多就会越费时

###3.Windows IOCP模型的缺陷
windows完成端口实现的AIO,实际上也只是使用内部用线程池实现的,最后的结果是IO有个线程池,你的应用程序也需要一个线程池。很多文档其实已经指出了这引发的线程context-switch所带来的代价。

###4.EPOLL模型的优点
(1) 支持一个进程打开大数目的socket描述符(FD)
epoll没有select模型中的限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于select 所支持的2048。下面是我的小PC机上的显示:

cat```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
6815744 
那么对于服务器而言,这个数目会更大。
(2) IO效率不随FD数目增加而线性下降
传统select/poll的另一个致命弱点就是当你拥有一个很大的socket集合,由于网络得延时,使得任一时间只有部分的socket是”活跃”的,而select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对”活跃”的socket进行操作:这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。于是,只有”活跃”的socket才会主动去调用callback函数,其他idle状态的socket则不会。在这点上,epoll实现了一个”伪”AIO”,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的,比如一个高速LAN环境,epoll也不比select/poll低多少效率,但若过多使用的调用epoll_ctl,效率稍微有些下降。然而一旦使用idle connections模拟WAN环境,那么epoll的效率就远在select/poll之上了。
(3) 使用mmap加速内核与用户空间的消息传递
无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就显得很重要。在这点上,epoll是通过内核于用户空间mmap同一块内存实现。

###5.EPOLL模型的工作模式
####(1) LT模式
`LT:level triggered`,这是缺省的工作方式,同时支持`block`和`no-block socket`,在这种模式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
####(2) ET模式
`ET:edge-triggered`,这是高速工作方式,只支持`no-block socket`。在这种模式下,当描述符从未就绪变为就绪时,内核就通过epoll告诉你,然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作而导致那个文件描述符不再是就绪状态(比如你在发送,接收或是接受请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核就不会发送更多的通知(only once)。不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。

###6.EPOLL模型的使用方法
epoll用到的所有函数都是在头文件`sys/epoll.h`中声明的,下面简要说明所用到的数据结构和函数:

epoll_data、epoll_data_t、epoll_event
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32; uint64_t u64;
} epoll_data_t;

struct epoll_event {
__uint32_t events; / Epoll events /
epoll_data_t data; / User data variable /
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件。epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件,可能的取值为:
`EPOLLIN`: 表示对应的文件描述符可以读;
`EPOLLOUT`: 表示对应的文件描述符可以写;
`EPOLLPRI`: 表示对应的文件描述符有紧急的数据可读;
`EPOLLERR`: 表示对应的文件描述符发生错误;
`EPOLLHUP`: 表示对应的文件描述符被挂断;
`EPOLLET`: 表示对应的文件描述符有事件发生;

联合体epoll_data用来保存触发事件的某个文件描述符相关的数据。例如一个client连接到服务器,服务器通过调用accept函数可以得到于这个client对应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段,以便后面的读写操作在这个文件描述符上进行。

(2)`epoll_create `
函数声明:`intepoll_create(intsize) `
函数说明:该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围。

(3) `epoll_ctl`函数
函数声明:`intepoll_ctl(int epfd,int op, int fd, struct epoll_event *event) `
函数说明:该函数用于控制某个文件描述符上的事件,可以注册事件、修改事件、删除事件。
`epfd`:由 epoll_create 生成的epoll专用的文件描述符;
`op`:要进行的操作,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除;
`fd`:关联的文件描述符;
`event`:指向epoll_event的指针;
如果调用成功则返回0,不成功则返回-1。

(4) `epoll_wait`函数
函数声明:`int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout) `
函数说明:该函数用于轮询I/O事件的发生。
`epfd`:由epoll_create 生成的epoll专用的文件描述符;
`epoll_event`:用于回传代处理事件的数组;
`maxevents`:每次能处理的事件数;
`timeout`:等待I/O事件发生的超时值;
返回发生事件数。

###7 设计思路及模板
首先通过`create_epoll(int maxfds)`来创建一个epoll的句柄,其中maxfds为你的epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作都将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。
然后在你的网络主循环里面,调用`epoll_wait(int epfd, epoll_event events, int max_events,int timeout)`来查询所有的网络接口,看哪一个可以读,哪一个可以写。基本的语法为:
`nfds = epoll_wait(kdpfd, events, maxevents, -1); `
其中`kdpfd`为用`epoll_create`创建之后的句柄,events是一个`epoll_event*`的指针,当`epoll_wait`函数操作成功之后,events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout参数指示 epoll_wait的超时条件,为0时表示马上返回;为-1时表示函数会一直等下去直到有事件返回;为任意正整数时表示等这么长的时间,如果一直没有事件,则会返回。一般情况下如果网络主循环是单线程的话,可以用-1来等待,这样可以保证一些效率,如果是和主循环在同一个线程的话,则可以用0来保证主循环的效率。epoll_wait返回之后,应该进入一个循环,以便遍历所有的事件。
对epoll 的操作就这么简单,总共不过4个`API:epoll_create, epoll_ctl,epoll_wait和close`。以下是man中的一个例子。

struct epoll_event ev, events;
for(;;)
{
nfds = epoll_wait(kdpfd, events, maxevents, -1); //等待IO事件
for(n = 0; n < nfds; ++n)
{
//如果是主socket的事件,则表示有新连接进入,需要进行新连接的处理。
if(events[n].data.fd == listener)
{
client = accept(listener, (struct sockaddr
) &local, &addrlen);
if(client < 0)
{
perror(“accept error”);
continue;
}
// 将新连接置于非阻塞模式
setnonblocking(client);
ev.events = EPOLLIN | EPOLLET;
//注意这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听,
//如果有写操作的话,这个时候epoll是不会返回事件的,
//如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET。
// 并且将新连接也加入EPOLL的监听队列
ev.data.fd = client;
// 设置好event之后,将这个新的event通过epoll_ctl
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0)
{
//加入到epoll的监听队列里,这里用EPOLL_CTL_ADD
//来加一个新的 epoll事件。可以通过EPOLL_CTL_DEL来减少
//一个epoll事件,通过EPOLL_CTL_MOD来改变一个事件的监听方式。
fprintf(stderr, “epoll set insertion error: fd=%d”0, client);
return -1;
}
}
else
// 如果不是主socket的事件的话,则代表这是一个用户的socket的事件,
// 则用来处理这个用户的socket的事情是,比如说read(fd,xxx)之类,或者一些其他的处理。
do_use_fd(events[n].data.fd);
}

1
2

###8 EPOLL模型的简单实例

#include

#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>

#define MAXLINE 10

#define OPEN_MAX 100

#define LISTENQ 20

#define SERV_PORT 5555

#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 i, maxi, listenfd, connfd, sockfd, epfd, nfds;
ssize_t n;
char line[MAXLINE];
socklen_t clilen;
//声明epoll_event结构体的变量, ev用于注册事件, events数组用于回传要处理的事件
struct epoll_event ev,events[20];
//生成用于处理accept的epoll专用的文件描述符, 指定生成描述符的最大范围为256
epfd = epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);

setnonblocking(listenfd); //把用于监听的socket设置为非阻塞方式
ev.data.fd = listenfd; //设置与要处理的事件相关的文件描述符
ev.events = EPOLLIN | EPOLLET; //设置要处理的事件类型
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //注册epoll事件
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char local_addr = “200.200.200.204”;
inet_aton(local_addr, &(serveraddr.sin_addr));
serveraddr.sin_port = htons(SERV_PORT); //或者htons(SERV_PORT);
bind(listenfd,(sockaddr
)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);

maxi = 0;
for( ; ; )
{
nfds = epoll_wait(epfd, events, 20, 500); //等待epoll事件的发生
for(i = 0; i < nfds; ++i) //处理所发生的所有事件
{
if(events[i].data.fd == listenfd) //监听事件
{
connfd = accept(listenfd, (sockaddr )&clientaddr, &clilen);
if(connfd < 0)
{
perror(“connfd<0”);
exit(1);
}
setnonblocking(connfd); //把客户端的socket设置为非阻塞方式
char
str = inet_ntoa(clientaddr.sin_addr);
std::cout << “connect from “ << str <<std::endl;
ev.data.fd=connfd; //设置用于读操作的文件描述符
ev.events=EPOLLIN | EPOLLET; //设置用于注测的读操作事件
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
//注册ev事件
}
else if(events[i].events&EPOLLIN) //读事件
{
if ( (sockfd = events[i].data.fd) < 0)
{
continue;
}
if ( (n = read(sockfd, line, MAXLINE)) < 0) // 这里和IOCP不同
{
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;
}
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);
}
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

###9.epoll进阶思考

最近学习`EPOLL`模型,介绍中说将`EPOLL`与`Windows IOCP`模型进行比较,说其的优势在于解决了`IOCP`模型大量线程上下文切换的开销,于是可以看出,`EPOLL`模型不需要多线程,即单线程中可以处理`EPOLL`逻辑。如果引入多线程反而会引起一些问题。但是`EPOLL`模型的服务器端到底可以不可以用多线程技术,如果可以,改怎么取舍,这成了困扰我的问题。上网查了一下,有这样几种声音:
(1) “要么事件驱动(如epoll),要么多线程,要么多进程,把这几个综合起来使用,感觉更加麻烦。”;
(2) “单线程使用epoll,但是不能发挥多核;多线程不用epoll。”;
#####(3) “主通信线程使用epoll所有需要监控的FD,有事件交给多线程去处理”;
#####(4) “既然用了epoll, 那么线程就不应该看到fd, 而只看到的是一个一个的业务请求/响应; epoll将网络数据组装成业务数据后, 转交给业务线程进行处理。这就是常说的半同步半异步”。
我比较赞同上述(3)、(4)中的观点
`EPOLLOUT`只有在缓冲区已经满了,不可以发送了,过了一会儿缓冲区中有空间了,就会触发`EPOLLOUT`,而且只触发一次。如果你编写的程序的网络IO不大,一次写入的数据不多的时候,通常都是`epoll_wait`立刻就会触发` EPOLLOUT`;如果你不调用` epoll`,直接写 `socket`,那么情况就取决于这个`socket`的缓冲区是不是足够了。如果缓冲区足够,那么写就成功。如果缓冲区不足,那么取决你的socket是不是阻塞的,要么阻塞到写完成,要么出错返回。所以EPOLLOUT事件具有较大的随机性,ET模式一般只用于`EPOLLIN`, 很少用于`EPOLLOUT`。
9.2. 具体做法
(1) 主通信线程使用epoll所有需要监控的FD,负责监控`listenfd`和`connfd`,这里只监听`EPOLLIN`事件,不监听`EPOLLOUT`事件;
(2) 一旦从Client收到了数据以后,将其构造成一个消息,放入消息队列中;
(3) 若干工作线程竞争,从消息队列中取出消息并进行处理,然后把处理结果发送给客户端。发送客户端的操作由工作线程完成。直接进行`write`。`write`到`EAGAIN`或`EWOULDBLOCK`后,线程循环`continue`等待缓冲区队列
发送函数代码如下:

bool send_data(int connfd, char *pbuffer, unsigned int &len,int flag)
{
if ((connfd < 0) || (0 == pbuffer))
{
return false;
}

int result = 0;
int remain_size = (int) len;
int send_size = 0;
const char *p = pbuffer;

time_t start_time = time(NULL);
int time_out = 3;

do
{
if (time(NULL) > start + time_out)
{
return false;
}

send_size = send(connfd, p, remain_size, flag);
if (nSentSize < 0)
{
  if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR))
  {
      continue;
  }
  else
  {
      len -= remain_size;
      return false;
  }
}

p += send_size;
remain_size -= send_size;

}while(remain_size > 0);

return true;
}

1
2
3
4
5
6
7
8
9
10
###10 epoll 实现服务器和客户端例子
最后我们用C++实现一个简单的客户端回射,所用到的代码文件是

`net.h` ` server.cpp ` `client.cpp`服务器端:epoll实现的,干两件事分别为:1.等待客户端的链接,2.接收来自客户端的数据并且回射;

客户端:select实现,干两件事为:1.等待键盘输入,2.发送数据到服务器端并且接收服务器端回射的数据;

**/***********
net.h
***********/**

#include <stdio.h>

#ifndef _NET_H

#define _NET_H

#include

#include

#include

#include <stdio.h>

#include <sys/types.h>

#include <sys/epoll.h> //epoll ways file

#include <sys/socket.h>

#include <fcntl.h> //block and noblock

#include <stdlib.h>

#include <error.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#include <string.h>

#include <signal.h>

using namespace std;

#define hand_error(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)

#endif

1
2
3
4
5
6
7
8
9
10
11
12
13
14



关于
######signal(SIGPIPE, SIG_IGN) https://blog.csdn.net/lizhi200404520/article/details/6535983
######socket编程(一)使用SOCK_STREAM建立可靠通信
https://blog.csdn.net/beautyleaf/article/details/51171103
#####setsockopt https://blog.csdn.net/yangzhongxuan/article/details/8079705



**/***********
server.c
***********/**

#include “net.h”

#define MAX_EVENTS 10000

int setblock(int sock)
{
int ret = fcntl(sock, F_SETFL, 0);
if (ret < 0 )
hand_error(“setblock”);
return 0;
}
int setnoblock(int sock) //设置非阻塞模式
{
int ret = fcntl(sock, F_SETFL, O_NONBLOCK );
if(ret < 0)
hand_error(“setnoblock”);
return 0;
}

int main()
{
signal(SIGPIPE,SIG_IGN);
int listenfd;
listenfd = socket( AF_INET, SOCK_STREAM,0 ); //create a socket stream
if( listenfd < 0 )
hand_error( “socket_create”);
setnoblock(listenfd);
int on = 1;
if( setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))< 0)
hand_error(“setsockopt”);

struct sockaddr_in my_addr;
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(18000);  //here is host  sequeue
my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

if( bind( listenfd, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0)
    hand_error("bind");

int lisId = listen(listenfd, SOMAXCONN);
if( lisId < 0)  //LISTEN
    hand_error("listen");

struct sockaddr_in peer_addr;  //用来 save client addr
socklen_t peerlen;  
//下面是一些初始化,都是关于epoll的。
vector<int> clients;
int count = 0;
int cli_sock = 0;
int epfd = 0;  //epoll 的文件描述符
int ret_events;  //epoll_wait()的返回值

struct epoll_event ev_remov, ev, events[MAX_EVENTS]; //events 用来存放从内核读取的的事件
ev.events = EPOLLET | EPOLLIN; //边缘方式触发
ev.data.fd = listenfd;

epfd = epoll_create(MAX_EVENTS);  //create epoll,返回值为epoll的文件描述符
//epfd = epoll_create(EPOLL_CLOEXEC);  //新版写法
if(epfd < 0)
    hand_error("epoll_create");
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);  //添加时间
if(ret < 0)
    hand_error("epoll_ctl");


while(1)
{
    ret_events = epoll_wait(epfd, events, MAX_EVENTS, -1);  //类似于select函数,这里是等待事件的到来。
    if(ret_events == -1)
    {
        cout<<"ret_events = "<<ret_events<<endl;
        hand_error("epoll_wait");
    }

    if( ret_events == 0)
    {
        cout<<"ret_events = "<<ret_events<<endl;
        continue;
    }

// cout<<”ret_events = “<<ret_events<<endl;
for( int num = 0; num < ret_events; num ++)
{
cout<<”num = “<<num<<endl;
cout<<”events[num].data.fd = “<<events[num].data.fd<<endl;
if(events[num].data.fd == listenfd) //client connect
{
cout<<”listen sucess and listenfd = “<<listenfd<<endl;
cli_sock = accept(listenfd, (struct sockaddr*)&peer_addr, &peerlen);
if(cli_sock < 0)
hand_error(“accept”);
cout<<”count = “<<count++;
printf(“ip=%s,port = %d\n”, inet_ntoa(peer_addr.sin_addr),peer_addr.sin_port);
clients.push_back(cli_sock);
setnoblock(cli_sock); //设置为非阻塞模式
ev.data.fd = cli_sock;// 将新连接也加入EPOLL的监听队列
ev.events = EPOLLIN | EPOLLET ;
if(epoll_ctl(epfd, EPOLL_CTL_ADD, cli_sock, &ev)< 0)
hand_error(“epoll_ctl”);
}

        else if( events[num].events & EPOLLIN)
        {
            cli_sock = events[num].data.fd;
            if(cli_sock < 0)
                hand_error("cli_sock");
            char recvbuf[1024];
            memset(recvbuf, 0 , sizeof(recvbuf));
            int num = read( cli_sock, recvbuf, sizeof(recvbuf));
            if(num == -1)
                hand_error("read have some problem:");
            if( num == 0 )  //stand of client have exit
            {
                cout<<"client have exit"<<endl;
                close(cli_sock);
                ev_remov = events[num];
                epoll_ctl(epfd, EPOLL_CTL_DEL, cli_sock, &ev_remov);
                clients.erase(remove(clients.begin(), clients.end(), cli_sock),clients.end());
            }
            fputs(recvbuf,stdout);
            write(cli_sock, recvbuf, strlen(recvbuf));
        }
    }
}

return 0;

}

1
2
3
4
5
6



**/***********
client.c
***********/**

#include “net.h”

int main()
{
signal(SIGPIPE,SIG_IGN);
int sock;
sock = socket( AF_INET, SOCK_STREAM,0 ); //create a socket stream
if( sock< 0 )
hand_error( “socket_create”);

struct sockaddr_in my_addr;

//memset my_addr;
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(18000);  //here is host sequeue

// my_addr.sin_addr.s_addr = htonl( INADDR_ANY );
my_addr.sin_addr.s_addr = inet_addr(“127.0.0.1”);

int conn = connect(sock, (struct sockaddr *)&my_addr, sizeof(my_addr)) ;
if(conn != 0)
    hand_error("connect");

char recvbuf[1024] = {0};
char sendbuf[1024] = {0};
fd_set rset;
FD_ZERO(&rset);   

int nready = 0;
int maxfd;
int stdinof = fileno(stdin);
if( stdinof > sock)
    maxfd = stdinof;
else
    maxfd = sock;
while(1)
{
    //select返回后把原来待检测的但是仍没就绪的描述字清0了。所以每次调用select前都要重新设置一下待检测的描述字
    FD_SET(sock, &rset);  
    FD_SET(stdinof, &rset);
    nready = select(maxfd+1, &rset, NULL, NULL, NULL); 
    cout<<"nready = "<<nready<<"  "<<"maxfd = "<<maxfd<<endl;
    if(nready == -1 )
        break;
    else if( nready == 0)
        continue;
    else
    {
        if( FD_ISSET(sock, &rset) )  //检测sock是否已经在集合rset里面。
        {
            int ret = read( sock, recvbuf, sizeof(recvbuf));  //读数据
            if( ret == -1)
                hand_error("read");
            else if( ret == 0)
            {
                cout<<"sever have close"<<endl;
                close(sock);
                break;
            }
            else
            {
                fputs(recvbuf,stdout);    //输出数据
                memset(recvbuf, 0, strlen(recvbuf));
            }  
        }

        if( FD_ISSET(stdinof, &rset))  //检测stdin的文件描述符是否在集合里面
        {  
            if(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
            {
                int num = write(sock, sendbuf, strlen(sendbuf));  //写数据
                cout<<"sent num = "<<num<<endl;
                memset(sendbuf, 0, sizeof(sendbuf));
            }
        }
    }
}
return 0;

}
`

Hello World

发表于 2018-06-29

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

kinchen

kinchen

3 日志
GitHub E-Mail CSDN Twitter FB Page
© 2018 kinchen
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4