ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

learnyounode 实战:用 Node.js net 模块构建一个 TCP 时间服务器(time_server 练习全解)

learnyounode 实战:用 Node.js net 模块构建一个 TCP 时间服务器(time_server 练习全解) 教程CLI【免费下载链接】learnyounodeLearn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.项目地址https://gitcode.com/gh_mirrors/le/learnyounode点击查看免费下载本指南围绕 learnyounode 开源工作坊中的time_serverTCP 时间服务器练习展开完整讲解如何利用 Node.js 核心模块net从零搭建一个监听 TCP 连接、按YYYY-MM-DD hh:mm格式回写当前时间的服务器并深入剖析其背后的验证机制与实现原理。读完本文你将掌握net.createServer()、server.listen()、socket.write()/socket.end()的完整用法学会手动实现零填充日期格式化并能通过 learnyounode 自带的验证器确认代码正确性。练习目标与核心要求在 exercises/time_server/problem.es.md及其英文原版 exercises/time_server/problem.md中本练习要求你编写一个名为time-server.js的TCP 时间服务器其行为规范非常明确服务器监听TCP 连接监听端口由程序启动时的第一个命令行参数提供每收到一个连接就向该连接写入当前日期与 24 小时制时间格式为YYYY-MM-DD hh:mm输出必须以一个换行符\n结尾月份、日期、小时和分钟必须用零填充为固定两位数例如2013-07-06 17:42数据发送完毕后关闭该连接。值得强调的是这是一个裸 TCP 服务器完全不涉及 HTTP 协议层。这正是本练习与同仓库中 http_client、http_file_server 等练习的最大区别——它要求你直接使用 Node 核心模块net操作网络套接字。第一步用 net.createServer() 搭建服务骨架net模块是 Node.js 的核心模块之一包含所有基础的网络功能。在本仓库的 docs-nodejs/net.htmlNode.js v12.10.0 官方文档离线镜像中可以看到net.createServer([options][, connectionListener])用于创建一个新的 TCP 或 IPC 服务器它接收两个可选参数参数类型说明options.allowHalfOpenboolean是否允许半开 TCP 连接对端发来 FIN 后服务端不主动回 FIN默认falseoptions.pauseOnConnectboolean新连接建立后是否立即暂停 socket 数据读取默认falseconnectionListenerFunction自动注册为connection事件的监听器与 Node 中常见的回调函数如fs的读取回调只调用一次不同createServer()传入的这个回调是connection listener连接监听器它会被调用多次——服务器每收到一个 TCP 连接就会触发一次回调签名如下function callback (socket) { /* ... */ }同时net.createServer()会返回一个server实例需要调用server.listen(portNumber)才能真正开始监听端口。文档中给出的典型 Node TCP 服务器骨架是const net require(net) const server net.createServer(function (socket) { // socket 处理逻辑 }) server.listen(8000)重要提醒本练习中不要硬编码端口号8000必须使用程序启动时通过命令行传入的第一个参数即process.argv[2]。深入listen() 的多种形态在 docs-nodejs/net.html 中server.listen()实际上有四种重载形态server.listen(handle[, backlog][, callback])、server.listen(options[, callback])、server.listen(path[, backlog][, callback])Unix 域套接字以及最常用的server.listen([port[, host[, backlog]]][, callback])。本练习使用的是最后一种只传端口号让服务器监听在所有网络接口上listen还支持传入host如127.0.0.1限定监听地址以及backlog挂起连接队列长度和监听成功后的回调函数。第二步理解 socket —— 一个双工流net.createServer()回调收到的socket对象包含大量连接元数据如socket.localAddress、socket.localPort等但它本质上是一个Node 双工流duplex Stream——既可以读取数据也可以写入数据这正是stream.Duplex类的实例参见 docs-nodejs/net.html 中Class: net.Socket一节。对于本练习我们只需要写入数据然后关闭连接用到的核心方法是socket.write(data)向 socket 写入数据socket.end()半关闭 socket即发送 FIN 包socket.end(data)等价于socket.write(data)后紧接着调用socket.end()可以一步完成写入并关闭。按照 docs-nodejs/net.html 对socket.end([data][, encoding][, callback])的说明data可以是字符串、Buffer或Uint8Arrayencoding仅在 data 为字符串时使用默认utf8。本练习的响应是一段 UTF-8 文本因此直接用socket.end(data)即可。第三步格式化当前时间零填充是关键服务器输出的核心数据是当前时间可以通过new Date()获得然后调用以下日期方法手动拼接格式date.getFullYear() date.getMonth() // 注意从 0 开始需要 1 date.getDate() // 返回当月第几天从 1 开始 date.getHours() date.getMinutes()这里有两个最容易踩坑的细节getMonth()从 0 开始计数一月是 0、二月是 1……因此必须1才能得到真实的月份数字月份、日期、小时、分钟都需要零填充为两位数。例如 7 月 6 日必须输出07-06下午 5 点 42 分必须输出17:42而不是7-6或17:4。仓库参考实现官方 solution本仓库自带的参考答案 exercises/time_server/solution/solution.js 展示了最直观的零填充写法use strict const net require(net) function zeroFill (i) { return (i 10 ? 0 : ) i } function now () { const d new Date() return d.getFullYear() - zeroFill(d.getMonth() 1) - zeroFill(d.getDate()) zeroFill(d.getHours()) : zeroFill(d.getMinutes()) } const server net.createServer(function (socket) { socket.end(now() \n) }) server.listen(Number(process.argv[2]))其关键设计值得逐条拆解zeroFill(i)用(i 10 ? 0 : ) i实现了零填充——小于 10 的数字补一个前导0两位数及以上则原样输出正确处理了 10、11、12 等不受影响的月份/小时now()中getMonth() 1修正了从 0 开始计数的月份服务端回调里直接使用socket.end(now() \n)即写入格式化时间 换行符然后关闭连接一步完成监听端口使用Number(process.argv[2])——显式把第一个命令行参数转换为数字再传给server.listen()因为process.argv[2]在 shell 中以字符串形式存在。测试用例中的另一种写法仓库测试目录 test/time_server/valid_01.js 展示了一种更函数式的零填充思路供开阔思路function now (d) { return [ d.getFullYear(), -, d.getMonth() 1, -, d.getDate(), , d.getHours(), :, d.getMinutes() ].reduce(function (before, current, count) { return before (count % 2 || ( current).length 2 ? : 0) current }) } require(net).createServer(function (socket) { socket.end(now(new Date()) \n) }).listen(process.argv[2] | 0)这里用Array.prototype.reduce在遍历时判断奇偶位置插入-/ /:分隔符并通过( current).length 2判断是否需要补零listen(process.argv[2] | 0)则用位或运算把参数转成整数。官方solution.js的可读性明显更高也更适合作为学习模板。备选方案npm 的 strftime 包如果你愿意引入第三方依赖练习文档还提到一个更冒险的备选方案——npm 上的strftime包。其strftime(fmt, date)函数接受与 Unix 命令date类似的时间格式字符串例如strftime(%F %H:%M, new Date())即可直接得到2013-07-06 17:42这样的零填充输出。不过在 learnyounode 的练习语境中手写零填充能让你真正理解日期 API 的行为细节官方也建议优先使用new Date()的方式。验证learnyounode 如何检查你的服务器完成代码后本练习英文原版 exercises/time_server/problem.md提供了验证命令$ {appname} verify time-server.js其中{appname}对应 learnyounode 的命令名即learnyounode verify time-server.js如果你把文件命名为program.js则传入program.js。验证器会返回 PASS/FAIL 结果并给出失败原因。验证器源码剖析learnyounode 的每个练习都配有对应的exercise.js作为验证逻辑本练习的验证器位于 exercises/time_server/exercise.js其工作流程非常值得研究文件检查filecheck(exercise)确认你提交的time-server.js确实存在并行执行execute(exercise)用spawn()并行启动你的提交程序和官方 solution作为两个子进程动态端口分配在addSetup阶段通过lib/rndport.js中的rndport()生成随机端口——该工具的实现是1024 Math.floor(Math.random() * 64511)见 lib/rndport.js即 1024 到 65535 之间的随机数。你的程序收到submissionPort官方 solution 收到submissionPort 1延迟探测addProcessor中通过setTimeout(query.bind(this, mode), 500)延迟 500ms 再发起连接目的是等待两个服务器完成启动客户端连接与对比query()内部用net.connect(port)连接两个端口把收到的响应分别pipe到提交流和 solution 流若连接失败服务器未启动、端口未监听等则触发fail事件并报告fail.connection错误信息标准输出比对最后comparestdout(exercise)将你的程序响应与官方 solution 的响应逐字节比对因为两边都用new Date()同一秒内格式一致可以通过比对。从测试文件 test/time_server/invalid_01.js空文件可以看出验证器同时覆盖了提交内容不合格的失败场景。这一整套机制说明time_server 验证的核心是你的进程能否在指定端口上、在 500ms 的探测窗口内给出与官方实现一致的格式化时间字符串。手动验证用 nc / telnet 自测在提交验证之前你也可以完全脱离 learnyounode 手动验证自己的实现。先启动服务器以端口 8000 为例$ node time-server.js 8000然后在另一个终端用ncnetcat或telnet连接$ nc localhost 8000 2013-07-06 17:42或者用 Node 一行式模拟客户端$ node -e require(net).connect(8000, 127.0.0.1).on(data, d process.stdout.write(d))如果输出格式为YYYY-MM-DD hh:mm且以换行结尾、月份/日期/小时/分钟均为两位数即可进入learnyounode verify正式验证环节。常见错误与排查要点结合练习要求、官方 solution 与测试用例以下四类问题最容易导致验证失败端口写死或读错参数使用了固定端口如8000而没有读process.argv[2]或忘记用Number()/| 0把字符串参数转成数字。验证器传入的是随机端口写死端口必然连接失败月份未 1getMonth()从 0 开始直接拼接会输出2013-06-06这种错误月份忘记零填充输出2013-7-6 17:4而非2013-07-06 17:42导致字符串长度与官方实现不一致comparestdout比对失败忘记换行符或没有关闭连接输出必须以\n结尾且发送后应关闭连接——直接使用socket.end(now() \n)可以同时规避这两点。进阶思考从练习到真实场景虽然 time_server 是一个教学练习但它背后的知识体系直接通向真实网络编程裸 TCP 服务器的适用范围net模块可用于构建自定义协议服务、日志收集管道、数据库代理等不依赖 HTTP 的场景net.createServer()甚至支持通过server.listen(path)监听 Unix 域套接字实现进程间通信IPCsocket 即双工流的思维理解了socket是stream.Duplex后就能把pipe()、背压backpressure、data/end事件等流的知识直接迁移到网络编程中这正是 learnyounode 后续 http_file_server、http_uppercaserer 等练习的基础可读代码优于技巧对比官方 solution.js 与测试用的 reduce 写法前者以明确的zeroFill辅助函数取胜——当你的服务器逻辑逐渐复杂时可读性就是可维护性。至此你已经完整掌握了用net模块构建 TCP 时间服务器的全部要点理解连接监听回调、正确调用listen()、用双工流 socket 写出格式化时间以及理解 learnyounode 验证器如何动态分配端口并比对输出。可以直接上手编写 exercises/time_server/solution/solution.js 同款的time-server.js并用learnyounode verify time-server.js检验成果。赞分享教程CLI【免费下载链接】learnyounodeLearn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.项目地址https://gitcode.com/gh_mirrors/le/learnyounode点击查看免费下载相关推荐learnyounode 实战用 Node.js net 模块编写 TCP 时间服务器time_server 习题全解析learnyounode 实战用 Node.js net 模块编写 TCP 时间服务器time_server 习题全解析 本篇技术指南以 learnyou教程CLIawesome-deepseek-agent 指南将 DeepSeek 接入轻量级 AI Agent nanobotawesome deepseek agent 指南将 DeepSeek 接入轻量级 AI Agent nanobot nanobot 是一个开源轻量级 AI教程CLISecurity-101 课程仓库协作指南基于 Markdown 与 Docsify 的网络安全课程开发与多语言翻译流水线解析Security 101 课程仓库协作指南基于 Markdown 与 Docsify 的网络安全课程开发与多语言翻译流水线解析 Security 101 是一教程CLI上一篇Motion-Primitives滚动进度指示器提升用户导航体验的完整指南下一篇JSON Schema to TypeScript错误处理与调试常见问题解决方案大全创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表