Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

socket_study

一个跨 Windows 和 Linux 的 C++ Socket 网络编程学习仓库。项目从原始 TCP/UDP API 开始,逐步演示阻塞与非阻塞连接、UDP 广播、Linux epoll、多线程服务端、跨平台 xsocket 动态库、简化 HTTP 服务和 UDP Syslog 监听。

每个目录都是相对独立的实验。Windows 侧使用 Visual Studio 和 Winsock,Linux 侧使用 Makefile、POSIX Socket、pthread 和 epoll。

你可以学到什么

  • TCP 服务端的 socket -> bind -> listen -> accept -> recv/send 流程。
  • TCP 客户端的 socket -> connect -> send/recv 流程。
  • UDP 为什么不需要建立连接,以及 sendto/recvfrom 如何携带对端地址。
  • 主机字节序与网络字节序为什么需要 htons/ntohs
  • 阻塞 Socket、非阻塞 Socket 和 select() 超时连接。
  • 一个连接一个线程的模型有什么优点和扩展限制。
  • epoll 的注册、等待、水平触发和边缘触发。
  • 如何把平台差异封装到 XTcpXUdp
  • HTTP 请求行、响应头、静态文件发送的基本流程。
  • Syslog UDP 监听和正则提取登录失败信息。

仓库结构

socket_study/
├── bin/       Windows 可执行文件和 xsocket.dll
├── doc/       项目文档
├── lib/       Windows xsocket 导入库
├── src/
│   ├── testsocket/   独立的 XTcp 与多线程 TCP 服务端
│   ├── tcpserver/    依赖 xsocket 的 TCP 服务端
│   ├── tcpclient/    依赖 xsocket 的 TCP 客户端
│   ├── udpserver/    UDP 服务端
│   ├── udpclient/    UDP 单播客户端
│   ├── budpclient/   UDP 广播客户端
│   ├── epoll/        Linux epoll 服务端
│   ├── xsocket/      XTcp、XUdp 跨平台动态库
│   ├── xhttp/        简化 HTTP 文件服务
│   └── xsyslog/      Syslog UDP 监听
└── README.md

构建环境

Windows

当前 Visual Studio 工程主要使用:

  • C++20
  • MSVC 平台工具集 v145
  • x64
  • Winsock 系统库 Ws2_32.lib
  • xsocket.lib 和运行时 xsocket.dll

独立示例可以直接打开对应解决方案:

src/testsocket/testsocket.slnx
src/udpserver/udpserver.slnx
src/udpclient/udpclient.slnx
src/xsyslog/xsyslog.slnx

依赖 xsocket 的工程建议按以下顺序构建:

  1. 打开 src/xsocket/xsocket.slnx
  2. 构建 Debug|x64 或需要的配置。
  3. 确认 xsocket.lib 位于 lib/xsocket.dll 位于运行目录或 bin/
  4. 再构建 tcpservertcpclientxhttp 等上层工程。

其他解决方案:

src/tcpclient/tcpclient.slnx
src/epoll/epoll.sln
src/xhttp/tcpserver.sln

epoll 的核心接口只存在于 Linux。目录中的 Visual Studio 工程文件不代表 sys/epoll.h 能在 Windows 原生编译。

Linux

多个目录提供 makefile。常见依赖:

  • g++
  • pthread
  • 标准 Linux Socket 头文件
  • epoll

先构建共享库:

cd src/xsocket
make

再构建依赖它的程序,例如:

cd ../tcpserver
make

部分现有 Makefile 会把:

libxsocket.so 复制到 /usr/lib
可执行文件复制到 /usr/bin

这些步骤会修改系统目录,通常需要管理员权限。运行前先阅读对应 Makefile。如果只想在仓库内测试,可以移除安装用的 cp 命令,并临时指定:

export LD_LIBRARY_PATH=/path/to/socket_study/src/xsocket:$LD_LIBRARY_PATH

示例总览

示例 默认行为 关键接口 注意点
testsocket 8080 端口多线程 TCP 服务端 socket/bind/listen/accept 每个客户端创建一个分离线程
tcpserver 8080 端口回显式服务端 XTcp::Bind/Accept/Recv/Send 收到 quit 后回复并关闭当前连接
tcpclient 连接参数 IP 的 8080 端口 SetBlock/Connect/Send/Recv 源码默认 IP 是 192.168.60.128
udpserver 8080 接收一个 UDP 数据报并回复 nihao recvfrom/sendto UDP 不需要 listen()
udpclient 127.0.0.1:8080 发送 hello sendto/recvfrom 先启动 UDP 服务端
budpclient 向广播地址发送 hello SO_BROADCAST 网络和防火墙必须允许广播
epoll epoll ET 模式 HTTP 单字符响应 epoll_create/ctl/wait 仅 Linux,非阻塞 Socket
xsocket 导出 XTcpXUdp Winsock/POSIX 平台封装 其他工程依赖其库文件
xhttp 8080 端口提供 www/ 文件 请求解析、文件响应、多线程 仅为教学实现,安全边界很弱
xsyslog UDP 514 端口接收 Syslog XUdpstd::regex Linux 低端口可能需要额外权限

Socket 基础

TCP 服务端

socket(AF_INET, SOCK_STREAM)
        |
        v
bind(本地地址和端口)
        |
        v
listen(连接队列长度)
        |
        v
accept() -> 新的客户端 Socket
        |
        v
recv()/send()
        |
        v
close()/closesocket()

监听 Socket 只负责接受连接。每次 accept() 返回的客户端 Socket 才负责与一个具体对端通信。

TCP 客户端

socket(AF_INET, SOCK_STREAM)
        |
        v
connect(服务端 IP 和端口)
        |
        v
send()/recv()
        |
        v
close()/closesocket()

TCP 是字节流协议。一次 send() 不保证对端用一次 recv() 收到完全相同的边界。代码必须根据协议长度、分隔符或固定结构处理粘包和半包。

UDP

服务端: socket -> bind -> recvfrom -> sendto
客户端: socket -> sendto -> recvfrom

UDP 保留数据报边界,但不保证到达、不保证顺序,也不会自动重传。单个数据报过大还可能发生 IP 分片。

地址和字节序

IPv4 地址结构:

sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");

常见转换:

函数 含义
htons 16 位主机字节序转网络字节序
ntohs 16 位网络字节序转主机字节序
htonl 32 位主机字节序转网络字节序
ntohl 32 位网络字节序转主机字节序
inet_addr 点分十进制 IPv4 字符串转二进制地址
inet_ntoa IPv4 地址转字符串,旧接口
inet_pton/inet_ntop 更通用的现代地址转换接口

端口必须使用 htons(),否则在不同字节序机器上会得到错误端口值。

testsocket 与 tcpserver

testsocket 内置一份简化 XTcp,用于先理解封装本身。主线程持续 Accept(),每个连接创建一个 TcpThread

主线程 accept
  |
  v
new TcpThread
  |
  v
std::thread(&TcpThread::Main, object)
  |
  v
detach

客户端线程循环接收:

  • 普通消息:回复 ok
  • 包含 quit:回复 quit success!,退出循环。
  • recv() 返回 0 或负值:认为连接关闭或出错。

线程函数最后执行 delete this。这要求对象只能由 new 创建,并且之后没有其他代码再访问它。这种自删除模式容易出错,通用代码更适合用 shared_ptr 或由连接管理器统一回收。

一个连接一个线程的模型便于学习,但大量连接会带来线程栈、上下文切换和调度成本。

tcpclient 与非阻塞连接

当前客户端:

  1. 从命令行读取 IP;没有参数时使用 192.168.60.128
  2. 创建 TCP Socket。
  3. 设置非阻塞。
  4. 调用带 1000ms 超时的 XTcp::Connect()
  5. 发送字符串 client
  6. 阻塞接收服务端回复。

