Go搭建TcpSocket服务器
1.net包的强大功能
不可否认,go在网络服务开发有强大的优势。net库是一个功能强大的网络编程库,它提供了构建TCP、UDP和HTTP服务器和客户端所需的所有基础工具。
例如,搭建tcp服务器,只需要几行代码。
func main() {listener, err := net.Listen("tcp", ":8080")if err != nil {log.Fatal(err)}for {conn, err := listener.Accept()if err != nil {log.Print(err)continue}go handleConnection(conn)}
}func handleConnection(conn net.Conn) {defer conn.Close()// 处理连接...
}
搭建udp服务器
func main() {addr, err := net.ResolveUDPAddr("udp", ":8080")if err != nil {log.Fatal(err)}conn, err := net.ListenUDP("udp", addr)if err != nil {log.Fatal(err)}defer conn.Close()buffer := make([]byte, 1024)for {n, remoteAddr, err := conn.ReadFromUDP(buffer)if err != nil {log.Print(err)continue}// 处理数据...}
}
搭建http服务器,使用net/http子模块
func main() {http.HandleFunc("/", handler)log.Fatal(http.ListenAndServe(":8080", nil))
}func handler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello, World!")
}
2. java搭建tcp服务器
2.1.使用原生API的挑战
虽然利用java原生的NIOAPI也可以进行socket开发,但存在诸如以下的缺陷与挑战:
- 编程复杂性:NIO的编程模型相对复杂,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等类库和API。开发者需要处理多路复用、事件循环、非阻塞读写等操作,这增加了编程的难度和代码的复杂性。
- 可靠性和稳定性问题:原生NIO需要开发者自己处理网络的闪断、客户端的重复接入、客户端的安全认证、消息的编解码、半包读写等情况,如果没有足够的NIO编程经验积累,一个NIO框架的稳定往往需要半年甚至更长的时间。
- 线程管理:NIO编程涉及到Reactor模式,必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序。线程的管理和调度也是一个挑战,尤其是在高并发场景下。
- JDK NIO的BUG:例如臭名昭著的Epoll Bug,它会导致Selector空轮询,最终导致CPU 100%。虽然在JDK 1.7中有所改善,但并未完全解决。
- 资源消耗:使用内存占用较高,使用直接内存缓冲区可能导致较高的内存消耗,尤其是在处理大量数据时。
正因如此,mina,netty这类高效的NIO框架应运而生。然而,netty,mina的学习成本也不小。
2.2.netty编解码示例
下面看下,netty如何处理tcp网络连接,主要是黏包/拆包逻辑。
解码器:
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {if (in.readableBytes() < DefaultMessageHeader.SIZE) {return;}in.markReaderIndex();// ----------------protocol pattern-------------------------// header(12bytes) | body// msgLength = 12+len(body) | body// msgLength | index | cmd | bodybyte[] header = new byte[DefaultMessageHeader.SIZE];in.readBytes(header);DefaultMessageHeader headerMeta = new DefaultMessageHeader();headerMeta.read(header);int length = headerMeta.getMsgLength();if (length > maxProtocolBytes) {logger.error("message data frame [{}] too large, close session now", length);ctx.close();return;}int bodySize = length - DefaultMessageHeader.SIZE;if (in.readableBytes() < bodySize) {in.resetReaderIndex();return;}int cmd = headerMeta.getCmd();byte[] body = new byte[bodySize];in.readBytes(body);Class<?> msgClazz = messageFactory.getMessage(cmd);Object message = messageCodec.decode(msgClazz, body);out.add(new RequestDataFrame(headerMeta, message));}
编码器:
@Overrideprotected void encode(ChannelHandlerContext ctx, Object message, ByteBuf out) throws Exception {assert message instanceof SocketDataFrame;SocketDataFrame dataFrame = (SocketDataFrame) message;// ----------------protocol pattern-------------------------// header(12bytes) | body// msgLength = 12+len(body) | body// msgLength | index | cmd | bodyint cmd = messageFactory.getMessageId(dataFrame.getMessage().getClass());try {byte[] body = messageCodec.encode(dataFrame.getMessage());// 写入包头//消息内容长度int msgLength = body.length + DefaultMessageHeader.SIZE;out.writeInt(msgLength);out.writeInt(dataFrame.getIndex());// 写入cmd类型out.writeInt(cmd);// 写入包体out.writeBytes(body);} catch (Exception e) {logger.error("wrote message {} failed", cmd, e);}}
2.3.创建socket服务器
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel arg0) throws Exception {ChannelPipeline pipeline = arg0.pipeline();if (protocolDecoder != null) {pipeline.addLast("protocolDecoder",protocolDecoder);} else {pipeline.addLast("protocolDecoder", new DefaultProtocolDecoder(messageFactory, messageCodec, maxProtocolBytes));}if (protocolEncoder != null) {pipeline.addLast("protocolEncoder", protocolEncoder);} else {pipeline.addLast("protocolEncoder", new DefaultProtocolEncoder(messageFactory, messageCodec));}if (idleTime > 0) {// 客户端XXX没收发包,便会触发UserEventTriggered事件到IdleEventHandlerpipeline.addLast(new IdleStateHandler(0, 0, idleTime,TimeUnit.MILLISECONDS));pipeline.addLast("serverIdleHandler", serverIdleHandler);}pipeline.addLast("socketIoHandler", channelIoHandler);}}
总体来说,还是挺复杂的,需要对netty的api有一定的了解。
3. go搭建tcp服务器
3.1私有协议栈编解码
Tcp传输层在网络传递的数据是一堆字节码,类似于水管的流水,没有边界。由于传输层不是应用层,没有公有协议,也就没有统一的通信格式,都是应用程序内部定义的,只要服务器与客户端遵守同样的规则即可,因此这种协议也叫“私有协议”。
import ("bytes""errors"
)// Protocol constants.
const (HeadLength = 8MaxPacketSize = 64 * 1024
)type MessageHeader struct {Cmd int //消息类型Size int //消息长度
}// ErrPacketSizeExceed is the error used for encode/decode.
var ErrPacketSizeExceed = errors.New("Protocol: packet size exceed")type Protocol struct {buf *bytes.Buffer
}// NewDecoder returns a new decoder that used for decode network bytes slice.
func NewDecoder() *Protocol {return &Protocol{buf: bytes.NewBuffer(nil),}
}func (c *Protocol) readHeader() (*MessageHeader, error) {buff := c.buf.Next(HeadLength)id := bytesToInt(buff[0:4])size := bytesToInt(buff[4:HeadLength])// packet length limitationif size > MaxPacketSize {return nil, ErrPacketSizeExceed}return &MessageHeader{Cmd: id, Size: size}, nil
}func (c *Protocol) Decode(data []byte) ([]*Packet, error) {c.buf.Write(data)// check lengthif c.buf.Len() < HeadLength {return nil, errors.New("length too small")}var packets []*Packetfor c.buf.Len() > 0 {header, err := c.readHeader()if err != nil {return packets, err}if header.Size <= c.buf.Len() {body := c.buf.Next(header.Size)p := &Packet{Header: *header, Data: body}packets = append(packets, p)} else {break}}return packets, nil
}func (c *Protocol) Encode(cmd int, data []byte) ([]byte, error) {// p := &Packet{Cmd: cmd, Length: len(data)}bodyLen := len(data)buf := make([]byte, HeadLength+bodyLen)copy(buf[0:4], intToBytes(cmd))copy(buf[4:HeadLength], intToBytes(bodyLen))copy(buf[HeadLength:], data)return buf, nil
}// Decode packet data length byte to int(Big end)
func bytesToInt(b []byte) int {result := 0for _, v := range b {result = result<<8 + int(v)}return result
}func intToBytes(n int) []byte {buf := make([]byte, 4)buf[0] = byte((n >> 24) & 0xFF)buf[1] = byte((n >> 16) & 0xFF)buf[2] = byte((n >> 8) & 0xFF)buf[3] = byte(n & 0xFF)return buf
}
大致逻辑:
每次链接建立时,将缓存区的二进制流转移到session自己的缓存区(Protocol#buf字段),在循环内部,每次读取包头8个字节,如果剩余的字节树超过包体的长度,则接取对应的长度,反序列化为一个完整的消息包,再转交给下一步的消息解码器。这个流程只依赖go的内部api,完全无需引入其他依赖,非常漂亮。
3.2.tcp服务器
package networkimport ("errors""fmt""github.com/gorilla/websocket""log""net""net/http""reflect"
)type Node struct {Name string // 服务器名称option Options
}func (n *Node) Startup(opts ...Option) error {// 设置参数opt := Options{}for _, option := range opts {option(&opt)}n.option = optif n.option.ServiceAddr == "" {return errors.New("service address cannot be empty in master node")}go func() {n.listenTcpConn()}()return nil
}// Enable current server accept connection
func (n *Node) listenTcpConn() {listener, err := net.Listen("tcp", n.option.ServiceAddr)if err != nil {log.Fatal(err.Error())}defer listener.Close()for {conn, err := listener.Accept()if err != nil {log.Println(err.Error())continue}go handleClient(n, conn)}
}// 处理客户端连接,包括socket,websocket
func handleClient(node *Node, conn net.Conn) {defer conn.Close() // 确保在函数结束时关闭连接ioSession := NewSession(&conn, node.option.MessageCodec)// 异步向客户端写数据go ioSession.Write()// read loopbuf := make([]byte, 2048)for {n, err := conn.Read(buf)if err != nil {log.Printf(fmt.Sprintf("Read message error: %s, session will be closed immediately", err.Error()))return}if n <= 0 {continue}packets, err := ioSession.ProtocolCodec.Decode(buf[:n])if err != nil {log.Println(err.Error())return}// process packets decodedfor _, p := range packets {typ, _ := GetMessageType(p.Header.Cmd)msg := reflect.New(typ.Elem()).Interface()err := node.option.MessageCodec.Decode(p.Data, msg)if err != nil {log.Println(err.Error())continue}ioFrame := &RequestDataFrame{Header: p.Header, Msg: msg}node.option.IoDispatch.OnMessageReceived(ioSession, *ioFrame)}}
}
完整代码请移步:
--> go游戏服务器
相关文章:
Go搭建TcpSocket服务器
1.net包的强大功能 不可否认,go在网络服务开发有强大的优势。net库是一个功能强大的网络编程库,它提供了构建TCP、UDP和HTTP服务器和客户端所需的所有基础工具。 例如,搭建tcp服务器,只需要几行代码。 func main() {listener, …...
hadoop3跑第一个例子wordcount
1、创建目录 hdfs dfs -mkdir -p /user/input2、创建测试文件,并上传文件到hdfs echo 1 > 1.txt hdfs dfs -put 1.txt /user/input3、进入hadoop-3目录,并创建测试文件 cd /app/hadoop-3创建目录 mkdir wcinput cd wcinput 保存wc.input nano wc.i…...
Maven笔记(二):进阶使用
Maven笔记(二)-进阶使用 一、Maven分模块开发 分模块开发对项目的扩展性强,同时方便其他项目引入相同的功能。 将原始模块按照功能拆分成若干个子模块,方便模块间的相互调用,接口共享(类似Jar包一样之间引用、复用)…...
Apache ZooKeeper 及 Curator 使用总结
1. 下载 官网地址:Apache ZooKeeper 点击下载按钮 选择对应的版本进行下载 2. 使用 1、解压 tar -zxf apache-zookeeper-3.9.2-bin.tar.gz2、复制配置文件,有一个示例配置文件 conf/zoo_sample.cfg,此文件不能生效,需要名称为…...
深入探索:MATLAB中的硬件支持包(HSP)及其应用
在MATLAB环境中,硬件支持包(HSP)扮演着至关重要的角色,尤其是在与硬件交互和嵌入式系统开发方面。HSP提供了一套工具和库,使得MATLAB能够与特定的硬件平台进行有效通信,实现代码的生成和优化。本文将详细介…...
5.内容创作的未来:ChatGPT如何辅助写作(5/10)
引言 在信息爆炸的时代,内容创作已成为连接品牌与受众、传递信息与知识、以及塑造文化与观念的重要手段。随着数字媒体的兴起,内容创作的需求日益增长,对创作者的写作速度和质量提出了更高的要求。人工智能(AI)技术的…...
Day26_0.1基础学习MATLAB学习小技巧总结(26)——数据插值
利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍,为了在这个过程中加深印象,也为了能够有所足迹,我会把自己的学习总结发在专栏中,以便学习交流。 参考书目: 1、《MATLAB基础教程 (第三版) (薛山)》 2、《MATL…...
SQL进阶技巧:火车票相邻座位预定一起可能情况查询算法 ?
目录 0 场景描述 1 数据准备 2 问题分析 2.1 分析函数法 2.2 自关联求解 3 小结...
神经网络构建原理(以MINIST为例)
神经网络构建原理(以MINIST为例) 在 MNIST 手写数字识别任务中,构建神经网络并训练模型来进行分类是经典的深度学习应用。MNIST 数据集包含 28x28 像素的手写数字图像(0-9),任务是构建一个神经网络,能够根据输入的图像…...
【ArcGIS微课1000例】0123:数据库中要素类批量转为shapefile
除了ArcGIS之外的其他GIS平台,想要打开ArcGIS数据库,可能无法直接打开,为了便于使用shp,建议直接将数据库中要素类批量转为shapefile。 文章目录 一、连接至数据库二、要素批量转shp一、连接至数据库 打开ArcMap,或者打开ArcCatalog,找到数据库连接,如下图: 数据库为个…...
【Elasticsearch系列十九】评分机制详解
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
神经网络通俗理解学习笔记(3)注意力神经网络
Tansformer 什么是注意力机制注意力的计算键值对注意力和多头注意力自注意力机制注意力池化及代码实现Transformer模型Transformer代码实现BERT 模型GPT 系列模型GPT-1模型思想GPT-2模型思想GPT-3 模型思想 T5模型ViT模型Swin Transformer模型GPT模型代码实现 什么是注意力机制…...
【C#】 EventWaitHandle的用法
EventWaitHandle 是 C# 中用于线程间同步的一个类,它提供了对共享资源的访问控制,以及线程间的同步机制。EventWaitHandle 类位于 System.Threading 命名空间下,主要用于实现互斥访问、信号量控制等场景。 创建 EventWaitHandle 创建一个 E…...
设计模式之结构型模式例题
答案:A 知识点 创建型 结构型 行为型模式 工厂方法模式 抽象工厂模式 原型模式 单例模式 构建器模式 适配器模式 桥接模式 组合模式 装饰模式 外观模式 享元模式 代理模式 模板方法模式 职责链模式 命令模式 迭代器模式 中介者模式 解释器模式 备忘录模式 观…...
camtasia2024绿色免费安装包win+mac下载含2024最新激活密钥
Hey, hey, hey!亲爱的各位小伙伴,今天我要给大家带来的是Camtasia2024中文版本,这款软件简直是视频制作爱好者的福音啊! camtasia2024绿色免费安装包winmac下载,点击链接即可保存。 先说说这个版本新加的功能吧&#…...
如何导入一个Vue并成功运行
注意1:要确保自己已经成功创建了一个Vue项目,创建项目教程在如何创建Vue项目 注意2:以下操作均在VS Code,教程在VS Code安装教程 一、Vue项目导入VS Code 1.点击文件,然后点击将文件添加到工作区 2. 选择自己的vue项…...
封装svg图片
前言 项目中有大量svg图片,为了方便引入,所以对svg进行了处理 一、svg是什么? svg是可缩放矢量图形,是一种图片格式 二、使用步骤 1.创建icons文件夹 将icons文件夹放进src中,并创建一个svg文件夹和index.js&…...
tomcat的Catalinalog和localhostlog乱码
找到tomcat安装目录的loging文件 乱码这两个由UTF-8改为GBK...
行人持刀检测数据集 voc yolo
行人持刀检测数据集 9000张 持刀检测 带标注 voc yolo 行人持刀检测数据集 数据集描述 该数据集旨在用于行人持刀行为的检测任务,涵盖了多种场景下的行人图像,特别是那些携带刀具的行人。数据集包含大量的图像及其对应的标注信息,可用于训练…...
基于51单片机的汽车倒车防撞报警器系统
目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 本课题基于微控制器控制器, 设计一款汽车倒车防撞报警器系统。 要求: 要求:1.配有距离, 用于把车和障碍物之间的距离信号送入控制器。 2.配有报警系…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...
