用Go实现的简易TCP通信框架

2023-10-28

接触到GO之后,GO的网络支持非常令人喜欢。GO实现了在语法层面上可以保持同步语义,但是却又没有牺牲太多性能,底层一样使用了IO路径复用,比如在LINUX下用了EPOLL,在WINDOWS下用了IOCP。

但是在开发服务端程序的时候,很多都是被动触发的,都是客户端发送来的请求需要处理。天生就是一个event-based的程序。而在GO下,因为并发是作为语言的一部分,goroutine, channel等特性则很容易的使程序员在实现功能时从容的在同步与异步之间进行转换。

因为自己的需要,我针对event-based场景的服务端做了简易的封装。具体代码见这里.

设计原则

因为GO的IO机制和并发原语的原生支持,再加上对网络API的封装,程序员可以简单的实现一个高效的服务端或者客户端程序。一般的实现就是调用net.Listen(“tcp4”, address)得到一个net.Listener,然后无限循环调用net.Listener.Accept,之后就可以得到一个net.Conn,可以调用net.Conn的接口设置发送和接收缓冲区大小,可以设置KEEPALIVE等。因为TCP的双工特性,所以可以针对一个net.Conn可以专门启动一个goroutine去无限循环接收对端发来的数据,然后解包等。

我的想法是在这个简单实现的基础上做一层薄薄的封装,使其尽量的精简,但是又不失灵活。希望能够适应不同的协议,对使用者造成尽量小的约束。

Session对象

该对象就是对net.Conn的一个简易封装,可以通过swnet.Server.AcceptLoop得到,也可以通过swnet.NewSession创建新的对象,这种一般是客户端情境下使用。得到Session对象后,可以调用Start方法开始工作。之所以还暴露出一个方法叫Start是因为在服务端下,可能会有某些需求,比如针对IP设置了ACL,那么,把Start行为交给使用者决定如何调用。但是这里需要注意的是,如果使用者不想Start,使用者有责任自己Close掉,否则会造成资源泄露。

Start后,会启动两个goroutine,一个用于专门接收对端发来的数据,一个专门用来发送数据到对端。想发送数据到对端,可以用AsyncSend方法,该方法会把要发送的数据排队到发送通道。这里使用通道的原因是因为在服务端情境下,有必要对发送的数据进行排队,防止发送很快,但是对端接收很慢,或者过多的调用AsyncSend方法,导致堆积了太多的数据,增加了内存的压力。通过channel来控制发送速率我认为是比较合理的。同时,还提供了方法可以用来修改channel的长度,一是调用NewSession时传入指定大小,二是调用Session.SetSendChannelSize设置大小,但是要注意的是,调用此方法时必须在Start之前完成,否则会产生错误。这样做的原因也是因为没必要动态更改发送通道大小。

如果发送channel满了,AsyncSend方法会返回ErrSendChanBlocking。增加这个错误类型也是因为上面的设计导致的。不返回这个错误,就没有办法让使用者得到处理该问题的机会。使用者如果拿到该错误,可以自己试着分析问题的原因,或者可以尝试循环发送,或者直接丢弃该次的发送数据。总之能够让使用者得到自己处理的机会。

如果Session对象已经Close了,那么调用AsyncSend会返回ErrStoped错误。除此之外,因为AsyncSend是把数据排队到发送channel中,那么使用者有责任确保发送的数据在发送完成前不会修改。

如果数据发送失败,或者其他原因,我的实现是直接粗暴的Close掉该Session。

还有就是,可能有些用例情景下,会发送比较大的数据包,比如64K大小,或者32K大小的数据等,未了避免反复申请内存,特此为Session增加了SetSendCallback方法。可以设置一个回调函数,用于在发送完成后可以调用该回调,给予使用者回收数据对象的机会,比如可以配合sync.Pool使用。虽然我自己测试时并没有太大的效果。

为了方便使用者设置一些net.Conn参数,增加了一个RawConn方法,可以获取到net.Conn 的实例。这里其实是挺纠结的。因为暴露出这个内部资源后,会给予使用者一个非常大的灵活度。它可以直接绕过Session的发送channel,自己玩自己的。不过出于方便使用者使用的目的,我还是这么做了。使用者自己承担相应的责任。其实这里还可以像net.HTTP那样增加一个Hijack方法,让使用者自己接管net.Conn,自己玩自己的。

Session中的很多SET/GET方法都是没有加锁的。一方面是因为很多操作在Start前一次完成,或者是GET的数据不是那么紧密的。

有些时候,如果一个Session被关闭了,可能需要知道这个行为。所以提供了SetCloseCallback方法,可以设置该方法。不设置也没有关系。调用closeCallback时会确保只调用一次。

协议序列化抽象

因为目标之一就是能够隔离具体协议格式。所以对协议做了抽象。只需要实现PacketProtocol接口即可:

// PacketReader is used to unmarshal a complete packet from buff
type PacketReader interface {
    // Read data from conn and build a complete packet.
    // How to read from conn is up to you. You can set read timeout or other option.
    // If buff's capacity is small, you can make a new buff, then return it,
    // so can reuse to reduce memory overhead.
    ReadPacket(conn net.Conn, buff []byte) (interface{}, []byte, error)
}

// PacketWriter is used to marshal packet into buff
type PacketWriter interface {
    // Build a complete packet. If buff's capacity is too small,  you can make a new one
    // and return it to reuse.
    BuildPacket(packet interface{}, buff []byte) ([]byte, error)

    // How to write data to conn is up to you. So you can set write timeout or other option.
    WritePacket(conn net.Conn, buff []byte) error
}

// PacketProtocol just a composite interface
type PacketProtocol interface {
    PacketReader
    PacketWriter
}

也就是实现PacketReader/PacketWriter两个接口。为了让内存尽量的复用,减少内存压力,所以在ReadPacket方法和BuildPacket方法的返回值中需要返回一个切片。框架会在第一次调用时传入一个默认大小的切片到这两个方法中,如果容量不够,使用者可以自己重新建立切片,然后写入数据后返回该切片。下一次再实用时就使用这个返回出来的切片。

其中ReadPacket方法是在一个专门用于接收数据的goroutine中调用。实现者可以自己根据自己的策略进行读取,因为传入了net.Conn,所以使用者可以自己设置I/O Timeout。实现者有责任返回一个完整的请求包。如果中间出了错误,有必要返回一个error。当发现有error后,会关闭该Session。这样做的原因是当读取或者构建一个请求包失败时,可能是数据错误,可能是链路错误,或者其他原因,总之,个人认为这种情况下没有必要继续处理,直接关闭链接。而且这里还有一个需要注意的事项,返回出来的请求包中的数据如果有包含切片类型的数据,建议重新分配一个切片,然后从buff中拷贝进去,尽量不要对buff切片做复用,否则可能会产生额外的BUG。

BuildPacket方法是在一个专门处理发送的goroutine中调用。当发送goroutine收到数据包后,会调用BuildPacket,实现者就可以按照自己的私有格式进行序列化。同样的,buff不够,就自己重新构造一个buff,然后填充数据,并返回这个buff。

WritePacket是给予实现者自己个性化发送的需求。可能实现者需要设置I/O Timeout.

请求包路由

基于event-based的实现,总是少不了要做的事情就是把一个请求包转发到对应的处理函数中。但是具体怎么转,怎么做是取决于具体的用例情景和实现的。所以我这里做的非常简单,就是定义了一个PacketHandler接口:

// PacketHandler is used to process packet that recved from remote session
type PacketHandler interface {
    // When got a valid packet from PacketReader, you can dispatch it.
    Handle(s *Session, packet interface{})
}

使用者自己实现对应的Handle方法即可。当接收数据的goroutine收到对端发来的数据并调用PacketReader.ReadPacket后,会调用Handle方法 ,传入该Session实例与请求包。传入Session的目的是方便使用者不用去维护一个Session的实例。因为有的程序员要实现的逻辑可能比较简单,他仅仅用Session就满足了他的需求,他只需要实现对应的处理函数就好了。处理完成后,就调用Session.AsyncSend发送回应包。

这里其实可以提供一个简单的默认版本的实现的。但是考虑到协议的不同,那么就导致调度的key的不同,所以还是让使用者自己发挥吧。

使用者其实在这里有很大的自由度,他可以做基于map关系的回调分发逻辑,也可以做一个简单的实现逻辑,然后通过type assert做相应的实现。具体也是看各自的口味而定。我是比较喜欢后者,可以减少很多的Register,实现出Actor Model + Pattern Match味道的东西。

Server对象

这里还要说一下对服务端的一个简易封装。Server的实现非常简单,就是反复的去Accept,然后构造一个Session,之后就是调用用户传入的回调函数,就完活了。使用者可以自己传入net.Listener,可以传入PacketProtocol, PacketHandler以及SendChanSize。这些参数会在构造Session时传入进去,可以减少重复的代码实现。Server.AcceptLoop不会关闭构造出来的Session,使用者负责完成这件事情!

缺点

整体非常简陋,只是搭了一个模制。在我自己未公开的代码里,其实是实现了我所在公司的协议,实现了PacketProtocol。为此还专门写了个代码生成器。

还有就是NewServer需要传入一个net.Listener,比较蛋疼。后面再决定是否干掉。NewSession需要传入net.Conn,其实是妥协的产物,因为net.Listener返回的就是net.Conn,这个实例需要交给Session使用,不得已而为之,但是这里囧的是,客户端使用的时候,需要自己去net.Dial,得到一个net.Conn,也许该提供一个swnet.Dial方法。

总结

我这个发布的代码是在原有的代码基础上进行了修改,从达达的https://github.com/funny/link中得到了一些启发,但是又有很多的不同。再次感谢达达的贡献。

本文来自:博客园

感谢作者:concurrency

查看原文:用Go实现的简易TCP通信框架


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用Go实现的简易TCP通信框架 的相关文章

  • 深入了解golang 的channel

    文章目录 1 channel 是什么 channel的特点 2 channel 的数据结构 hchan 等待队列和发送队列的类型包装 sudog 3 channel 分类 有缓冲channel 无缓冲channel 4 channel 的创
  • Go语言学习5-切片类型

    切片类型 引言 1 切片 1 1 类型表示法 1 2 值表示法 1 3 属性和基本操作 1 4 切片使用的复杂用法 总结 引言 上篇我们介绍了 Go语言的数组类型 本篇将介绍Go语言的切片类型 主要如下 1 切片 切片可以看作是对数组的一种
  • Sublime Text 3配置Go语言开发环境

    Sublime Text 3配置Go语言开发环境 1 Go语言环境搭建 2 GoSublime安装和配置 2 1 安装步骤 2 2 代码开发 2 3 编译运行 1 Go语言环境搭建 本篇博文是在读者Go自身环境已经搭好 Sublime Te
  • go语言各种hash哈希算法使用汇总(超详细代码)

    目录 前言 一 首先以md4为例 一 16进制字符串的md4 二 字符串的md4 三 16进制字符串 字符串封装 二 md4 md5 sha1 ripemd160 sha256 sha512 一 导包 二 单个使用 三 md4 md5 sh
  • Go语言学习19-样本测试

    样本测试 引言 样本测试 1 编写样本测试函数 2 样本测试的运行 3 样本测试函数的命名 结语 引言 上一篇笔者介绍了 Go 语言的 基准测试 其实在测试源码文件中还可以添加样本测试函数 但编写样本测试函数不需要使用 testing 代码
  • 【笔记】Go语言学习笔记

    一 概述 什么是程序 程序 为了让计算机执行某些操作或解决某个问题而编写的一系列有序指令的集合 Go语言 是区块链最主流的编程语言 同时也是当前最具发展潜力的语言 Go语言是Google公司创造的语言 也是Google主推的语言 Googl
  • Go语言学习17-功能测试

    功能测试 引言 功能测试 1 编写功能测试函数 2 常规记录 3 错误记录 4 致命错误记录 5 失败标记 6 立即失败标记 7 失败判断 8 忽略测试 9 并行运行 10 功能测试的运行 11 关于测试运行的时间 12 测试的并发执行 结
  • pprof 性能分析

    pprof 是一个强大的性能分析工具 可以捕捉到多维度的运行状态的数据 在程序运行过程中可以获取cpu heap block traces等执行信息 一般情况下使用下列三种pprof分析 runtime pprof 采集程序 非 Serve
  • go语言的常见函数

    1 make函数 创建数组切片 Go语言提供的内置函数make 可以用于灵活地创建数组切片 创建一个初始元素个数为5的数组切片 元素初始值为0 mySlice1 make int 5 创建一个初始元素个数为5的数组切片 元素初始值为0 并预
  • go语言学习笔记1--flag代码包

    flag代码包用于接收和解析命令参数 我们以hello world代码作为示例 package main import fmt func main fmt Println hello world 现在 我们想要根据输入定制hello的对象
  • [GO语言基础] 三.变量声明、数据类型、标识符及编程练习12题

    作为网络安全初学者 会遇到采用Go语言开发的恶意样本 因此从今天开始从零讲解Golang编程语言 一方面是督促自己不断前行且学习新知识 另一方面是分享与读者 希望大家一起进步 前文介绍了Go的编译运行 语法规范 注释转义及API标准库知识
  • error An unexpected error occurred: “https://registry.yarnpkg.com/axios: con 解决方案

    error An unexpected error occurred https registry yarnpkg com axios con 今天用在跑一个项目的时候发现了这个错误 看着像是网络连接不上 发现这里是用的Dokcerfile
  • VMware+CentOS7搭建私有云桌面服务

    VMware CentOS7搭建私有云桌面服务 1 安装VMware虚拟机工作台 官网下载安装包 版本 14 1 3 Pro 地址 https my vmware com en web vmware info slug desktop en
  • 解决GO语言编译程序在openwrt(mipsle架构)上运行提示Illegal instruction问题

    RT 最近在研究openwrt mipsle架构 上运行go语言编译出来的程序 一运行就报 Illegal instruction 这样的错误 百度和Google搜索了一遍 得出两种解决方案 PS 更新一遍 当时写这个文档的时候没有发现Go
  • Go单体服务开发最佳实践

    单体最佳实践的由来 对于很多初创公司来说 业务的早期我们更应该关注于业务价值的交付 并且此时用户体量也很小 QPS 也非常低 我们应该使用更简单的技术架构来加速业务价值的交付 此时单体的优势就体现出来了 正如我直播分享时经常提到 我们在使用
  • Go语言学习9-结构体类型

    结构体类型 引言 1 结构体 1 1 类型表示法 1 2 值表示法 1 3 属性和基本操作 附录 引言 书接上篇 我们了解了Go语言的接口类型 现在介绍Go语言的结构体类型 主要如下 1 结构体 结构体类型既可以包含若干个命名元素 又称字段
  • go添加国内镜像加速

    添加国内镜像加速 七牛云 七牛云镜像 全球CDN加速 全球CDN加速 打开你的命令终端输入Go 1 13 及以上 推荐 go env w GO111MODULE on go env w GOPROXY https goproxy cn di
  • go语言exec包调用shell命令

    工程中需要用到ffmpeg 想直接用exec包调用shell命令 本来以为很简单 结果折腾了一下午 最后查到了解决方案 假如之前执行报错的语句为 cmd exec Command echo helloworld out err cmd Ou
  • Golang并发安全和锁

    目录 场景 互斥锁 读写互斥锁 互斥锁 读写锁 Sync Once sync Map 定时器 场景 有时候在Go代码中可能会存在多个goroutine同时操作一个资源 临界区 这种情况会发生竞态问题 数据竞态 类比现实生活中的例子有十字路口
  • Go语言学习4-数组类型

    数组类型 引言 1 数组 1 1 类型表示法 1 2 值表示法 1 3 属性和基本操作 总结 引言 上篇我们了解 Go语言的基本数据类型 现在开始介绍数组类型 主要如下 1 数组 在Go语言中 数组被称为Array 就是一个由若干相同类型的

