tcp 应用层协议选择

 

使用自建协议,还是grpc 等

在Go语言中使用gob(Go binary)进行数据传输时,是否需要使用gRPC(Google Remote Procedure Call)取决于你的具体需求和场景。两者并不相互依赖,但可以结合使用。下面是详细的解释:

Gob

Gob是Go语言自带的序列化和反序列化工具,它可以将Go的数据结构编码成字节流,以便在网络上传输或保存到磁盘。Gob的主要特点是:

  • 高效:Gob是为Go设计的,序列化和反序列化速度快,数据压缩率高。
  • 简便:Go标准库自带,易于使用,无需额外安装依赖。

gRPC

gRPC是一种高性能、开源和通用的RPC框架,它基于HTTP/2协议,并使用Protocol Buffers作为接口描述语言。gRPC的主要特点包括:

  • 多语言支持:gRPC支持多种编程语言,便于跨语言通信。
  • 流式传输:gRPC支持客户端流、服务器流和双向流,适合复杂的通信模式。
  • 负载均衡:gRPC内置支持负载均衡和重试机制。
  • 安全性:gRPC支持基于SSL/TLS的加密通信。

两者结合使用

你可以将Gob和gRPC结合使用,以获得两者的优点。具体来说,你可以使用gRPC作为通信协议框架,同时使用Gob进行数据序列化和反序列化。这样做的好处包括:

  • 高效的二进制传输:Gob的二进制序列化效率高,配合gRPC的传输性能,可以实现高效的数据传输。
  • 标准化的通信接口:gRPC提供了规范化的接口定义和强类型检查,便于维护和扩展。

示例代码

下面是一个简单的示例,展示如何在gRPC中使用Gob进行数据传输:

定义数据结构和服务

go
// data.go package main import ( "bytes" "encoding/gob" "fmt" "net" "google.golang.org/grpc" "google.golang.org/grpc/reflection" ) type MyData struct { Name string Value int } type myService struct{} func (s *myService) SendData(stream MyService_SendDataServer) error { for { in, err := stream.Recv() if err != nil { return err } var data MyData dec := gob.NewDecoder(bytes.NewReader(in.GetData())) if err := dec.Decode(&data); err != nil { return err } fmt.Printf("Received: %+v\n", data) } }

定义gRPC协议

protobuf
// service.proto syntax = "proto3"; service MyService { rpc SendData(stream DataChunk) returns (stream DataChunk); } message DataChunk { bytes data = 1; }

编写客户端和服务器

go
// server.go package main import ( "bytes" "encoding/gob" "log" "net" "google.golang.org/grpc" pb "path/to/your/protobuf/package" ) func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterMyServiceServer(s, &myService{}) reflection.Register(s) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } // client.go package main import ( "bytes" "context" "encoding/gob" "log" "time" "google.golang.org/grpc" pb "path/to/your/protobuf/package" ) func main() { conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() client := pb.NewMyServiceClient(conn) stream, err := client.SendData(context.Background()) if err != nil { log.Fatalf("could not send data: %v", err) } data := &MyData{Name: "example", Value: 123} var buf bytes.Buffer enc := gob.NewEncoder(&buf) if err := enc.Encode(data); err != nil { log.Fatalf("could not encode data: %v", err) } chunk := &pb.DataChunk{Data: buf.Bytes()} if err := stream.Send(chunk); err != nil { log.Fatalf("could not send chunk: %v", err) } time.Sleep(1 * time.Second) stream.CloseSend() }

总结

是否使用gRPC取决于你的需求。如果你需要一个高性能、跨语言、支持复杂通信模式的框架,那么gRPC是一个不错的选择。如果你的需求比较简单,只需在Go服务之间传输数据,那么Gob可能已经足够。但两者结合使用可以充分发挥各自的优势。

=====================================

来自:https://www.cnblogs.com/pengtianyang/p/16415845.html

超文本传输协议(Hyper Text Transfer Protocol,HTTP)是一个简单的请求-响应协议它通常运行在 TCP 之上。

HTTP是一个基于TCP/IP通信协议来传递数据(HTML文件,图片文件,查询结果等).

HTTP协议工作原理

HTTP协议工作于客户端/服务端架构上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。

Web服务器有:Nginx ,Apache服务器,llS服务器等。

Web服务器根据接收到的请求后,向客户端发送响应信息。

HTTP默认端口号为80,但是你也可以改为8080或者其他端口。

 

客户端浏览器通过端口和服务器直接建立TCP连接,在发送一个Http请求,web服务器接收到请求之后将

所需内容返回给客户端,并释放TCP连接,客户端浏览器解析返回的内容

一个请求一个返回

Http三点注意事项:

  1.HTTP是无连接:无连接含义为限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。

这种方式可以节省传输时间。

  Keep-Alive

  2.HTTP是媒体独立的:只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。

客户端以及服务器指定使适合的MIME-type内容类型。

  3.HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,

则它必须重传,这样可能导致每次连接传送的数据星增大。另一方面,在服务器不需要先前信息时它的应答就较快。

 

客户端请求信息及服务端响应信息

客户端发送一个HTTP请求到服务器的请求消息包括以下格式︰

请求行( request line )、请求头部 ( header ) .空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

 文本+ json(binary)

 

 服务器响应消息

HTTP响应也由四个部分组成,分别为:状态行、消息报头、空行和响应正文 

 

 

==============================

来自:https://doc.yonyoucloud.com/doc/wiki/project/the-way-to-go/12.11.html

Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式;可以在 encoding 包中找到。这种格式的数据简称为 Gob (即 Go binary 的缩写)。类似于 Python 的 "pickle" 和 Java 的 "Serialization"。

Gob 通常用于远程方法调用(RPCs,参见 15.9 的 rpc 包)参数和结果的传输,以及应用程序和机器之间的数据传输。 它和 JSON 或 XML 有什么不同呢?Gob 特定地用于纯 Go 的环境中,例如,两个用 Go 写的服务之间的通信。这样的话服务可以被实现得更加高效和优化。 Gob 不是可外部定义,语言无关的编码方式。因此它的首选格式是二进制,而不是像 JSON 和 XML 那样的文本格式。 Gob 并不是一种不同于 Go 的语言,而是在编码和解码过程中用到了 Go 的反射。

Gob 文件或流是完全自描述的:里面包含的所有类型都有一个对应的描述,并且总是可以用 Go 解码,而不需要了解文件的内容。

只有可导出的字段会被编码,零值会被忽略。在解码结构体的时候,只有同时匹配名称和可兼容类型的字段才会被解码。当源数据类型增加新字段后,Gob 解码客户端仍然可以以这种方式正常工作:解码客户端会继续识别以前存在的字段。并且还提供了很大的灵活性,比如在发送者看来,整数被编码成没有固定长度的可变长度,而忽略具体的 Go 类型

 

====================

来自:https://xiaolincoding.com/network/2_http/http_rpc.html#%E4%BC%A0%E8%BE%93%E7%9A%84%E5%86%85%E5%AE%B9

既然有 HTTP 协议,为什么还要有 RPC?

来源:公众号@小白debug

原文地址:既然有 HTTP 协议,为什么还要有 RPC?(opens new window)

我想起了我刚工作的时候,第一次接触 RPC 协议,当时就很懵,我 HTTP 协议用的好好的,为什么还要用 RPC 协议?

于是就到网上去搜。

不少解释显得非常官方,我相信大家在各种平台上也都看到过,解释了又好像没解释,都在用一个我们不认识的概念去解释另外一个我们不认识的概念,懂的人不需要看,不懂的人看了还是不懂。

这种看了,又好像没看的感觉,云里雾里的很难受,我懂。

为了避免大家有强烈的审丑疲劳,今天我们来尝试重新换个方式讲一讲。

#从 TCP 聊起

作为一个程序员,假设我们需要在 A 电脑的进程发一段数据到 B 电脑的进程,我们一般会在代码里使用 Socket 进行编程。

这时候,我们可选项一般也就 TCP 和 UDP 二选一。TCP 可靠,UDP 不可靠。除非是马总这种神级程序员(早期 QQ 大量使用 UDP),否则,只要稍微对可靠性有些要求,普通人一般无脑选 TCP 就对了。

类似下面这样。

fd = socket(AF_INET,SOCK_STREAM,0);

其中 SOCK_STREAM,是指使用字节流传输数据,说白了就是 TCP 协议。

在定义了 Socket 之后,我们就可以愉快的对这个 Socket 进行操作,比如用 bind() 绑定 IP 端口,用 connect() 发起建连。

握手建立连接流程

在连接建立之后,我们就可以使用 send() 发送数据,recv() 接收数据。

光这样一个纯裸的 TCP 连接,就可以做到收发数据了,那是不是就够了?

不行,这么用会有问题。

#使用纯裸 TCP 会有什么问题

八股文常背,TCP 是有三个特点,面向连接、可靠、基于字节流。

TCP 是什么

这三个特点真的概括的非常精辟,这个八股文我们没白背。

每个特点展开都能聊一篇文章,而今天我们需要关注的是基于字节流这一点。

字节流可以理解为一个双向的通道里流淌的数据,这个数据其实就是我们常说的二进制数据,简单来说就是一大堆 01 串。纯裸 TCP 收发的这些 01 串之间是没有任何边界的,你根本不知道到哪个地方才算一条完整消息。

01 二进制字节流

正因为这个没有任何边界的特点,所以当我们选择使用 TCP 发送"夏洛"和"特烦恼"的时候,接收端收到的就是"夏洛特烦恼",这时候接收端没发区分你是想要表达"夏洛"+"特烦恼"还是"夏洛特"+"烦恼"。

