blog icon indicating copy to clipboard operation
blog copied to clipboard

动手写RPC框架 - GeeRPC第五天 支持HTTP协议 | 极客兔兔

Open geektutu opened this issue 3 years ago • 39 comments

https://geektutu.com/post/geerpc-day5.html

7天用 Go语言/golang 从零实现 RPC 框架 GeeRPC 教程(7 days implement golang remote procedure call framework from scratch tutorial),动手写 RPC 框架,参照 golang 标准库 net/rpc 的实现,实现了服务端(server)、支持异步和并发的客户端(client)、消息编码与解码(message encoding and decoding)、服务注册(service register)、支持 TCP/Unix/HTTP 等多种传输协议。第五天支持了 HTTP 协议,并且提供了一个简单的 DEBUG 页面。

geektutu avatar Oct 08 '20 09:10 geektutu

我又来了,周末把这一篇实践完了,感觉有点晕😂,这一章做的是:

  1. Server实现handler接口,只接受HTTP CONNECT的请求,并Hijack这个http的tcp连接来做Client和Server之间通信的conn,而之前是直接用Server Accept一个TCP listener,然后做通信。
  2. debugHTTP持Server变量,实现handler接口,处理函数里用持有的Server变量做一些debug相关的统计,这时候可以通过HTTP请求获取到对应的Server的一些调用状态。

所以看起来更像是让client可以通过HTTP CONNECT方法来创建RPC C/S之间的连接吗,这个对于RPC框架的使用者是不是透明的? 比如我使用这个RPC框架,调用的时候还是创建Client,然后调用CallMethod,还是这种函数的调用方式。

多谢多谢🙏

furthergo avatar Oct 12 '20 10:10 furthergo

@furthergo

HTTP 协议转化为 RPC 协议的过程是包装了的,使用者不感知,客户端的协议转换过程已经在 NewHTTPClient 里实现了。main.go 里面有使用示例的。

// 服务端
geerpc.HandleHTTP()
// 客户端
client, _ := geerpc.DialHTTP("tcp", <-addrCh)

geektutu avatar Oct 12 '20 12:10 geektutu

大佬,来一个grpc的

sockstack avatar Oct 14 '20 01:10 sockstack

@sockstack 感谢推荐,grpc 过于复杂了,不过后续可以考虑下。

geektutu avatar Oct 15 '20 10:10 geektutu

@geektutu @sockstack 感谢推荐,grpc 过于复杂了,不过后续可以考虑下。

grpc +1!!!大佬这几个系列太赞了👍

TDTzzz avatar Oct 26 '20 15:10 TDTzzz

@TDTzzz 感谢认可,笔芯~

geektutu avatar Oct 28 '20 10:10 geektutu

请教一下,不是太理解支持HTTP协议的意思。看起来是建立tcp连接后客户端发送了一个HTTP包(CONNECT方法)给服务端,之后的客户端的RPC请求应该就是普通的tcp包了?

ppd0705 avatar Nov 30 '20 04:11 ppd0705

@ppd0705 一般来说,http 是基于 tcp的,未来可能会有不基于 tcp 实现的 http。所以客户端通过在 TCP 上包一层 HTTP 协议来支持 HTTP。使用 HTTP 有很多好处,比如监听一个 tcp 端口,可以使用不同的 PATH 支持不同的 HTTP 服务。

你可以把 HTTP 协议理解为一种协议协商的方式,客户端和服务端一侧做编码,一侧做解码就好了,除了 HTTP 协议头,后面的报文是没有变化的。

geektutu avatar Nov 30 '20 07:11 geektutu

请问XDial测试里面,addr := "/tmp/geerpc.sock"。之后remove了这个addr。这一块是什么写法

andcarefree avatar Dec 31 '20 03:12 andcarefree

请问XDial测试里面,addr := "/tmp/geerpc.sock"。之后remove了这个addr。这一块是什么写法

@andcarefree net.Listen 监听 unix socket 时,会创建这个文件。如果这个文件存在,可能会监听失败,监听前删除比较安全。

geektutu avatar Dec 31 '20 14:12 geektutu

		go func() {
			_ = os.Remove(addr)
			l, err := net.Listen("unix", addr)
			if err != nil {
				t.Fatal("failed to listen unix socket")
			}
			ch <- struct{}{}
			Accept(l)
		}()

XDail测试代码的这一段中,如果net.Listen真的报错了,t.Fatal之后不是会让主协程一直阻塞吗,就ch信道一直收不到那个空结构体

andcarefree avatar Jan 01 '21 09:01 andcarefree

想请教一下,conn, _, err := w.(http.Hijacker).Hijack()这一步是在做什么呢

JesseStutler avatar Mar 17 '21 03:03 JesseStutler

@JesseStutler 想请教一下,conn, _, err := w.(http.Hijacker).Hijack()这一步是在做什么呢

获取tcp 套接字,http协议也是基于tcp协议的 。注意下面的ServeConn 这个方法 他的参数是什么类型的,这就是他要从http链接中获取套接字的原因。

wilgx0 avatar Mar 24 '21 03:03 wilgx0

@ppd0705 请教一下,不是太理解支持HTTP协议的意思。看起来是建立tcp连接后客户端发送了一个HTTP包(CONNECT方法)给服务端,之后的客户端的RPC请求应该就是普通的tcp包了?

谈一下我个人的看法, http协议从应用层编程的角度,我把他简单的理解成一个种数据格式, 服务器端和客服端之间的请求和响应需要按照这种数据格式来发送数据,数据格式详见:https://www.runoob.com/http/http-messages.html。 HTTP协议基于tpc但是它在应用层是感知不到TCP的存在的。 所以不存在先发了一个http包,之后的请求应该是tcp包的说法。兔老大这里只是用http 协议完成了一个握手的过程,然后在使用自己的定义的协议(详见Day1 服务端与消息编码 中的通信过程)来进行双方的通讯。

wilgx0 avatar Mar 24 '21 08:03 wilgx0

请问大佬能不能把http1改为http2,如果能试试3就是黑科技了

gnehcein avatar Apr 09 '21 13:04 gnehcein

建议最后改成用XDial拨号,另外xdial必须http@...才能等价于dialHTTP(“tcp”,...),感觉有点奇怪。。

gnehcein avatar Apr 10 '21 02:04 gnehcein

@andcarefree

		go func() {
			_ = os.Remove(addr)
			l, err := net.Listen("unix", addr)
			if err != nil {
				t.Fatal("failed to listen unix socket")
			}
			ch <- struct{}{}
			Accept(l)
		}()

XDail测试代码的这一段中,如果net.Listen真的报错了,t.Fatal之后不是会让主协程一直阻塞吗,就ch信道一直收不到那个空结构体

wbjy avatar May 24 '21 14:05 wbjy

我试了一下好像死锁了

wbjy avatar May 24 '21 14:05 wbjy

const (
	connected        = "200 Connected to Gee RPC"
	defaultRPCPath   = "/_geeprc_"
	defaultDebugPath = "/debug/geerpc"
)

这里打错字了 应该是geerpc

nanfeng1999 avatar Jul 10 '21 12:07 nanfeng1999

switch protocol {
	case "http":
		return DialHTTP("tcp", addr, opts...)

明明是http请求为什么在dialhttp用的还是tcp

imtzer avatar Jul 15 '21 06:07 imtzer

go1.12 报错了,读不到返回的请求- -

lwb0214 avatar Jul 26 '21 07:07 lwb0214

@goder-tao

switch protocol {
	case "http":
		return DialHTTP("tcp", addr, opts...)

明明是http请求为什么在dialhttp用的还是tcp

http目前是基于tcp的

src/net/dial.go: The network must be "tcp", "tcp4", "tcp6", "unix" or "unixpacket".

yqchilde avatar Aug 17 '21 08:08 yqchilde

defaultRPCPath   = "/_geeprc_"

这里不该叫/geerpc

wangxso avatar Sep 06 '21 06:09 wangxso

这里踩了个坑。ServeHTTP最后写入"HTTP/1.0 "应该有个空格,一开始省略了这个导致一直rpc server: options error: EOF 另外这里要往响应里写入数据,用io的WriteString是不是不太好,为什么不用ResponseWriter的Write方法呢

whitebluepants avatar Nov 01 '21 04:11 whitebluepants

@wilgx0

@ppd0705 请教一下,不是太理解支持HTTP协议的意思。看起来是建立tcp连接后客户端发送了一个HTTP包(CONNECT方法)给服务端,之后的客户端的RPC请求应该就是普通的tcp包了?

谈一下我个人的看法, http协议从应用层编程的角度,我把他简单的理解成一个种数据格式, 服务器端和客服端之间的请求和响应需要按照这种数据格式来发送数据,数据格式详见:https://www.runoob.com/http/http-messages.html。 HTTP协议基于tpc但是它在应用层是感知不到TCP的存在的。 所以不存在先发了一个http包,之后的请求应该是tcp包的说法。兔老大这里只是用http 协议完成了一个握手的过程,然后在使用自己的定义的协议(详见Day1 服务端与消息编码 中的通信过程)来进行双方的通讯。

刚刚开始码的时候一样有疑惑,先说我自己的结论,添加http支持是为了支持不同路径提供不同的服务,至于你说的先建立一个tcp请求再发送http connect方法似乎是connect方法一个常见的操作流程,我在这篇文章里面找到了相似的地方https://www.jianshu.com/p/54357cdd4736,connect方法被服务器接收到之后,劫持了conn,之后就只是tcp传输了,和楼上说的connect只是完成了一个握手的过程

imtzer avatar Nov 07 '21 09:11 imtzer

兔大,这里的功能是不是就是用http建立连接,然后客户端和服务端的数据还是用的我们实现的格式传输。那“完整”的支持http是不是建立连接也是用http,客户端传数据也是http协议格式,中间需要实现一个http格式转为我们格式的功能呢?

fanandli avatar Nov 18 '21 06:11 fanandli

我理解了这一段支持HTTP协议的意义了,要对HTTP,RPC和TCP有了解:1.HTTP 对应于应用层,TCP 协议对应于传输层,HTTP 协议是在 TCP 协议之上建立的,HTTP 在发起请求时通过 ,TCP是传输层协议,定义的是数据传输和连接方式的规范,HTTP是应用层协议,定义的是传输数据的内容的规范
  1. RPC是一种API,HTTP是一种无状态的网络协议。RPC可以基于HTTP协议实现,也可以直接在TCP协议上实现 3.也就是说其实传输层用TCP协议是不变的,只不过在应用层多了一层HTTP协议用于定义传输数据的内容和规范

vvzhihao avatar Jul 18 '22 03:07 vvzhihao

@goder-tao

switch protocol {
	case "http":
		return DialHTTP("tcp", addr, opts...)

明明是http请求为什么在dialhttp用的还是tcp

http应用层协议,下面用的还是tcp

ShiMaRing avatar Jul 31 '22 08:07 ShiMaRing

求问为啥客户端通过conn发起CONNECT方法的请求服务端收不到呀

onlyshawn avatar Aug 10 '22 08:08 onlyshawn

最后测试的时候,为什么把call函数中的内容直接放在main函数里会造成死锁?

SharkLJ avatar Jan 31 '23 09:01 SharkLJ