{"id":41996705,"url":"https://github.com/yddeng/smux","last_synced_at":"2026-01-26T01:06:06.407Z","repository":{"id":57708558,"uuid":"369119919","full_name":"yddeng/smux","owner":"yddeng","description":"socket multiplexing, 多路复用","archived":false,"fork":false,"pushed_at":"2021-06-29T03:22:53.000Z","size":105,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-06-19T16:45:53.655Z","etag":null,"topics":["multiplexing","smux","tcp"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yddeng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-05-20T07:28:06.000Z","updated_at":"2024-04-14T13:56:52.000Z","dependencies_parsed_at":"2022-09-26T21:20:57.668Z","dependency_job_id":null,"html_url":"https://github.com/yddeng/smux","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/yddeng/smux","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yddeng%2Fsmux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yddeng%2Fsmux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yddeng%2Fsmux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yddeng%2Fsmux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yddeng","download_url":"https://codeload.github.com/yddeng/smux/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yddeng%2Fsmux/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28763132,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T00:37:26.264Z","status":"ssl_error","status_checked_at":"2026-01-26T00:37:25.959Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["multiplexing","smux","tcp"],"created_at":"2026-01-26T01:06:05.803Z","updated_at":"2026-01-26T01:06:06.402Z","avatar_url":"https://github.com/yddeng.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# smux\n\n`socket multiplexing`, 基于可靠连接`tcp`的多路复用。\n\n基于 `xtaci/smux` 修改、新增部分逻辑。相较于原项目，传输效率有所下降。\n\n下降原因，项目增加了发送字节确认机制。\n\n## 1 修改及新增\n\n1. 某一个 `stream` 不读，但对端一直写导致 `tcp` 缓冲区被写满，进而堵塞其他的 `stream` 。\n\n2. 对于`tcp`的超时写，可能已经写了部分数据才超时。 返回到`stream`应该表现出来。由问题1引出，如果设置了写超时，\n    本次的写入数据只写入了一部分，返回应用层写入0（但实际tcp会将本次数据写完），如果应用层选择重发，导致数据重复或者混乱。\n\n3. streamID 可能溢出，虽然uint32的容量挺大的，但还是有可能。同时降低包头 streamID占用的长度，提高传输效率。\n\n4. 新增检测对端是否使用多路复用\n\n5. 新增 `aio`\n\n## 2 项目设计\n\n基于可靠有序的连接\n\n### 2.1 协议包头\n\n`cmd (byte) + id (uint16) + length (uint32)`\n\ncmd 命令类型：`cmdSYN` 打开一个`stream`；`cmdFIN`  关闭一个 `stream`；`cmdPSH` 数据推送；`cmdCFM` 数据接受确认；\n\nid streamID。\n\nlength 视情况而定。 `cmdPSH` 代表携带的数据长度。`cmdCFM` 代表确认的数据长度。其他情况为0，无意义。\n\n### 2.2 StreamID\n\n根据协议包头可知，最大能支持的ID值为 `65535` 。\n\n`streamID` 采用回收复用的方式，避免累加后超过上限值。用位图来存储使用、空闲的`id`，减少内存占用。\n\n也意味着同时能开启的`stream`数量为`65535`个，超过这个数字就没有可使用的`id`了。经测试这个值较为合适，\n连接数达到一定阀值，即`tcp`的传输效率已经到达最大，更多的`stream`数反而会带来性能瓶颈。这个时候建议多开`tcp`连接。\n\n### 2.3 打开连接\n\n在 `idBitmap` 中申请一个id，发往对端 `cmdSYN`命令。\n\n正常情况下对端新建 `stream` 将其放入 `accept channel`中，对端通过 `Accept` 获取新`stream`。\n存在两端同时 `Open` 且 分配到相同的`ID`。两端都会收到 `cmdSYN`，直接忽略命令(相当于直接绑定到现有`stream`上)。\n\n### 2.4 数据传输\n\n给每个`stream` 设置读`buffer`，通过窗口调节发送频率。\n\n由于通信链路只有一条，而有多条虚拟链路。需保证通信链路畅通，不被任意 `stream` 堵塞。\n通信链路的 `buffer` 大小固定为 `64k`。`stream` 的读写缓冲区大小固定为 `512k`。\n\nA端发送数据累计到 `waitConfirm` 中，往对端发送的窗口大小为 `streamWindowSize - waitConfirm`, \n结果为0 时，B端读缓冲区已满。本端不再发送数据，等待B端读数据。\n                                  \nB端应用层调用 `Read` 后，推送已读数据长度 `bufferRead` 。A端 `waitConfirm -= bufferBuffer`, \nA端的发送窗口不为0，继续发送数据。\n\n\n### 2.5 写超时\n\n在`Stream`端可能会多次调用 `tcp.Write`（发送数据大于`frameSize`将会分包）。\n已经调用了`tcp.Write` 的数据默认成功发送（除非`tcp`连接报错，否则一定能到达对端，即使会堵塞一段时间）。\n\n如果 `stream` 的写超时到达，累计已经调用 `tcp.Write` 数据的长度返回，还未调用的不再调用。由应用层选择是否继续发送超时数据。\n\n### 2.6 关闭连接\n\n主动关闭连接方发送 `cmdFIN`到对端，并等待 `cmdFIN`回来。 整个流程全部完成，释放资源并回收`streamID`。\n\n需要确认关闭的原因：\n存在 A 调用`Close`，已经释放资源并回收`streamID`，`cmdFIN`正在发送B端。\n这时，A端 `Open` 一个相同ID 的 `Stream`，B端还没有收到 `cmdFIN`，仍在向A端发送数据。\n导致数据混乱。\n\n同时关闭的情况，对于任意一端来说，都相当于发出并接收到`cmdFIN`，完成了整个关闭流程。\n\n### 2.7 判断对端是否使用多路复用\n\n`cmdVRM` 。id 和 length 为随机数，对端经过算法验证返回两个随机数的确定值。\n\n`IsSmux(conn net.Conn) bool `\n\n```\n/*\n\tv1:uint16, v2:uint32。\n\tv1 分4个4位(b1,b2,b3,b4): (b1,b2,b3,b4) -\u003e (b3,b4,b2,b1)\n\tv2 分4个8位(b1,b2,b3,b4): (b1,b2,b3,b4) -\u003e (b1,b4,b2,b3)\n*/\nfunc verifyCode(v1 uint16, v2 uint32) (uint16, uint32) {\n\tv11 := ((v1 \u0026 0xFF) \u003c\u003c 8) | ((v1 \u0026 0xF000) \u003e\u003e 16) | ((v1 \u0026 0xF00) \u003e\u003e 4)\n\tv22 := (v2 \u0026 0xFF000000) | ((v2 \u0026 0xFF0000) \u003e\u003e 8) | ((v2 \u0026 0xFF00) \u003e\u003e 8) | ((v2 \u0026 0xFF) \u003c\u003c 16)\n\treturn v11, v22\n}\n```\n\n## 3 Usage\n\n```go\n\nfunc client() {\n    // Get a TCP connection\n    conn, err := net.Dial(...)\n    if err != nil {\n        panic(err)\n    }\n\n    // Session of smux\n    session := smux.SmuxSession(conn)\n\n    // Open a new stream\n    stream, err := session.Open()\n    if err != nil {\n        panic(err)\n    }\n\n    // Stream implements io.ReadWriteCloser\n    stream.Write([]byte(\"ping\"))\n    stream.Close()\n    session.Close()\n}\n\nfunc server() {\n    // Accept a TCP connection\n    conn, err := listener.Accept()\n    if err != nil {\n        panic(err)\n    }\n\n   // Session of smux\n   session := smux.SmuxSession(conn)\n\n    // Accept a stream\n    stream, err := session.Accept()\n    if err != nil {\n        panic(err)\n    }\n\n    // Listen for a message\n    buf := make([]byte, 4)\n    stream.Read(buf)\n    stream.Close()\n    session.Close()\n}\n```\n\n## example\n\n网关服务 https://github.com/yddeng/gate","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyddeng%2Fsmux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyddeng%2Fsmux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyddeng%2Fsmux/lists"}