网络编程关注的问题
连接的建立
分为两种:服务端处理接收客户端的连接,服务端作为客户端连接第三方服务;
int clientfd = accept(listenfd, addr, sz);
int connectfd = socket(AF_INET, SOCK_STREAM, 0);
connect(connectfd, (struct sockaddr *)&addr, sizeof(addr));
连接的断开
分为两种:主动断开和被动断开;
// 主动关闭
close(fd);
shutdown(fd, SHUT_RDWR);
// 主动关闭本地读端,对端写段关闭
shutdown(fd, SHUT_RD);
// 主动关闭本地写端,对端读段关闭
shutdown(fd, SHUT_WR);
// 被动:读端关闭
int n = read(fd, buf, sz);
if (n == 0) {
close_read(fd);
// close(fd);
}
// 被动:写端关闭
int n = write(fd, buf, sz);
if (n == -1 && errno == EPIPE) {
close_write(fd);
// close(fd);
}
close_read 和 close_write是我们自己写的函数,一般直接close即可
消息的到达
从读缓冲区中读取数据;
int n = read(fd, buf, sz);
if (n < 0) { //n == -1
if (errno == EINTR || errno == EWOULDBLOCK)
break;
close(fd);
} else if (n == 0) {
close(fd);
} else {
// 处理buf
}
消息发送完毕
往写缓冲区中写数据;
int n = write(fd, buf, sz);
if (n == -1) {
if (error == EINTR || errno == EWOULDBLOCK) {
return;
}
}
注意read和write的不同,非阻塞IO时,read一次没写完会返回写入的字符数,而write如果写不完则不写入任何数据,如果能写完则全部写完。
网络IO指责
检测IO
io 函数本身可以检测 io 的状态;但是只能检测一个 fd 对应的状态;
io 多路复用可以同时检测多个io的状态;
区别是:io函数可以检测具体状态;
io 多路复用只能检测出可读、可写、错误、断开等笼统的事件;
操作 IO
只能使用 io 函数来进行操作;分为两种操作方式:阻塞 io 和非阻塞 io;
阻塞 IO 和 非阻塞 IO
- 阻塞在网络线程;
- 连接的 fd 阻塞属性决定了 io 函数是否阻塞;
- 具体差异在:io 函数在数据未到达时是否立刻返回;
// 默认情况下,fd 是阻塞的,设置非阻塞的方法如下;
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);
IO 多路复用
epoll
结构与接口
红黑树 – 保存所有监听的fd和事件,已经一个指向用户空间的data(fd或ptr)
双向链表 – 保存监听到事件的fd,会被复制到epoll_wait提供的用户队列(数组)中
连接建立
//处理请求
struct epoll_event ev = {0};
ev.events |= EPOLLIN;
epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &ev);
...
clientfd = accept(...)
ev.events |= EPOLLIN;
epoll_ctl(efd, EPOLL_CTL_ADD, clientfd, &ev);
//处理非阻塞的链接
int connectfd = socket(AF_INET, SOCK_STREAM, 0);
connect(connectfd, (struct sockaddr *)&addr, sizeof(addr));
struct epoll_event ev;
ev.events |= EPOLLOUT;
epoll_ctl(efd, EPOLL_CTL_ADD, connectfd, &ev);
//当写事件触发时,连接建立成功
if (status == e_connecting && e->events & EPOLLOUT) {
status == e_connected;
}
连接断开
if (e->events & EPOLLRDHUP) close_read(fd); //读端关闭
if (e-e->events & EPOLLHUP) close(fd); //读写都关闭
数据到达
if (e->events & EPOLLIN) {
int n = read(fd, buf, sz);
if (n<0) {
if(errno == EINTR)
continue; //read again
if (errno == EWOULDBLOCK)
break; //read next time
close(fd);
} else if (n == 0) {
close_read(fd);
}
}
数据发送完毕
if (e->events & EPOLLOUT) {
int n = write(fd, buf, dz);
if (n == -1) {
if(errno == EINTR)
continue; //try again
if (errno == EWOULDBLOCK) {
struct epoll_event ev;
ev.events = EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
} else {
close(fd);
}
} else { // n > 0, success
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
}
}
reactor应用
The reactor design pattern is an event handling pattern (事件处理模式)for handling
service requests delivered concurrently to a service handler by one or more inputs(处理
一个或多个并发传递到服务端的服务请求). The service handler then demultiplexes the
incoming requests and dispatches them synchronously (同步)to the associated request
handlers.
单reactor
redis
适合业务逻辑简单的场景
redis 6.0 IO多线程优化
//---------工作线程处理--------//
int n = read(fd, buf, sz);
cmd, args = decode(buf, sz);
//--------主线程处理--------------//
out = business_logic(cmd, args);
//--------工作线程处理------------//
reply = encode(out);
write(fd, reply, sizeof(reply);
多reactor
多线程,多进程
memcache,nginx
每个线程或者进程一个reactor
如果数据交互小,基本不相干,则要比单reactor有效很多
参考资料
[1] 零声教育c/c++linux后台开发 2.2.2