随机推荐

  • python怎么用print打出赋值_python print 输出赋值到变量

    In 52 import io In 53 row ACME 50 91 5 In 54 join row TypeError Traceback most recent call last in gt 1 join row TypeErr
  • 在 SQL 里描述数据分布情况的时候,有 Cardinality 和 Selectivity 两个概念,有什么区别?

    What is the difference between cardinality and selectivity In SQL cardinality refers to the number of unique values in p
  • 分享一波粉丝面试真题-主要是偏管理方面的

    怎么改善团队低迷现状 改善团队低迷的现状是一个重要的管理挑战 以下是一些可能有助于改善团队状态的方法 深入了解问题 首先 需要了解低迷的原因 这可能涉及与团队成员的个人会谈 收集反馈 观察工作流程等 明确问题的性质对于采取适当的措施至关重要
  • 使用python批量将svg转换成PNG

    CairoSVG介绍 CairoSVG是一个将SVG转为PNG PDF PS格式的库 当前版本的CairoSVG至少需要Python 3 5以上版本 CairoSVG安装和使用 pip install cairosvg 通过命令行你就可以使
  • 数据结构课设:学生信息管理系统(完整版)

    系统介绍 学生信息管理系统是针对学校人事处的大量业务处理工作而开发的管理软件 主要用于学校学生信息管理 总体任务是实现学生信息关系的系统化 科学化 规范化和自动化 其主要任务是用计算机对学生各种信息进行日常管理 如查询 修改 增加 删除 另
  • HTTP协议和web工作原理

    HTTP协议 是web学习的核心 学东东切忌只学配置 不学原理 只学会框架有什么用 要会自己写框架 web学习直接关系到J2EE的学习一 HTTP 超文本传输协议 人类之所发展得如此快 就是因为有自己的语言 1 所谓超文本 即纯文本语言 不
  • 使用git push太慢怎么办

    使用git push太慢怎么办 修改host文件 windows 的路径应该在 C Windows System32 drivers etc hosts 在host文件的最后一行加上 151 101 72 249 github global
  • 现代密码学之安全多方计算

    Secure Multi Party Computation 什么是Secure Multiparty Computation 安全定义 Ideal real model Oblivious transfer OT 1 out of 2 s
  • 拓撲排序學習

    确定比赛名次 Problem Description 有N个比赛队 1 lt N lt 500 编号依次为1 2 3 N进行比赛 比赛结束后 裁判委员会要将所有参赛队伍从前往后依次排名 但现在裁判委员会不能直接获得每个队的比赛成绩 只知道每
  • connection ftp refused_ftp: connect: Connection refused 解决方法

    redhat下使用ftp出现connection refused报错 通过查询是端口未开启的缘故 以下为解决方法 root localhost vsftpd usr sbin vsftpd 500 OOPS vsftpd does not
  • Flutter 版本更新 和 dart SDK版本跟新

    The current Dart SDK version is 2 19 5 Because pivot chat requires SDK version gt 3 0 0 lt 4 0 0 version solving failed
  • 基于 RxJs 的前端数据层实践

    近来前端社区有越来越多的人开始关注前端数据层的设计 DaoCloud 也遇到了这方面的问题 我们调研了很多种解决方案 最终采用 RxJs 来设计一套数据层 这一想法并非我们的首创 社区里已有很多前辈 大牛分享过关于用 RxJs 设计数据层的
  • 通过C实现sqlite3操作,(增删改查),导入电子词典

    一 插入 include
  • Tesseract-OCR下载和安装

    Y26 Tesseract 一款由HP实验室开发由Google维护的开源OCR Optical Character Recognition 光学字符识别 引擎 与Microsoft Office Document Imaging MODI
  • uni-app从入门到上天视频教程 23讲 我终于卷完了

    大家好 我是锋哥 祝大家新年快乐 过年走亲访友 休息了几天 今天把uni app课程卷完了 23讲 免费基础课程 uni app技术介绍 uni app 是一个使用 Vue js 开发所有前端应用的框架 开发者编写一套代码 可发布到iOS
  • STM32之舵机转动————寄存器版

    以STM32F407VGT6单片机为例 控制舵机转动的角度0 180 include pwm h 函数名 tim3 ch4 pwm 函数功能 舵机配置 返回值 void 形参 void 函数说明 红色线 5V 棕色线 GND 橘色线 PB1
  • java.lang.manament API 简介

    java lang management 包提供管理接口用于监控以及管理 JVM 以及 Java 运行时的其他组件 我们开发的 JVM Agent 就是通过这个包提供的接口 收集到 JVM 中包括内存 GC 线程在内的信息 java lan
  • 《JAVA核心知识》学习笔记(JVM)-1

    JVM 1 基本概念 JVM 是可运行 Java 代码的假想计算机 包括一套字节码指令集 一组寄存器 一个栈 一个垃圾回收 堆 和 一个存储方法域 JVM 是运行在操作系统之上的 它与硬件没有直接 的交互 Hotspot JVM 后台运行的
  • Android 实现ListView 文字+ 图片

    1 ListView item 布局
  • 用Go实现的简易TCP通信框架

    接触到GO之后 GO的网络支持非常令人喜欢 GO实现了在语法层面上可以保持同步语义 但是却又没有牺牲太多性能 底层一样使用了IO路径复用 比如在LINUX下用了EPOLL 在WINDOWS下用了IOCP 但是在开发服务端程序的时候 很多都是