1,IP地址和端口号
IP地址,好比哪一栋楼,告诉你在哪台机器;
port端口,好比哪一层住户,也就是机器上的哪个用户;
两个人聊天:
聊天要有 电话机 (socket)
聊天要有 听电话的方式 (阻塞/非阻塞、epoll 事件机制)
2,TCP、客户端、服务端
TCP:就像打电话,连接建立后,可以可靠、按顺序地说话,不会丢字。一个TCP连接需要:
本地IP + 本地端口 ——> 远端IP + 远端端口
客户端 (client):主动打电话的人(发起连接)。
服务端 (server):等电话的人(被动等待)。
3,Socket(套接字)
socket 就是一部“电话机”,你要么用它来打电话,要么用它来接电话。
在代码里,socket() 函数会给你一个 fd(文件描述符),就像一个电话句柄。
Linux 下万物皆文件,socket 也当作文件来处理,所以你能对它用 open/close, read/write, fcntl 等函数。
4,一个 TCP 服务端常见流程(框架)
服务端:
socket() // 买电话机
bind() // 把电话机安装到你家某个门牌号(IP+端口)
listen() // 开始等待电话
accept() // 有人打电话进来,接起
recv()/send() // 和对方说话
close() // 挂电话
客户端:
socket() // 买电话机
connect() // 拨号 (对方IP+端口)
send()/recv() // 说话
close() // 挂电话
5,
事件驱动 epoll
假如你有 不止一个电话,要同时听怎么办?
阻塞:你只能一心一意守着一个电话。recv() 会一直等到有数据,像电话响才接。
非阻塞:recv() 如果没数据,直接返回,不等。
轮询:傻傻的去问“有电话吗?有电话吗?”(效率低)。
epoll:Linux 提供的“秘书”,帮你同时监听很多电话(fd),只要有响铃(EPOLLIN/EPOLLOUT),秘书马上提醒你。这样你不用去问 socket 有没有数据,秘书会在有数据时告诉你。
相关操作:
epoll_create:雇个秘书。
epoll_ctl:告诉秘书你要盯哪些电话(fd)。
epoll_wait:让秘书帮你等,有电话就叫醒你。
那epoll_wait 等待的到底是啥?
epoll_wait(epoll_fd, events, MAX_EVENTS, -1)
本质上是:阻塞等候多个 fd 上的“状态变化”。
所谓的“event”,就是 文件描述符(fd) 上发生的某种情况。
常见的 event 类型:
EPOLLIN
:可读(socket 收到数据 / 有新连接 / eventfd 有新数)EPOLLOUT
:可写(socket 发送缓冲区空闲了)EPOLLERR
:错误…
所以:
client 有电话来了(发数据) → 对应
client_fd
上出现 EPOLLIN → epoll_wait 醒来,告诉你“这个 fd 有数据了”。有新连接 → 对应
server_fd
上出现 EPOLLIN → epoll_wait 醒来,告诉你“有新客户端要 accept”。你自己写了 eventfd → eventfd 上出现 EPOLLIN → epoll_wait 醒来,告诉你“eventfd 被触发了”。
结论:client 消息、新连接、eventfd 唤醒,本质都是“fd 上的状态变化(event)”,epoll_wait 统一帮你监控。
6,eventfd
是一种特殊的 fd,它本质上就是一个小“计数器”。可以通过write(event_fd, &u, sizeof(u))往里面写一个数(比如 1),它就像按了一个“按钮”,当这个计数器大于 0 时,这个 fd 就会变成 可读(EPOLLIN)。你用 read(event_fd, &u, sizeof(u))
把它读出来,计数器清零或减少。别人 epoll_wait 的时候会被唤醒,知道“有人按了按钮”。
所以 eventfd 的作用就是:让你自己也能制造一个“事件”,把它接入 epoll 体系。
比喻:你在办公室打瞌睡(epoll_wait 阻塞中)。你队友过来敲一下桌子(写 eventfd)。你马上醒来(epoll_wait 返回),知道有新任务。
6.1,eventfd 和 server 有啥关系?
严格说:
eventfd 和 server 没有业务上的关系
但是它是 server 内部机制的一部分,用来做 控制 / 通知 / 线程间通信
比如:
你的 server 正在 epoll_wait() 里阻塞着
你想优雅关闭 server → 你调用
stop()
→ 给event_fd
写一个数epoll_wait 被唤醒(就像有人打断秘书)
server 主循环读出 eventfd → 知道要退出
如果没有 eventfd,你就很难在 epoll_wait
阻塞时告诉它“该醒了”。
再也可以这样理解:
server_fd
就像一个“门口的门铃”。有新客户端来敲门,epoll 就会告诉主循环去accept()
。event_fd
就像“老板有急事按的紧急电话”。即使没有客户端消息,也能让epoll_wait()
马上醒来执行。比如,stop()
就是写1
到 eventfd → 让主循环退出。
类比:
socket 的“事件”是 客户端发数据
eventfd 的“事件”是 你自己写进去的数字
epoll 不管 fd 是 socket 还是 eventfd,反正都是“fd 上有事儿了”
6.2,eventfd 使用的 API
典型使用流程:
// 1. 创建 eventfd
int efd = eventfd(0, EFD_NONBLOCK);
// 2. 加入 epoll 监听
struct epoll_event ev{};
ev.events = EPOLLIN; // 等它可读
ev.data.fd = efd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, efd, &ev);
// 3. 在别的地方写入,触发事件
uint64_t u = 1;
write(efd, &u, sizeof(uint64_t));
// 4. epoll_wait 被唤醒后,你在主循环里处理
if (fd == efd)
{
uint64_t v;
read(efd, &v, sizeof(uint64_t));
// 把数读出来
std::cout << "eventfd triggered, value=" << v << std::endl;
}
👉 这样,eventfd 就和 server 的 epoll 主循环挂上了钩。
6.3,整体关系再捋一下
server_fd:监听新连接
client_fd:收/发客户端消息
eventfd:给自己用的“内部通知器”,比如唤醒 server 停止、线程通信等
epoll:秘书,统一盯着所有 fd 的状态变化(server_fd/client_fd/eventfd)
6.4,类比总结
socket 就像 客户电话,客户打来你就得接。
eventfd 就像 老板的内线电话,不是客户,但可以随时打断你。
epoll_wait 就像 前台秘书,她同时帮你盯着客户电话和老板内线,哪个响了都立刻告诉你。
server 主循环:秘书报告后,你去决定是接客户电话还是听老板指示。
7,setsockopt, fcntl, keepalive
setsockopt:给 socket 调调参数,比如:
开启 TCP keepalive(让系统定期发心跳包,防止对方死了你还蒙在鼓里)。
设置收发缓冲区大小。
fcntl:修改 fd 的属性,比如把 socket 设置成 非阻塞(打电话时不必傻等,可以先去干别的事)。
8. recv / send / open / close
recv:接收数据(别人说的话)。
send:发送数据(你说话)。
open/close:打开/关闭一个“文件”或者“设备”,socket 也走这套接口。
9. 整个常见框架(Server + Clients)
把这些拼起来,一个典型的 Linux 网络编程模型:
Server 进程:
socket → bind → listen
epoll_create(雇秘书)
epoll_ctl(秘书盯着监听 socket + 已连接的 socket)
epoll_wait(秘书等电话)
accept() 接来电,recv/send 聊天
Client 进程:
socket → connect → send/recv
挂掉时 close
多线程/多进程:
可以一个线程专门负责 epoll_wait
收到数据后丢给 worker 线程处理
10,运行流程
server 启动 →
server_fd
加入 epoll新客户端连进来 →
server_fd
上有事件 →accept()
→ 得到client_fd
客户端发消息 →
client_fd
上有事件 →recv()
→ 丢给线程池处理 → 回包如果主程序要退出 →
stop()
往event_fd
写1
→ epoll_wait 被唤醒 → runLoop 退出