C/C++实战:从零构建高性能即时聊天软件,深入网络编程与epoll应用
1. 项目概述从零构建一个即时聊天软件最近在技术社区里看到不少朋友对用C/C写网络应用特别是即时聊天软件很感兴趣。这确实是个经典又充满挑战的实战项目它能把你学过的网络编程、多线程、数据结构甚至密码学知识串起来形成一个完整的闭环。很多人觉得C/C写应用层软件太“底层”、太“麻烦”不如用Java或Go来得快。但反过来想正是这种“麻烦”让你能亲手控制每一个字节的流向理解从Socket建立到消息收发的每一个细节这对于深入理解计算机系统的工作原理至关重要。这个项目就是带你用最“硬核”的方式打造一个属于你自己的聊天工具。这个项目适合谁呢首先当然是正在学习C/C并且已经掌握了指针、内存管理、基础数据结构如链表的开发者。其次是对网络编程Socket和多线程编程有初步了解但缺乏一个完整项目来巩固和实践的朋友。最后它也适合那些对“轮子”内部构造充满好奇不满足于仅仅调用高级API想探究底层通信机制的硬核爱好者。通过完成它你不仅能得到一个可以运行的聊天程序更能获得一套解决同类问题的“肌肉记忆”。项目的核心目标很明确实现一个支持多人在线、实时文本聊天的客户端/服务器C/S架构软件。我们将从最基础的TCP Socket通信开始逐步构建起服务器的事件处理模型、客户端的用户界面、以及保障通信可靠性的协议设计。整个过程我会穿插着讲解为什么这么选型以及我踩过的那些坑让你少走弯路。2. 核心架构设计与技术选型动手写代码之前定好架构和技术栈是关键。一个混乱的架构会让后期开发举步维艰。对于我们的即时聊天软件核心是经典的C/S架构但里面的门道不少。2.1 为什么选择TCP而非UDP这是第一个要做的抉择。UDP无连接、速度快但对于聊天软件可靠性是生命线。你肯定不希望自己打的字丢了一半或者顺序错乱吧TCP提供的面向连接、可靠传输、流量控制和拥塞控制正是我们需要的。虽然它的三次握手、四次挥手带来了一些开销但在局域网或现代互联网环境下这点开销对于聊天应用来说完全可以接受。因此我们毫不犹豫地选择TCP作为传输层协议。2.2 服务器模型I/O多路复用是必选项服务器要同时处理成百上千个客户端的连接和消息传统的“一个连接一个线程”模型多线程/多进程会迅速耗尽系统资源。这时I/O多路复用技术就成了救星。在Linux下我们有三个主要选择select、poll和epoll。select最古老有文件描述符数量限制通常是1024且每次调用都需要在内核和用户空间之间拷贝整个描述符集合效率较低。poll解决了文件描述符数量限制的问题但同样存在拷贝整个集合的性能问题。epollLinux特有的高性能方案。它采用事件驱动的方式只在描述符状态变化时通知应用程序避免了无谓的遍历和拷贝非常适合连接数多但活动连接比例不高的场景比如聊天服务器大部分时间连接是空闲的。对于我们的项目如果目标平台是Linuxepoll是不二之选。它的边缘触发ET模式虽然编程稍复杂但能最大限度地减少系统调用提升性能。如果考虑跨平台libevent或libuv这类网络库封装了不同系统的底层I/O多路复用机制如Linux的epollBSD的kqueueWindows的IOCP是更优雅的选择。但为了彻底理解原理我们这个项目先基于epoll来实现。2.3 通信协议设计自定义应用层协议TCP是流式协议它保证字节流顺序不错、不丢但不管边界。这意味着客户端发送“Hello”和“World”两个包服务器可能一次收到“HelloWorld”也可能分两次收到“He”和“lloWorld”。所以我们必须自己在应用层定义消息的边界这就是协议。一个简单而有效的设计是“长度内容”的二进制协议。每个消息包由两部分组成消息头Header固定长度比如4个字节存储一个无符号整数表示后面“消息体”的长度。消息体Body可变长度存储实际的数据内容比如JSON格式的聊天文本、命令等。发送时先计算Body的长度转换成网络字节序htonl写入Header再写入Body。接收时先尝试读取固定长度的Header解析出Body长度N然后循环读取直到收满N个字节的Body。这样就完美解决了粘包和拆包问题。注意网络字节序大端序和主机字节序可能不同必须用htonl/ntohl进行转换。这是网络编程初学者最容易忽略的坑之一会导致在跨平台或不同架构机器间通信时解析出错误的长度。2.4 数据序列化JSON vs. 二进制消息体里放什么我们需要一种格式来序列化结构化的数据比如消息类型、发送者、接收者、内容、时间戳等。常见选择有JSON、XML或纯二进制结构体。JSON文本格式人类可读调试方便有很多成熟的C/C解析库如nlohmann/jsonfor C,cJSONfor C。缺点是体积稍大解析需要一定开销。纯二进制结构体效率最高体积最小。但需要严格处理结构体对齐、字节序问题扩展性差增加字段会导致新旧版本不兼容调试困难。对于学习项目我推荐使用JSON。它的可读性和易用性能极大降低开发调试门槛。性能方面在千级用户、消息频率不高的聊天场景下JSON解析的开销几乎可以忽略。我们可以这样设计一个聊天消息的JSON体{ type: chat, from: user123, to: group_general, content: 大家好我上线了, timestamp: 1697012345 }3. 服务器端核心实现详解服务器是整个系统的大脑它负责维护所有在线用户、转发消息、处理逻辑。我们基于epoll来实现一个高性能的事件驱动服务器。3.1 主循环与epoll事件驱动服务器的核心是一个无限循环在这个循环里我们调用epoll_wait等待事件发生。事件可能有两种1新的客户端连接请求2已连接客户端发来了数据。// 伪代码示意 int epoll_fd epoll_create1(0); struct epoll_event ev, events[MAX_EVENTS]; // 将监听socket加入epoll ev.events EPOLLIN; // 监听可读事件新连接 ev.data.fd listen_fd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, ev); while (running) { int nfds epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (int i 0; i nfds; i) { int fd events[i].data.fd; if (fd listen_fd) { // 处理新连接 accept_new_connection(listen_fd, epoll_fd); } else { // 处理客户端数据 handle_client_data(fd); } } }这里有一个关键技巧为了支持边缘触发ET模式我们需要将socket设置为非阻塞模式O_NONBLOCK并且在读取数据时必须循环读取直到read返回EAGAIN或EWOULDBLOCK错误确保一次性读完所有可读数据。否则在ET模式下同一个事件只会通知一次没读完的数据可能永远没机会再读了。3.2 连接管理与用户状态维护我们需要一个数据结构来管理所有在线的客户端。一个简单的办法是用一个std::map或std::unordered_map以socket文件描述符fd为键存储一个用户会话对象Session。struct UserSession { int fd; std::string username; std::string current_channel; // 当前所在聊天室 // ... 其他状态如登录状态、最后活跃时间等 }; std::unordered_mapint, UserSession active_sessions;当accept一个新连接后就创建一个UserSession对象放入map。当客户端断开连接read返回0就从map中移除并关闭fd。心跳机制TCP连接本身不会自动检测对端是否异常掉线如拔网线。我们需要一个心跳机制。客户端定期比如每30秒发送一个特殊的心跳包{type: heartbeat}。服务器端记录每个会话的最后活跃时间。另一个后台线程定期检查如果某个会话超过一定时间比如90秒没有收到任何数据包括心跳就判定其超时主动断开连接并清理资源。这能有效防止“僵尸连接”占用系统资源。3.3 消息路由与群聊/私聊实现服务器收到一个完整的消息包并解析出JSON后根据type字段进行路由。登录/认证(type: login)验证用户名密码项目中为了简化可以写死或放内存成功后将username绑定到当前会话并通知该用户其好友列表或群列表如果有。群聊消息(type: chat, to: group_xxx)服务器需要知道有哪些用户在group_xxx这个群里。这需要维护一个“群组-用户列表”的映射关系。当收到群聊消息时遍历该群的所有在线用户将消息转发给除了发送者之外的每一个人。私聊消息(type: chat, to: username)服务器根据目标用户名在active_sessionsmap中查找对应的socket fd。如果找到说明对方在线就将消息转发过去如果没找到可以选择将消息暂存到离线消息队列如果实现了的话或者回复发送者一个“用户不在线”的错误。这里消息转发的性能考量如果群成员很多遍历转发可能成为瓶颈。一种优化是为每个群组维护一个fd的列表转发时直接遍历这个fd列表进行写操作避免每次都通过用户名去map里查找。4. 客户端核心实现详解客户端需要完成两件事1与服务器网络通信2提供用户界面UI。为了简化我们可以先做一个命令行CLI客户端这能让我们更专注于网络通信和业务逻辑。4.1 网络通信模块非阻塞I/O与协议解析客户端同样需要处理网络I/O。一个简单的做法是使用阻塞式Socket但在一个单线程的CLI程序中阻塞式recv会卡住整个界面用户无法在等待消息时输入文字。因此我们需要将Socket设置为非阻塞或者使用多线程。多线程模型是一个清晰的选择主线程负责读取用户输入、更新界面。网络线程专门负责从Socket读取数据。它在一个循环中使用select或poll因为客户端连接数少等待socket可读然后读取数据并调用协议解析函数。解析出完整消息后通过线程安全的方式如队列传递给主线程处理。客户端的协议解析逻辑和服务器端是对称的也是先读4字节头解析长度N再读N字节体。这里要特别注意处理“拆包”的情况即一次recv可能只收到消息的一部分。我们需要一个“读缓冲区”把未读完的数据缓存起来下次接着读。class NetWorkClient { int sockfd; std::vectorchar read_buffer; // 读缓冲区 // ... void on_data_received(const char* data, size_t len) { read_buffer.insert(read_buffer.end(), data, data len); while (try_parse_packet()) { // 解析出一个完整包处理它 } } bool try_parse_packet() { if (read_buffer.size() 4) return false; uint32_t body_len parse_header(read_buffer.data()); if (read_buffer.size() 4 body_len) return false; // 解析出一个完整包 std::string body(read_buffer.data() 4, body_len); handle_packet(body); // 从缓冲区移除已处理的数据 read_buffer.erase(read_buffer.begin(), read_buffer.begin() 4 body_len); return true; } };4.2 用户界面与交互逻辑对于CLI客户端界面就是终端里的文字。我们需要处理几个并发的任务显示收到的消息、等待用户输入、可能还需要显示一个在线用户列表。这可以通过简单的“事件循环”来实现。一个简单的架构是主循环里用select同时监听两个文件描述符1标准输入stdin文件描述符0用于接收用户输入2网络Socket如果网络线程用队列传递消息则这里监听的是某个管道或条件变量。// 伪代码主线程事件循环 while (running) { fd_set read_fds; FD_ZERO(read_fds); FD_SET(STDIN_FILENO, read_fds); FD_SET(pipe_fd_for_net_msg, read_fds); // 假设网络线程通过管道通知主线程 select(max_fd1, read_fds, NULL, NULL, NULL); if (FD_ISSET(STDIN_FILENO, read_fds)) { std::string user_input read_stdin(); process_user_command(user_input); // 处理用户输入可能是聊天文字或命令 } if (FD_ISSET(pipe_fd_for_net_msg, read_fds)) { ChatMessage msg pop_message_from_queue(); display_message(msg); // 在屏幕上显示收到的消息 } }用户命令处理除了聊天内容用户可能需要输入一些命令比如/join general加入群组、/list列出在线用户、/quit退出。主线程需要解析这些以/开头的输入并转换成相应的协议消息发送给服务器。4.3 消息的发送与本地回显当用户输入一行聊天文字并按下回车后客户端需要做两件事本地回显立即在用户自己的屏幕上显示“我xxx”。这能给用户即时的反馈体验更好。网络发送将消息按照“长度头JSON体”的格式打包通过Socket发送给服务器。这里有一个细节对于群聊消息本地回显就够了。但对于私聊消息为了区分回显时可以显示为“你对[用户名]说xxx”。发送的逻辑是统一的只是JSON体里的to字段不同。5. 关键问题排查与性能优化在实际编码和测试中你会遇到各种各样的问题。下面我总结几个最常见的坑和解决思路。5.1 连接失败与“Address already in use”当你重启服务器时可能会遇到bind失败提示“Address already in use”。这是因为之前的连接处于TIME_WAIT状态端口还没有被完全释放。解决方法是设置socket选项SO_REUSEADDR。int reuse 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)reuse, sizeof(reuse)) 0) { perror(setsockopt(SO_REUSEADDR) failed); } // 然后再 bind5.2 数据收发不完整与缓冲区管理这是网络编程中最常见的问题。无论是服务器还是客户端都必须妥善管理读写缓冲区。写缓冲区send或write函数可能不会一次性发送完你给的所有数据它返回实际发送的字节数。对于非阻塞socket如果返回EAGAIN说明内核发送缓冲区已满你需要将剩余数据放入自己的应用层写缓冲区并监听该socket的可写事件EPOLLOUT等缓冲区可写时再尝试发送。在我们的学习项目中为了简化可以使用阻塞socket并循环调用send直到发完但要注意这可能在极端情况下导致线程阻塞。读缓冲区如前所述必须用一个应用层缓冲区来缓存未处理完的字节流这是解决TCP粘包/拆包问题的核心。一个健壮的读缓冲区实现除了存储数据最好还能记录当前已解析的位置避免频繁的内存搬移erase操作。可以使用一个环形缓冲区或记录start_index和data_size。5.3 多线程下的数据共享与竞态条件如果客户端采用多线程UI线程和网络线程或者服务器在epoll工作线程之外还有别的线程比如心跳检查线程那么共享数据如active_sessions的访问就需要加锁。使用互斥锁mutex在C中std::mutex和std::lock_guard是好朋友。任何读写active_sessions的地方都需要锁保护。注意锁的粒度锁的粒度太粗比如一个全局大锁会严重影响性能。可以考虑使用读写锁std::shared_mutex因为“读”操作查找用户远多于“写”操作用户登录/登出。或者使用更细粒度的锁比如为每个用户或每个群组单独加锁。死锁预防确保加锁顺序一致或者使用std::scoped_lock来同时锁多个互斥量它能避免死锁。5.4 内存泄漏与资源管理C/C项目最怕内存泄漏。服务器是长时间运行的任何微小的泄漏都会被放大。使用RAII这是C的核心思想。用智能指针std::unique_ptr,std::shared_ptr管理动态内存用std::vector/std::string管理缓冲区用std::lock_guard管理锁。确保资源在离开作用域时能被自动释放。检查所有new/malloc和delete/free确保成对出现。在复杂逻辑或异常分支中很容易漏掉delete。Socket和文件描述符也是资源确保每一个accept返回的客户端fd在连接关闭后都调用了close。epoll中注册的fd在关闭前也最好用epoll_ctl的EPOLL_CTL_DEL移除虽然关闭fd后内核会自动将其从所有epoll实例中移除但显式删除是好习惯。使用Valgrind检测在Linux下用valgrind --leak-checkfull ./your_server来运行程序它能精准定位内存泄漏的位置。5.5 性能瓶颈分析与简单优化当连接数上去后可能会发现服务器CPU或内存占用过高。使用top和htop观察进程的CPU和内存使用情况。I/O操作是瓶颈大量小消息会导致频繁的系统调用read/write。可以考虑“写合并”优化对于要发给同一个用户的多个消息或者短时间内同一个群组的多个消息在应用层稍微缓冲一下合并成一个稍大的包再发送可以减少系统调用次数和网络包数量。但这会引入微小的延迟需要权衡。协议解析开销如果使用JSON在消息非常密集时解析开销可能显现。这时可以评估换用更高效的序列化方案如Protobuf或MessagePack但它们会引入额外的复杂性和依赖。日志输出调试时打的日志在线上运行时如果级别过低如DEBUG频繁的磁盘I/O或控制台输出会成为巨大瓶颈。务必使用分级日志并在生产环境关闭调试日志。6. 项目扩展方向与进阶思考完成基础版本后你可以选择以下一个或多个方向进行深化这会让你的项目从“玩具”升级为“作品”。6.1 引入数据库持久化目前用户信息和聊天记录都在内存中服务器重启就全没了。引入数据库是必然的一步。轻量级选择SQLite它是一个库无需单独部署数据库服务器。适合学习和小型应用。你可以创建users表用户名、密码哈希、messages表发送者、接收者/群组、内容、时间等。操作方式服务器启动时从数据库加载用户信息。用户登录时验证密码。聊天消息在转发的同时异步写入数据库注意不要因为等数据库写入而阻塞消息转发线程。可以设计一个单独的数据库访问线程或使用连接池。引入数据库后你就可以实现消息漫游查看历史记录和离线消息登录后拉取未读消息功能了。6.2 实现文件传输功能文字聊天的下一步自然是传文件。文件传输不能像聊天消息那样直接放在JSON里走TCP流因为文件可能很大。方案一HTTP分块上传这是最通用和简单的方法。客户端将文件通过HTTP POST上传到服务器的一个专门端口或路径服务器保存文件并返回一个下载链接或文件ID然后将这个链接作为一条特殊的聊天消息发送给接收方。接收方点击链接即可下载。这种方式对服务器架构改动小可以利用成熟的HTTP服务器如Nginx来处理文件。方案二自定义协议分片传输在现有TCP连接上定义新的消息类型如file_start,file_chunk,file_end。发送方将文件分片依次发送。服务器接收并暂存分片接收方准备好后再从服务器拉取或由服务器推送。这种方式更“原生”但实现复杂需要处理传输中断、续传、校验等问题。对于学习项目我建议先实现方案一因为它更贴近实际生产环境很多IM系统就是这么做的且能让你接触到HTTP协议。6.3 打造图形化客户端命令行客户端终究不够友好。用Qt或Dear ImGui这类库为你的聊天引擎套上一个图形界面体验立刻不同。Qt功能强大跨平台文档丰富。你可以用Qt Designer拖拽出登录窗口、好友列表、聊天对话框。网络部分可以继续用你写好的网络通信模块将其集成到Qt的事件循环中注意跨线程信号槽通信。Dear ImGui一个轻量级的即时模式GUI库渲染很漂亮适合做工具类应用。它更接近“在代码中画UI”对于C开发者来说可能更顺手。图形化客户端的关键在于将网络模块与UI线程分离。UI主线程绝不能因为等网络而卡住。所有耗时的网络操作连接、发送、接收都应该在单独的线程中完成然后通过线程安全的方式如Qt的信号槽、ImGui的线程队列将结果如收到新消息传递回UI线程进行渲染。6.4 安全性考量初步一个真正的聊天软件必须考虑安全。传输加密目前所有消息都是明文传输在公共网络下极易被窃听。最直接的改进是使用TLS/SSL。你可以集成OpenSSL或mbedTLS库将你的普通TCP Socket升级为SSL Socket。客户端和服务器在握手阶段交换证书生产环境需要CA签发的证书测试可以用自签名证书之后的通信全部加密。密码存储绝对不要明文存储密码使用加盐哈希Salt Hash算法如bcrypt、scrypt或Argon2。当用户注册时服务器生成一个随机盐salt将盐和密码拼接后哈希存储哈希值和盐。登录时用同样的盐和用户输入的密码计算哈希与存储的哈希值对比。这样即使数据库泄露攻击者也无法直接得到用户密码。输入验证与防注入服务器要对客户端发来的所有数据进行严格的验证和转义防止SQL注入如果用了数据库、JSON解析错误甚至缓冲区溢出攻击。实现这些安全特性尤其是TLS会显著增加项目的复杂度但这是从“实验项目”走向“可用的软件”的必经之路。

相关新闻

AI辅助RTL设计的实践与挑战

AI辅助RTL设计的实践与挑战

1. 当RTL工程师遇上AI:一场效率革命还是灾难现场?上周五凌晨两点,我在办公室盯着屏幕上那行诡异的Verilog代码已经三小时——这是用最新AI工具生成的"优化版"状态机,理论上应该减少20%的功耗。但实际仿真时,…

2026/10/12 8:26:28 阅读更多 →
Linux内核高端内存映射机制与优化实践

Linux内核高端内存映射机制与优化实践

1. 内核地址空间管理的核心挑战在32位Linux系统中,内核面临着物理内存管理的经典难题——如何高效映射超过1GB的高端内存区域。这个问题源于32位体系结构下4GB虚拟地址空间的硬性限制。内核默认需要占用1GB的虚拟地址空间(0xC0000000 - 0xFFFFFFFF&#…

2026/10/12 8:26:29 阅读更多 →
智能优化与深度学习在轴承故障诊断中的应用

智能优化与深度学习在轴承故障诊断中的应用

1. 项目背景与核心价值轴承作为旋转机械的核心部件,其健康状态直接影响设备运行安全。传统振动信号分析方法在复杂工况下存在特征提取不充分、诊断精度不足等问题。本项目提出了一种融合智能优化算法与深度学习的端到端诊断框架,通过西储大学轴承数据集验…

2026/10/4 3:29:08 阅读更多 →

最新新闻

不会编程,用AI写代码靠谱吗?TaoToken统一Key实测Cline MCP与Windsurf BYOK

不会编程,用AI写代码靠谱吗?TaoToken统一Key实测Cline MCP与Windsurf BYOK

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 8:26:01 阅读更多 →
claude-mem:为大模型对话打造跨会话长期记忆的实践指南

claude-mem:为大模型对话打造跨会话长期记忆的实践指南

很多人第一次看到 claude-mem 这个名字,可能觉得它是又一个聊天记录备份工具——把对话原样存进数据库,要用的时候再翻出来。我最早也是这样理解的,直到花了一个周末把它从原型推到可用状态,才意识到它真正解决的是大模型会话里最…

2026/10/12 8:26:01 阅读更多 →
OpenCV 4.5.5编译指南:VS2019+CUDA11.1+contrib全流程

OpenCV 4.5.5编译指南:VS2019+CUDA11.1+contrib全流程

简介:面向Windows平台C开发者的预编译OpenCV 4.5.5资源包,基于Visual Studio 2019与CUDA 11.1环境,以Release x64架构构建,内置CUDA加速与contrib扩展模块,适合需要利用GPU提升图像处理、目标检测、DNN模型推理性能的开…

2026/10/12 8:26:01 阅读更多 →
光伏+特高压+AI:从能源底座到电力系统的智能化实战解析

光伏+特高压+AI:从能源底座到电力系统的智能化实战解析

开篇先聊个现象:我身边很多做技术、做投资的朋友,这两年都在反复讨论同一个组合——"光伏特高压"。有人把它当作碳中和的核心叙事,有人盯着电网侧的改造机会,而像马斯克这样的全球科技圈人物,也公开把这一组…

2026/10/12 8:26:01 阅读更多 →
开放代码评审:重构团队协作与知识沉淀的实践范式

开放代码评审:重构团队协作与知识沉淀的实践范式

1. 项目概述:这不是代码检查,而是一场协作范式的重构“open-code-review”这个词组乍一看像某个开源工具的代号,但实际它根本不是一款软件,也不是某个具体平台的功能按钮。它是近年来在一线开发团队中自然生长出来的一套实践共识—…

2026/10/12 8:26:01 阅读更多 →
边界值分析法:从线上事故到测试用例设计的完整实战指南

边界值分析法:从线上事故到测试用例设计的完整实战指南

1. 从一次线上事故说起:你确定真的测过“边界”吗做软件测试这行,时间久了都会有一种感觉:用户永远比你更会“找茬”。前阵子我们上线了一个活动页面,里面有个优惠券金额输入框,需求文档写得清清楚楚:金额范…

2026/10/12 8:25:00 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →