转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

我正在参与掘金技术社区创作者签约计划招募活动,
点击链接报名投稿。
我想起了我刚工作的时候,第一次接触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是有三个特点,面向连接、可靠、基于字节流。

这三个特点真的概括的非常精辟,这个八股文我们没白背。
每个特点展开都能聊一篇文章,而今天我们需要关注的是基于字节流这一点。
字节流可以理解为一个双向的通道里流淌的数据,这个数据其实就是我们常说的二进制数据,简单来说就是一大堆 01 串。纯裸TCP收发的这些 01 串之间是没有任何边界的,你根本不知道到哪个地方才算一条完整消息。

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

这就是所谓的粘包问题,之前也写过一篇专门的
文章聊过这个问题。
说这个的目的是为了告诉大家,纯裸TCP是不能直接拿来用的,你需要在这个基础上加入一些自定义的规则,用于区分消息边界。
于是我们会把每条要发送的数据都包装一下,比如加入消息头,消息头里写清楚一个完整的包长度是多少,根据这个长度可以继续接收数据,截取出来后它们就是我们真正要传输的消息体。

而这里头提到的消息头,还可以放各种东西,比如消息体是否被压缩过和消息体格式之类的,只要上下游都约定好了,互相都认就可以了,这就是所谓的协议。
每个使用TCP的项目都可能会定义一套类似这样的协议解析标准,他们可能有区别,但原理都类似。
于是基于TCP,就衍生了非常多的协议,比如HTTP和RPC。
HTTP和RPC#
我们回过头来看网络的分层图。

TCP是传输层的协议,而基于TCP造出来的HTTP和各类RPC协议,它们都只是定义了不同消息格式的应用层协议而已。
HTTP协议(Hyper Text Transfer Protocol),又叫做超文本传输协议。我们用的比较多,平时上网在浏览器上敲个网址就能访问网页,这里用到的就是HTTP协议。

而RPC(Remote Procedure Call),又叫做远程过程调用。它本身并不是一个具体的协议,而是一种调用方式。
举个例子,我们平时调用一个本地方法就像下面这样。
res = localFunc(req)
复制代码
如果现在这不是个本地方法,而是个远端服务器暴露出来的一个方法remoteFunc,如果我们还能像调用本地方法那样去调用它,这样就可以屏蔽掉一些网络细节,用起来更方便,岂不美哉?