消息对比

这就是所谓的粘包问题,之前也写过一篇专门的文章 (opens new window)聊过这个问题。

说这个的目的是为了告诉大家,纯裸 TCP 是不能直接拿来用的,你需要在这个基础上加入一些自定义的规则,用于区分消息边界。

于是我们会把每条要发送的数据都包装一下,比如加入消息头,消息头里写清楚一个完整的包长度是多少,根据这个长度可以继续接收数据,截取出来后它们就是我们真正要传输的消息体。

消息边界长度标志

而这里头提到的消息头,还可以放各种东西,比如消息体是否被压缩过和消息体格式之类的,只要上下游都约定好了,互相都认就可以了,这就是所谓的协议。

每个使用 TCP 的项目都可能会定义一套类似这样的协议解析标准,他们可能有区别,但原理都类似。

于是基于 TCP,就衍生了非常多的协议,比如 HTTP 和 RPC。

#HTTP 和 RPC

我们回过头来看网络的分层图。

四层网络协议

TCP 是传输层的协议,而基于 TCP 造出来的 HTTP 和各类 RPC 协议,它们都只是定义了不同消息格式的应用层协议而已。

HTTP 协议(Hyper Text Transfer Protocol),又叫做超文本传输协议。我们用的比较多,平时上网在浏览器上敲个网址就能访问网页,这里用到的就是 HTTP 协议。

HTTP调用

而 RPC(Remote Procedure Call),又叫做远程过程调用。它本身并不是一个具体的协议,而是一种调用方式。

举个例子,我们平时调用一个本地方法就像下面这样。

 res = localFunc(req)

如果现在这不是个本地方法,而是个远端服务器暴露出来的一个方法 remoteFunc,如果我们还能像调用本地方法那样去调用它,这样就可以屏蔽掉一些网络细节,用起来更方便,岂不美哉?

 res = remoteFunc(req)

RPC可以像调用本地方法那样调用远端方法

基于这个思路,大佬们造出了非常多款式的 RPC 协议,比如比较有名的gRPCthrift

值得注意的是,虽然大部分 RPC 协议底层使用 TCP,但实际上它们不一定非得使用 TCP,改用 UDP 或者 HTTP,其实也可以做到类似的功能。

基于TCP协议的HTTP和RPC协议

到这里,我们回到文章标题的问题。

既然有 HTTP 协议,为什么还要有 RPC?

其实,TCP 是70年代出来的协议,而 HTTP 是 90 年代才开始流行的。而直接使用裸 TCP 会有问题,可想而知,这中间这么多年有多少自定义的协议,而这里面就有80年代出来的 RPC

所以我们该问的不是既然有 HTTP 协议为什么要有 RPC,而是为什么有 RPC 还要有 HTTP 协议。

那既然有 RPC 了,为什么还要有 HTTP 呢?

现在电脑上装的各种联网软件,比如 xx管家,xx卫士,它们都作为客户端(Client)需要跟服务端(Server)建立连接收发消息,此时都会用到应用层协议,在这种 Client/Server (C/S) 架构下,它们可以使用自家造的 RPC 协议,因为它只管连自己公司的服务器就 ok 了。

但有个软件不同,浏览器(Browser),不管是 Chrome 还是 IE,它们不仅要能访问自家公司的服务器(Server),还需要访问其他公司的网站服务器,因此它们需要有个统一的标准,不然大家没法交流。于是,HTTP 就是那个时代用于统一 Browser/Server (B/S) 的协议。

也就是说在多年以前,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。很多软件同时支持多端,比如某度云盘,既要支持网页版,还要支持手机端和 PC 端,如果通信协议都用 HTTP 的话,那服务器只用同一套就够了。而 RPC 就开始退居幕后,一般用于公司内部集群里,各个微服务之间的通讯。

那这么说的话,都用 HTTP 得了,还用什么 RPC?

仿佛又回到了文章开头的样子,那这就要从它们之间的区别开始说起。

#HTTP 和 RPC 有什么区别

我们来看看 RPC 和 HTTP 区别比较明显的几个点。

#服务发现

首先要向某个服务器发起请求,你得先建立连接,而建立连接的前提是,你得知道 IP 地址和端口。这个找到服务对应的 IP 端口的过程,其实就是服务发现。

在 HTTP 中,你知道服务的域名,就可以通过 DNS 服务去解析得到它背后的 IP 地址,默认 80 端口。

而 RPC 的话,就有些区别,一般会有专门的中间服务去保存服务名和IP信息,比如 Consul 或者 Etcd,甚至是 Redis。想要访问某个服务,就去这些中间服务去获得 IP 和端口信息。由于 DNS 也是服务发现的一种,所以也有基于 DNS 去做服务发现的组件,比如CoreDNS。