运行示例:

# 终端 1
.\tcpserver.exe 8080

# 终端 2
.\tcpclient.exe 127.0.0.1

XTcp::Connect()connect() 未立即成功时使用 select() 等待可写。完整实现还应在 select() 返回后调用 getsockopt(SO_ERROR),确认连接真的成功;“可写”也可能表示连接失败已经完成。当前封装省略了这一步。

timeval.tv_usec 必须小于 1,000,000。当前代码直接使用 timeoutms * 1000,如果超时参数大于等于 1000ms,严格实现应拆分成秒和微秒。

UDP 单播与广播

单播

先启动:

.\udpserver.exe 8080

再运行:

.\udpclient.exe 8080

客户端向 127.0.0.1:8080 发送 hello。服务端通过 recvfrom() 同时得到消息和客户端地址,然后回复 nihao

udpserver 中调用了 listen(sock, 10),但 listen() 只对面向连接的流式 Socket 有意义,UDP 不需要这一步。

广播

budpclient 先开启:

setsockopt(sock, SOL_SOCKET, SO_BROADCAST, ...);

然后把数据发送到 INADDR_BROADCAST。广播能否到达取决于网卡、子网、路由器、防火墙和系统策略。很多网络会限制全局广播,更常见的是使用具体子网广播地址。

epoll 服务端

epoll 示例使用:

epoll_create(...)
epoll_ctl(..., EPOLL_CTL_ADD, ...)
epoll_wait(...)

监听 Socket 和客户端 Socket 都注册为:

EPOLLIN | EPOLLET

EPOLLET 是边缘触发。事件只在状态发生变化时通知,因此配套 Socket 必须非阻塞,并在一次回调中持续 accept()recv(),直到返回 EAGAIN/EWOULDBLOCK。否则缓冲区里剩余的数据可能不会再次产生通知。

当前示例对监听 Socket 使用循环 Accept(),对客户端采取“一次读取、一次回复、立即关闭”的简化 HTTP 处理:

HTTP/1.1 200 OK
Content-Length: 1

X

它不是通用 HTTP 服务端,只用于观察 epoll 注册、等待和事件分发。

xsocket 封装

XTcp

方法 作用
CreateSocket() 创建 IPv4 TCP Socket
Bind(port) 绑定本地端口并开始监听
Accept() 接受连接并返回新的 XTcp
Connect(ip, port, timeout) 非阻塞发起连接并等待
SetBlock(bool) Windows 使用 ioctlsocket,Linux 使用 fcntl
Recv(buf, size) 接收字节
Send(buf, size) 循环发送,尽量处理短写
Close() 使用平台对应 API 关闭 Socket

平台差异:

Windows                 Linux
WSAStartup              无需对应初始化
closesocket             close
ioctlsocket             fcntl
WSAGetLastError         errno
Ws2_32.lib              系统 Socket 接口

当前 XTcp 没有自定义析构函数自动关闭句柄,也允许默认复制。复制只是复制同一个 Socket 数值,不会复制内核连接;多个对象关闭同一句柄可能产生问题。使用时需要明确谁拥有 Socket。

XUdp

XUdp 封装创建、绑定、接收和关闭。第一次 Recv() 时动态创建地址结构,Close() 负责释放。当前析构函数为空,因此调用者仍应显式执行 Close()

xhttp 执行流程

XHttpServer::Start()

  1. 创建并绑定 TCP 监听 Socket。
  2. 启动分离线程执行 XHttpServer::Main()
  3. 主循环持续接受客户端。
  4. 每个连接创建 XHttpClient 和独立线程。

XHttpClient::Main()

  1. Recv() 读取请求字节。
  2. XHttpResponse::SetRequest() 解析请求第一行。
  3. 只接受 GET
  4. / 映射到 /index.html
  5. www 目录打开文件。
  6. 生成响应头。
  7. 循环读取文件并发送。

运行时工作目录应包含:

www/
└── index.html

启动:

.\tcpserver.exe 8080

浏览器或 curl 访问:

curl http://127.0.0.1:8080/

PHP 演示

请求 .php 时,代码会拼接 php-cgi 命令,通过 system() 生成临时 HTML 文件。这要求 php-cgi 可执行文件可被找到。

这一实现直接把 URL 路径和查询参数拼入文件路径及系统命令,没有可靠转义,存在目录穿越和命令注入风险。只能在隔离的本地测试目录中学习,不能暴露到不可信网络。

其他教学限制:

  • 一次 Recv() 不保证收到完整 HTTP 请求。
  • 没有完整解析请求头、Content-Length、keep-alive 和分块传输。
  • MIME 类型基本固定。
  • 文件句柄生命周期和错误响应不完整。
  • Stop() 只设置标记,但 Accept() 可能仍在阻塞。
  • 分离线程依赖服务端对象持续存活。

xsyslog

xsyslog 在 UDP 514 端口接收日志,并用正则匹配:

Failed password for 用户 from IP port 端口

匹配成功后打印警告。Linux 上绑定小于 1024 的端口通常需要 root 或 CAP_NET_BIND_SERVICE。学习时也可以把源码端口改成高端口,例如 5514。

测试数据可以由另一台机器或本机 UDP 工具发送。UDP Syslog 本身不保证消息到达,也没有内建认证。

常见问题

bind failed

常见原因:

  • 端口已经被其他进程占用。
  • 程序刚退出,旧连接仍影响绑定。
  • 绑定低端口但权限不足。
  • 地址或端口字节序错误。
  • 防火墙或系统安全策略限制。

客户端一直卡在 recv()

阻塞 Socket 会等待数据。检查服务端是否已经启动、IP/端口是否一致、服务端是否真的发送回复,以及协议是否在等待更多输入。

能连接但收不到完整消息

TCP 没有消息边界。应循环接收并按协议解析,不能认为一次 recv() 就是一条完整消息。

Windows 找不到 xsocket.dll

确保 DLL 位于:

  • 可执行文件所在目录;
  • 当前工作目录;
  • 或系统 PATH 中可搜索的位置。

构建成功只表示链接时找到了 .lib,运行时仍需要 .dll

Linux 找不到 libxsocket.so

确认共享库路径并设置 LD_LIBRARY_PATH,或者使用正确的 rpath/系统安装路径。

广播客户端没有回复

检查服务端是否监听对应端口、网络是否允许广播、发送地址是否适合当前子网,以及 Windows 防火墙规则。

端口值不对

写入 sockaddr_in.sin_port 时使用 htons();从收到的地址读取时使用 ntohs()

推荐学习路线

第一阶段:原始调用流程

  1. testsocket
  2. udpserver
  3. udpclient
  4. budpclient

第二阶段:封装与客户端/服务端配合

  1. xsocket
  2. tcpserver
  3. tcpclient

第三阶段:I/O 多路复用和应用协议

  1. epoll
  2. xhttp
  3. xsyslog

建议实验

  • tcpserver 增加命令行 IP 和端口参数。
  • 修改客户端,让它循环处理 send()recv() 的短读短写。
  • 为 TCP 消息增加 4 字节长度头,解决消息边界问题。
  • XTcp 禁止复制并实现移动构造和移动赋值。
  • XTcp 增加 RAII 析构关闭,同时明确所有权转移。
  • Connect()select() 后检查 SO_ERROR
  • 把 epoll 客户端读取改成循环读到 EAGAIN
  • 为 HTTP 服务增加 400、404、405 响应和安全路径规范化。
  • xsyslog 端口改成 5514,并从另一个终端发送测试日志。

代码边界

这些程序用于学习 Socket API 和网络执行流程,不是生产级网络库。真实服务还需要处理协议分帧、非阻塞错误码、连接超时、重连、半关闭、背压、并发上限、资源所有权、异常安全、TLS、身份认证、输入校验、目录穿越和命令注入防护。

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages