CODE
示例代码
各篇教程里用到的全部代码,都在 Linux 下编译运行过,教程里贴出的输出就是实际结果。按教程分组,点文件名展开查看,也可以直接下载。
任务 3:现代 C++ 五个小例子(RAII、智能指针、移动、lambda)
5 个文件 · 出自 这篇教程
raii.cpp 49 行
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
#include <stdexcept>
// 不用 RAII:每条退出路径都要记得 close
void withoutRaii(bool fail) {
int fd = ::open("/etc/hostname", O_RDONLY);
std::cout << "opened fd " << fd << std::endl;
if (fail) {
throw std::runtime_error("something went wrong"); // 忘了 close,fd 泄漏
}
::close(fd);
std::cout << "closed fd " << fd << std::endl;
}
// 用 RAII:构造时拿到 fd,析构时一定 close
class File {
public:
explicit File(const char* path) : fd_(::open(path, O_RDONLY)) {
std::cout << "File opened fd " << fd_ << std::endl;
}
~File() {
if (fd_ >= 0) ::close(fd_);
std::cout << "File closed fd " << fd_ << std::endl;
}
File(const File&) = delete; // 禁止拷贝:否则两个对象会 close 同一个 fd
File& operator=(const File&) = delete;
int fd() const { return fd_; }
private:
int fd_;
};
void withRaii(bool fail) {
File f("/etc/hostname");
if (fail) {
throw std::runtime_error("something went wrong"); // f 的析构函数照样会执行
}
std::cout << "using fd " << f.fd() << std::endl;
} // 离开作用域,f 析构
int main() {
try { withoutRaii(true); } catch (const std::exception& e) { std::cout << "caught: " << e.what() << std::endl; }
try { withRaii(true); } catch (const std::exception& e) { std::cout << "caught: " << e.what() << std::endl; }
withRaii(false);
return 0;
}
unique.cpp 28 行
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct Connection {
explicit Connection(int id) : id(id) { std::cout << "Connection " << id << " created" << std::endl; }
~Connection() { std::cout << "Connection " << id << " destroyed" << std::endl; }
int id;
};
int main() {
{
std::unique_ptr<Connection> a = std::make_unique<Connection>(1);
std::cout << "a->id = " << a->id << std::endl;
// std::unique_ptr<Connection> b = a; // 编译错误:unique_ptr 不能拷贝
std::unique_ptr<Connection> b = std::move(a); // 可以移动:所有权从 a 转给 b
std::cout << "a is " << (a ? "not null" : "null") << ", b->id = " << b->id << std::endl;
} // b 离开作用域,Connection 1 被自动 delete
std::cout << "--- vector of unique_ptr ---" << std::endl;
std::vector<std::unique_ptr<Connection>> conns;
conns.push_back(std::make_unique<Connection>(2));
conns.push_back(std::make_unique<Connection>(3));
conns.erase(conns.begin()); // 从容器里删掉,就自动销毁
std::cout << "conns.size() = " << conns.size() << std::endl;
std::cout << "--- end of main ---" << std::endl;
return 0;
}
shared.cpp 43 行
#include <iostream>
#include <memory>
struct Node {
explicit Node(const char* name) : name(name) { std::cout << name << " created" << std::endl; }
~Node() { std::cout << name << " destroyed" << std::endl; }
const char* name;
std::shared_ptr<Node> next; // 强引用
std::weak_ptr<Node> prev; // 弱引用:不增加计数
};
int main() {
std::cout << "--- shared_ptr 计数 ---" << std::endl;
{
auto a = std::make_shared<Node>("A");
std::cout << "use_count = " << a.use_count() << std::endl;
{
auto b = a; // 拷贝:计数 +1
std::cout << "use_count = " << a.use_count() << std::endl;
} // b 析构:计数 -1
std::cout << "use_count = " << a.use_count() << std::endl;
} // a 析构:计数变成 0,对象被销毁
std::cout << "--- 循环引用:两个 shared_ptr 互相指着 ---" << std::endl;
{
auto x = std::make_shared<Node>("X");
auto y = std::make_shared<Node>("Y");
x->next = y;
y->next = x; // 循环了
} // 看输出:X 和 Y 都没有被销毁,内存泄漏
std::cout << "--- 用 weak_ptr 打破循环 ---" << std::endl;
{
auto p = std::make_shared<Node>("P");
auto q = std::make_shared<Node>("Q");
p->next = q;
q->prev = p; // 反向用 weak_ptr
if (auto locked = q->prev.lock()) { // 使用前先 lock,拿到一个临时的 shared_ptr
std::cout << "Q's prev is " << locked->name << std::endl;
}
}
std::cout << "--- end of main ---" << std::endl;
return 0;
}
move.cpp 53 行
#include <cstring>
#include <iostream>
#include <utility>
#include <vector>
// 一个自己管理内存的缓冲区,演示拷贝和移动的区别
class Buffer {
public:
explicit Buffer(size_t size) : data_(new char[size]), size_(size) {
std::cout << "construct " << size_ << " bytes" << std::endl;
}
~Buffer() { delete[] data_; }
// 拷贝构造:新开一块内存,把内容复制过来(深拷贝,慢)
Buffer(const Buffer& other) : data_(new char[other.size_]), size_(other.size_) {
std::memcpy(data_, other.data_, size_);
std::cout << "copy " << size_ << " bytes" << std::endl;
}
// 移动构造:直接把对方的指针拿过来,再把对方置空(快,不复制数据)
Buffer(Buffer&& other) noexcept : data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
std::cout << "move " << size_ << " bytes" << std::endl;
}
Buffer& operator=(const Buffer&) = delete;
Buffer& operator=(Buffer&&) = delete;
size_t size() const { return size_; }
private:
char* data_;
size_t size_;
};
Buffer makeBuffer() {
Buffer b(1024);
return b; // 编译器会直接在调用方构造(返回值优化),连移动都省了
}
int main() {
Buffer a(1024 * 1024);
Buffer b = a; // a 还要用,只能拷贝
Buffer c = std::move(a); // a 不再用了,把它的内存“搬”给 c
std::cout << "a.size() = " << a.size() << ", c.size() = " << c.size() << std::endl;
Buffer d = makeBuffer();
std::cout << "--- vector ---" << std::endl;
std::vector<Buffer> v;
v.reserve(2);
v.push_back(Buffer(16)); // 临时对象是右值,自动走移动
v.emplace_back(32); // 直接在 vector 里构造,连移动都没有
return 0;
}
lambda.cpp 64 行
#include <functional>
#include <iostream>
#include <map>
#include <string>
#include <vector>
// 一个“按钮”:被点击时调用事先设置好的回调。网络库里的 Channel 就是这个思路
class Button {
public:
using Callback = std::function<void(int)>;
void setOnClick(Callback cb) { onClick_ = std::move(cb); }
void click(int times) {
if (onClick_) onClick_(times);
}
private:
Callback onClick_;
};
int add(int a, int b) { return a + b; }
int main() {
// 1. 最简单的 lambda:[捕获](参数) { 函数体 }
auto square = [](int x) { return x * x; };
std::cout << "square(5) = " << square(5) << std::endl;
// 2. 按值捕获 [=] 和按引用捕获 [&]
int count = 0;
auto byValue = [count]() { return count + 1; }; // 拷了一份 count,之后外面怎么改都不影响
auto byRef = [&count]() { ++count; }; // 引用外面的 count,能修改它
byRef();
byRef();
std::cout << "count = " << count << ", byValue() = " << byValue() << std::endl;
// 3. std::function 能装下任何“可调用的东西”:普通函数、lambda
std::function<int(int, int)> op = add;
std::cout << "op(2, 3) = " << op(2, 3) << std::endl;
op = [](int a, int b) { return a * b; };
std::cout << "op(2, 3) = " << op(2, 3) << std::endl;
// 4. 回调:把“发生某事时做什么”交给别人保存,事件发生时由别人来调用
Button button;
int total = 0;
button.setOnClick([&total](int times) {
total += times;
std::cout << "clicked " << times << " times, total " << total << std::endl;
});
button.click(1);
button.click(3);
// 5. 用 map 存“命令名 -> 处理函数”,这就是阶段 2 命令分发的雏形
std::map<std::string, std::function<std::string(const std::string&)>> commands;
commands["PING"] = [](const std::string&) { return std::string("PONG"); };
commands["ECHO"] = [](const std::string& arg) { return arg; };
for (std::string name : {"PING", "ECHO", "GET"}) {
auto it = commands.find(name);
if (it == commands.end()) {
std::cout << name << " -> ERR unknown command" << std::endl;
} else {
std::cout << name << " -> " << it->second("hello") << std::endl;
}
}
return 0;
}
任务 4:亲眼看到三次握手、四次挥手和端口
2 个文件 · 出自 这篇教程
lazy_server.py 12 行
# 一个“偷懒”的服务器:接受连接后既不读也不关,用来观察 CLOSE_WAIT
import socket
import time
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("127.0.0.1", 9003))
s.listen()
print("listening on 9003")
conn, addr = s.accept()
print("accepted", addr, "- now sleeping 60s without closing")
time.sleep(60)
quick_client.py 9 行
# 连上 9003,发 3 个字节,马上关闭
import socket
import time
c = socket.create_connection(("127.0.0.1", 9003))
c.sendall(b"bye")
c.close()
print("client closed")
time.sleep(30) # 先别退出,方便用 ss 观察
任务 5:跑通阻塞版 echo server,亲眼看到它的问题
1 个文件 · 出自 这篇教程
blocking_echo.cpp 41 行
// 第一课:最简单的阻塞 echo server,一次只能服务一个客户端
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstdio>
#include <cstring>
int main() {
// 1. socket:向系统要一个“插座”(文件描述符 fd)
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 2. bind:把插座绑定到 本机任意IP:8888
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8888);
if (bind(listen_fd, (sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
// 3. listen:开始营业,等客人上门
listen(listen_fd, 128);
printf("listening on 8888\n");
while (true) {
// 4. accept:接待一个客人,拿到和他专属的 conn_fd(没人来就一直卡在这)
int conn_fd = accept(listen_fd, nullptr, nullptr);
printf("new client fd=%d\n", conn_fd);
char buf[1024];
while (true) {
// 5. read:等客人说话(不说话就一直卡在这)
ssize_t n = read(conn_fd, buf, sizeof(buf));
if (n <= 0) break; // 0 = 客人挂断,<0 = 出错
write(conn_fd, buf, n); // 6. write:原样说回去
}
close(conn_fd);
printf("client fd=%d closed\n", conn_fd);
}
}
任务 6:跑通 epoll 版 echo server,500 个连接全部回显
3 个文件 · 出自 这篇教程
nonblock_demo.cpp 33 行
// 演示非阻塞 read:没数据时立刻返回 -1,errno 是 EAGAIN;有数据时正常读到。
#include <fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
int main() {
// socketpair 创建一对互相连通的 socket:往 sv[0] 写的,能从 sv[1] 读到
int sv[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
// 把 sv[1] 设成非阻塞
int flags = fcntl(sv[1], F_GETFL);
fcntl(sv[1], F_SETFL, flags | O_NONBLOCK);
char buf[100];
ssize_t n = read(sv[1], buf, sizeof(buf)); // 还没人写,阻塞的话会永远卡在这里
printf("1st read returned %zd, errno = %d (%s)\n", n, errno, strerror(errno));
printf("EAGAIN = %d, so this means: no data right now, try again later\n", EAGAIN);
write(sv[0], "hello", 5);
n = read(sv[1], buf, sizeof(buf));
printf("2nd read returned %zd: %.*s\n", n, static_cast<int>(n), buf);
close(sv[0]); // 关闭另一端
n = read(sv[1], buf, sizeof(buf));
printf("3rd read returned %zd (peer closed)\n", n);
close(sv[1]);
return 0;
}
epoll_echo.cpp 70 行
// 第二课:epoll 版 echo server,单线程同时服务成千上万个客户端
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstdio>
// 把 fd 设成非阻塞:read/accept 没数据时立刻返回,而不是卡住
static void set_nonblock(int fd) {
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}
int main() {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8888);
if (bind(listen_fd, (sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
listen(listen_fd, 1024);
set_nonblock(listen_fd);
// 1. 创建 epoll 实例:一个“前台”,帮你盯住所有 fd
int epfd = epoll_create1(0);
// 2. 把监听 fd 交给前台盯着:有新连接时通知我(EPOLLIN = 可读)
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);
printf("listening on 8888 (epoll)\n");
epoll_event events[1024];
char buf[4096];
while (true) {
// 3. epoll_wait:没事就睡,一旦有 fd 就绪就醒来,返回就绪的个数
int n = epoll_wait(epfd, events, 1024, -1);
for (int i = 0; i < n; ++i) {
int fd = events[i].data.fd;
if (fd == listen_fd) {
// 4a. 监听 fd 就绪 = 有新客人:把排队的连接全部接进来
while (true) {
int conn_fd = accept(listen_fd, nullptr, nullptr);
if (conn_fd < 0) break; // EAGAIN:已经接完了
set_nonblock(conn_fd);
epoll_event cev{};
cev.events = EPOLLIN;
cev.data.fd = conn_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &cev);
}
} else {
// 4b. 普通连接就绪 = 客人说话了:读出来再写回去
ssize_t r = read(fd, buf, sizeof(buf));
if (r > 0) {
write(fd, buf, r); // 简化:假设一次写得完,阶段 1 用 Buffer 解决
} else if (r == 0 || errno != EAGAIN) {
// 客人挂断或出错:从 epoll 里移除并关闭
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);
close(fd);
}
}
}
}
}
test_clients.py 27 行
# 测试 echo server:先同时建立 N 个连接,再挨个发一条消息,检查能不能收到原样的回显。
# 用法:python3 test_clients.py 名字 连接数,例如 python3 test_clients.py epoll 500
import socket
import sys
name = sys.argv[1] # 只是打印时用的名字,比如 blocking 或 epoll
count = int(sys.argv[2]) # 要建立多少个连接
# 第一步:一口气建立 count 个连接,都先不发数据,模拟“很多人连着但不说话”
conns = []
for i in range(count):
s = socket.create_connection(("127.0.0.1", 8888))
s.settimeout(1) # recv 最多等 1 秒,等不到就算失败
conns.append(s)
# 第二步:挨个发一条不同的消息,看能不能收到一模一样的回显
ok = 0
for i, s in enumerate(conns):
msg = f"hi{i}".encode()
s.sendall(msg)
try:
if s.recv(100) == msg:
ok += 1
except socket.timeout:
pass # 1 秒内没收到回显
print(name, "echo ok:", ok, "/", count)
任务 7:课后练习(打印客户端地址、群聊、输出缓冲区)
4 个文件 · 出自 这篇教程
echo_addr.cpp 78 行
// 练习 1:在 epoll echo 的基础上,打印每个客户端的 IP 和端口
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <map>
#include <string>
static void set_nonblock(int fd) {
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}
// 把 sockaddr_in 转成 "127.0.0.1:41844" 这样的字符串
static std::string to_ip_port(const sockaddr_in& a) {
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &a.sin_addr, ip, sizeof(ip)); // 二进制 IP -> 点分十进制字符串
return std::string(ip) + ":" + std::to_string(ntohs(a.sin_port)); // 端口转回主机字节序
}
int main() {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8888);
if (bind(listen_fd, (sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
listen(listen_fd, 1024);
set_nonblock(listen_fd);
int epfd = epoll_create1(0);
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);
printf("listening on 8888\n");
std::map<int, std::string> peers; // fd -> "ip:port",断开时还要打印
epoll_event events[1024];
char buf[4096];
while (true) {
int n = epoll_wait(epfd, events, 1024, -1);
for (int i = 0; i < n; ++i) {
int fd = events[i].data.fd;
if (fd == listen_fd) {
while (true) {
sockaddr_in peer{};
socklen_t len = sizeof(peer); // 必须先设成结构体的大小,accept 会改写它
int conn_fd = accept(listen_fd, (sockaddr*)&peer, &len);
if (conn_fd < 0) break;
set_nonblock(conn_fd);
epoll_event cev{};
cev.events = EPOLLIN;
cev.data.fd = conn_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &cev);
peers[conn_fd] = to_ip_port(peer);
printf("new client %s fd=%d\n", peers[conn_fd].c_str(), conn_fd);
}
} else {
ssize_t r = read(fd, buf, sizeof(buf));
if (r > 0) {
write(fd, buf, r);
} else if (r == 0 || errno != EAGAIN) {
printf("client %s fd=%d closed\n", peers[fd].c_str(), fd);
peers.erase(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);
close(fd);
}
}
}
}
}
chat_server.cpp 73 行
// 练习 2:群聊服务器。任何一个客户端发的消息,转发给其他所有客户端
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <set>
#include <string>
static void set_nonblock(int fd) {
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}
int main() {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8888);
if (bind(listen_fd, (sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
listen(listen_fd, 1024);
set_nonblock(listen_fd);
int epfd = epoll_create1(0);
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);
printf("chat server listening on 8888\n");
std::set<int> clients; // 当前在线的所有连接
epoll_event events[1024];
char buf[4096];
while (true) {
int n = epoll_wait(epfd, events, 1024, -1);
for (int i = 0; i < n; ++i) {
int fd = events[i].data.fd;
if (fd == listen_fd) {
while (true) {
int conn_fd = accept(listen_fd, nullptr, nullptr);
if (conn_fd < 0) break;
set_nonblock(conn_fd);
epoll_event cev{};
cev.events = EPOLLIN;
cev.data.fd = conn_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &cev);
clients.insert(conn_fd);
printf("user %d joined, %zu online\n", conn_fd, clients.size());
}
} else {
ssize_t r = read(fd, buf, sizeof(buf));
if (r > 0) {
// 在消息前面加上发送者的编号,再发给除了自己以外的所有人
std::string msg = "[user " + std::to_string(fd) + "] " + std::string(buf, r);
for (int other : clients) {
if (other != fd) write(other, msg.data(), msg.size());
}
} else if (r == 0 || errno != EAGAIN) {
clients.erase(fd);
printf("user %d left, %zu online\n", fd, clients.size());
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);
close(fd);
}
}
}
}
}
big_test.py 30 行
# 测试“写不完”:先发 20 MB 数据,故意晚 1 秒才开始读回显,看最后收回来的是否完整
import socket
import threading
import time
SIZE = 20 * 1024 * 1024
data = bytes(i % 251 for i in range(SIZE)) # 有规律的内容,方便比对
s = socket.create_connection(("127.0.0.1", 8888))
def sender():
s.sendall(data)
t = threading.Thread(target=sender)
t.start()
time.sleep(1) # 这 1 秒里只发不收:服务器写回来的数据会把我们的接收缓冲区塞满
received = bytearray()
s.settimeout(2)
try:
while len(received) < SIZE:
chunk = s.recv(1 << 20)
if not chunk:
break
received += chunk
except socket.timeout:
pass
t.join(timeout=1)
print("sent", SIZE, "bytes, got back", len(received), "bytes,",
"OK" if bytes(received) == data else "DATA LOST")
echo_buffered.cpp 112 行
// 练习 4:给每个连接加输出缓冲区,解决 write 写不完的问题
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <map>
#include <string>
static void set_nonblock(int fd) {
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}
static int epfd;
static std::map<int, std::string> outbuf; // 每个连接还没发出去的数据
// 修改一个连接关心的事件:要不要关心 EPOLLOUT
static void watch_write(int fd, bool on) {
epoll_event ev{};
ev.events = on ? (EPOLLIN | EPOLLOUT) : EPOLLIN;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}
static void close_conn(int fd) {
outbuf.erase(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);
close(fd);
}
// 尽量把输出缓冲区里的数据写出去;写不完的留在缓冲区里。出错返回 false
static bool flush(int fd) {
std::string& out = outbuf[fd];
while (!out.empty()) {
ssize_t w = write(fd, out.data(), out.size());
if (w > 0) {
out.erase(0, w); // 删掉已经写出去的部分
} else if (w < 0 && errno == EAGAIN) {
break; // 内核发送缓冲区满了,等 EPOLLOUT 再写
} else {
return false; // 真的出错了(比如对方已经关闭)
}
}
return true;
}
int main() {
signal(SIGPIPE, SIG_IGN); // 往已关闭的连接写数据时,不要让 SIGPIPE 信号杀死进程
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8888);
if (bind(listen_fd, (sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
listen(listen_fd, 1024);
set_nonblock(listen_fd);
epfd = epoll_create1(0);
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);
printf("listening on 8888 (buffered)\n");
epoll_event events[1024];
char buf[65536];
while (true) {
int n = epoll_wait(epfd, events, 1024, -1);
for (int i = 0; i < n; ++i) {
int fd = events[i].data.fd;
if (fd == listen_fd) {
while (true) {
int conn_fd = accept(listen_fd, nullptr, nullptr);
if (conn_fd < 0) break;
set_nonblock(conn_fd);
epoll_event cev{};
cev.events = EPOLLIN;
cev.data.fd = conn_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &cev);
outbuf[conn_fd]; // 创建一个空的输出缓冲区
}
continue;
}
uint32_t e = events[i].events;
if (e & EPOLLIN) {
ssize_t r = read(fd, buf, sizeof(buf));
if (r > 0) {
outbuf[fd].append(buf, r); // 先放进输出缓冲区
if (!flush(fd)) { close_conn(fd); continue; }
if (!outbuf[fd].empty()) watch_write(fd, true); // 没写完,关心可写事件
} else if (r == 0 || errno != EAGAIN) {
close_conn(fd);
continue;
}
}
if (e & EPOLLOUT) {
// 内核发送缓冲区又有空间了,接着写
if (!flush(fd)) { close_conn(fd); continue; }
if (outbuf[fd].empty()) watch_write(fd, false); // 写完了,不再关心可写
}
}
}
}