可以看出服务发现这一块,两者是有些区别,但不太能分高低。

#底层连接形式

以主流的 HTTP/1.1 协议为例,其默认在建立底层 TCP 连接之后会一直保持这个连接(Keep Alive),之后的请求和响应都会复用这条连接。

而 RPC 协议,也跟 HTTP 类似,也是通过建立 TCP 长链接进行数据交互,但不同的地方在于,RPC 协议一般还会再建个连接池,在请求量大的时候,建立多条连接放在池内,要发数据的时候就从池里取一条连接出来,用完放回去,下次再复用,可以说非常环保。

connection_pool

由于连接池有利于提升网络请求性能,所以不少编程语言的网络库里都会给 HTTP 加个连接池,比如 Go 就是这么干的。

可以看出这一块两者也没太大区别,所以也不是关键。

#传输的内容

基于 TCP 传输的消息,说到底,无非都是消息头 Header 和消息体 Body。

Header 是用于标记一些特殊信息,其中最重要的是消息体长度。

Body 则是放我们真正需要传输的内容,而这些内容只能是二进制 01 串,毕竟计算机只认识这玩意。所以 TCP 传字符串和数字都问题不大,因为字符串可以转成编码再变成 01 串,而数字本身也能直接转为二进制。但结构体呢,我们得想个办法将它也转为二进制 01 串,这样的方案现在也有很多现成的,比如 Json,Protobuf。

这个将结构体转为二进制数组的过程就叫序列化,反过来将二进制数组复原成结构体的过程叫反序列化。

序列化和反序列化

对于主流的 HTTP/1.1,虽然它现在叫超文本协议,支持音频视频,但 HTTP 设计初是用于做网页文本展示的,所以它传的内容以字符串为主。Header 和 Body 都是如此。在 Body 这块,它使用 Json 来序列化结构体数据。

我们可以随便截个图直观看下。

HTTP 报文

可以看到这里面的内容非常多的冗余,显得非常啰嗦。最明显的,像 Header 里的那些信息,其实如果我们约定好头部的第几位是 Content-Type,就不需要每次都真的把"Content-Type"这个字段都传过来,类似的情况其实在 body 的 Json 结构里也特别明显。

而 RPC,因为它定制化程度更高,可以采用体积更小的 Protobuf 或其他序列化协议去保存结构体数据,同时也不需要像 HTTP 那样考虑各种浏览器行为,比如 302 重定向跳转啥的。因此性能也会更好一些,这也是在公司内部微服务中抛弃 HTTP,选择使用 RPC 的最主要原因。

HTTP 原理

RPC 原理

当然上面说的 HTTP,其实特指的是现在主流使用的 HTTP/1.1,HTTP/2 在前者的基础上做了很多改进,所以性能可能比很多 RPC 协议还要好,甚至连 gRPC 底层都直接用的 HTTP/2

那么问题又来了,为什么既然有了 HTTP/2,还要有 RPC 协议?

这个是由于 HTTP/2 是 2015 年出来的。那时候很多公司内部的 RPC 协议都已经跑了好些年了,基于历史原因,一般也没必要去换了。

#总结

  • 纯裸 TCP 是能收发数据,但它是个无边界的数据流,上层需要定义消息格式用于定义消息边界。于是就有了各种协议,HTTP 和各类 RPC 协议就是在 TCP 之上定义的应用层协议。
  • RPC 本质上不算是协议,而是一种调用方式,而像 gRPC 和 Thrift 这样的具体实现,才是协议,它们是实现了 RPC 调用的协议。目的是希望程序员能像调用本地方法那样去调用远端的服务方法。同时 RPC 有很多种实现方式,不一定非得基于 TCP 协议。
  • 从发展历史来说,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。很多软件同时支持多端,所以对外一般用 HTTP 协议,而内部集群的微服务之间则采用 RPC 协议进行通讯。
  • RPC 其实比 HTTP 出现的要早,且比目前主流的 HTTP/1.1 性能要更好,所以大部分公司内部都还在使用 RPC。
  • HTTP/2.0 在 HTTP/1.1 的基础上做了优化,性能可能比很多 RPC 协议都要好,但由于是这几年才出来的,所以也不太可能取代掉 RPC。

 

======================

来自:https://andrewpqc.github.io/2019/02/24/thrift/

Thrift整体架构图:

Thrift是一套包含序列化功能和支持服务通信的RPC框架,主要包含三大部分:代码生成、序列化框架、RPC框架,大致相当于protoc + protobuffer + grpc,并且支持大量语言,保证常用功能在跨语言间功能一致,是一套全栈式的RPC解决方案。Thrift最初由FaceBook开发,之后由ASF管理。

 

posted @ 2024-06-18 14:10  redrobot  阅读(6)  评论(0编辑  收藏  举报