本章目标:理解 socket 编程模型、用 POSIX API 写 TCP 客户端/服务器、认识 asio 等现代方案。 学完你会写出"本机可跑"的回显服务器和客户端。
TCP 模型(人话):
和每个客户端收发数据
术语:
socket 网络通信的"文件描述符"(像文件一样读写)
bind 把 socket 绑到端口
listen 开始监听(等别人来连)
accept 接受一个连接(阻塞等待)
connect 主动连接
recv/send 收发数据
// server.cpp —— 收到什么就回什么
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#include <print>
int main() {
int fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) { std::println("创建 socket 失败"); return 1; }
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定所有网卡
addr.sin_port = htons(8888); // 端口 8888
if (::bind(fd, (sockaddr*)&addr, sizeof(addr)) < 0) {
std::println("绑定端口失败(可能已被占用)");
return 1;
}
::listen(fd, 5); // 开始监听
std::println("服务器监听 8888 端口...");
while (true) {
int client = ::accept(fd, nullptr, nullptr); // 等一个连接
if (client < 0) break;
char buf[1024];
ssize_t n = ::recv(client, buf, sizeof(buf), 0);
if (n > 0) {
std::println("收到: {}", std::string_view(buf, n));
::send(client, buf, n, 0); // 原样回发
}
::close(client);
}
::close(fd);
}
编译:clang++ -std=c++26 server.cpp -o server (Termux 需要:pkg install 不需要额外包,socket 是系统调用)
// client.cpp
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <print>
int main() {
int fd = ::socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本机
addr.sin_port = htons(8888);
if (::connect(fd, (sockaddr*)&addr, sizeof(addr)) < 0) {
std::println("连接失败");
return 1;
}
const char* msg = "你好,服务器!";
::send(fd, msg, std::strlen(msg), 0);
char buf[1024];
ssize_t n = ::recv(fd, buf, sizeof(buf), 0);
std::println("服务器回复: {}", std::string_view(buf, n));
::close(fd);
}
要写循环直到收完(这引出了"消息边界"问题)
工程上现代方案:直接用 asio / Boost.Asio 或 C++26 网络 TS 草案实现——手写 POSIX 只用于理解原理。
while (true) {
int client = ::accept(fd, nullptr, nullptr);
if (client < 0) continue;
std::thread([client] {
char buf[1024];
while (true) {
ssize_t n = ::recv(client, buf, sizeof(buf), 0);
if (n <= 0) break; // 连接断开
::send(client, buf, n, 0);
}
::close(client);
}).detach(); // 线程自主运行
}
注意:detach 的线程访问 client(栈变量)——这里 client 是 accept 循环的局部变量,每个迭代是独立副本,安全。
测试文件实现一个简化版:
这是"端到端"测试,跑通说明你的网络栈理解正确。
练习题(配套测试:测试_第27章_网络编程.cpp)