恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Go Web 服务开发指南:Socket、WebSocket、REST 与 RPC 的 Go 实现(build-web-application-with-golang 第 8 章)
首页
资讯中心
/
Go Web 服务开发指南:Socket、WebSocket、REST 与 RPC 的 Go 实现(build-web-application-with-golang 第 8 章)
Go Web 服务开发指南:Socket、WebSocket、REST 与 RPC 的 Go 实现(build-web-application-with-golang 第 8 章)
发布时间:2026/10/1 2:07:27
文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载Web 服务允许我们通过 HTTP 使用 XML、JSON 等格式在客户端与服务端之间交换信息是构建跨平台、分布式应用的基础设施。本章节是开源图书 build-web-application-with-golang 的第 8 章导论梳理了 Go 生态中四种主流的 Web 服务实现路线——Socket、WebSocket、REST 与 RPC。读完本文你将掌握用 Go 标准库net/net/http/net/rpc编写 TCP/UDP 服务、实现 HTML5 全双工通信、搭建 RESTful 接口以及开发分布式 RPC 服务的完整实战方案。什么是 Web 服务Web 服务让你能够使用 XML 或 JSON 等格式通过 HTTP 交换信息。例如想查询上海明天的天气、苹果公司的当前股价或者亚马逊上的商品信息你可以编写一段代码从开放平台抓取这些数据。在 Go 中这个过程可以类比为调用本地函数并获取其返回值——关键点在于 Web 服务是平台无关的你可以把应用部署在 Linux 上与 Windows 上的 ASP.NET 应用交互就像在 FreeBSD 上与 JSP 交互一样毫无障碍。目前 Web 服务的主流实现风格有两种REST 架构与SOAP 协议REST请求基于 HTTP非常直接。每一个 REST 请求本质上就是一个 HTTP 请求服务器通过不同的方法method处理请求。由于大多数开发者已经熟悉 HTTPREST 几乎可以信手拈来。本书将在 REST 一节展示如何在 Go 中实现 REST。SOAP由 W3C 发起、用于跨网络信息传输和远程计算机函数调用的标准。SOAP 的问题在于其规范非常冗长复杂而且还在不断增长。Go 信奉简单即美因此本书不讨论 SOAP幸运的是Go 对RPCRemote Procedure Call远程过程调用提供了性能良好且易于开发的官方支持我们将在 RPC 一节介绍其实现。Go 常被称为21 世纪的 C 语言追求简单且高性能。基于这一品质本章将在 Sockets 一节介绍 Go 的 Socket 编程——如今许多实时服务器都借助 Socket 来克服 HTTP 性能偏低的问题随着 HTML5 的快速发展WebSocket 已被众多网页游戏公司采用相关内容在 WebSocket 一节展开。章节总览可参考 第 8 章入口文件 与 全书目录上一章文本文件处理含 XML/JSON/正则/模板见 第 7 章小结。8.1 SocketsGo 网络编程的地基Socket 是什么Socket 起源于 Unix。基于 Unix万物皆文件的哲学一切都可以用open - write/read - close的方式操作Socket 正是这一哲学的一种实现它有一个打开 Socket 的函数调用类似打开文件返回一个整数描述符descriptor随后可以用该描述符进行建立连接、传输数据等操作。常用的 Socket 有两种类型流式 SocketSOCK_STREAM面向连接如 TCP。数据报 SocketSOCK_DGRAM不建立连接如 UDP。Socket 通信的定位原理要让 Socket 可靠通信必须保证每个 Socket 唯一。每个进程有唯一的 PID但 PID 只在本地有效、无法跨网络使用。TCP/IP 解决了这个问题网络层的IP 地址在主机网络中唯一协议 端口在主机应用间唯一。两者组合即可唯一标识一个 Socket。下图展示了网络协议分层中 Socket 所处的位置所有基于 TCP/IP 的应用都在代码中以这样或那样的方式使用 Socket API。随着网络应用的普及一切皆 Socket的说法并不夸张。IP 基础IPv4 与 IPv6全球互联网以 TCP/IP 为协议基础其中 IP 是网络层、TCP/IP 的核心部分。IPv4版本号为 4地址为 32 位理论上可容纳 2^32 台设备唯一接入互联网。由于互联网发展迅速IP 地址近年已近枯竭。地址格式如127.0.0.1、172.122.121.111。IPv6下一代互联网协议地址为 128 位基本无需担心地址短缺按地球每平方米可分配超过 1000 个地址估算同时改善了点对点连接、服务质量QoS、安全性、多播等问题。地址格式如2002:c0e8:82e7:0:0:0:c0e8:82e7。Go 中的 IP 类型Go 的net包提供了大量网络编程的类型、函数与方法。IP 的定义如下type IP []byte函数ParseIP(s string) IP用于把 IPv4 或 IPv6 格式的字符串转换为 IPpackage main import ( net os fmt ) func main() { if len(os.Args) ! 2 { fmt.Fprintf(os.Stderr, Usage: %s ip-addr\n, os.Args[0]) os.Exit(1) } name : os.Args[1] addr : net.ParseIP(name) if addr nil { fmt.Println(Invalid address) } else { fmt.Println(The address is , addr.String()) } os.Exit(0) }TCP Socket客户端在 Go 的net包中TCPConn类型支撑客户端/服务器的交互它有两个关键方法func (c *TCPConn) Write(b []byte) (n int, err os.Error) func (c *TCPConn) Read(b []byte) (n int, err os.Error)TCPConn可被客户端或服务器用于读写数据。还需要TCPAddr表示 TCP 地址信息type TCPAddr struct { IP IP Port int }用ResolveTCPAddr获取TCPAddrfunc ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)net参数可取tcp4、tcp6或tcp分别表示仅 IPv4、仅 IPv6、以及 IPv4/IPv6 皆可。addr可以是域名或 IP 地址例如www.google.com:80、127.0.0.1:22。Go 客户端用DialTCP创建 TCP 连接返回TCPConn对象连接建立后服务器端持有同一类型的连接对象双方即可交换数据。连接会一直保持直到某一方关闭func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)net参数同上tcp4/tcp6/tcp。laddr表示本地地址多数情况下设为nil。raddr表示远端地址。下面模拟一个基于 HTTP 请求头HEAD / HTTP/1.0\r\n\r\n向服务器发起连接的客户端package main import ( fmt io/ioutil net os ) func main() { if len(os.Args) ! 2 { fmt.Fprintf(os.Stderr, Usage: %s host:port , os.Args[0]) os.Exit(1) } service : os.Args[1] tcpAddr, err : net.ResolveTCPAddr(tcp4, service) checkError(err) conn, err : net.DialTCP(tcp, nil, tcpAddr) checkError(err) _, err conn.Write([]byte(HEAD / HTTP/1.0\r\n\r\n)) checkError(err) result, err : ioutil.ReadAll(conn) checkError(err) fmt.Println(string(result)) os.Exit(0) } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error: %s, err.Error()) os.Exit(1) } }运行后服务器响应可能形如HTTP/1.0 200 OK ETag: -9985996 Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT Content-Length: 18074 Connection: close Date: Sat, 28 Aug 2010 00:43:48 GMT Server: lighttpd/1.4.23TCP Socket服务器服务器端需要把服务绑定到某个未占用的端口并监听客户端请求func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error) func (l *TCPListener) Accept() (c Conn, err os.Error)参数与DialTCP相同。下面实现一个监听 7777 端口的简单时间同步服务package main import ( fmt net os time ) func main() { service : :7777 tcpAddr, err : net.ResolveTCPAddr(tcp4, service) checkError(err) listener, err : net.ListenTCP(tcp, tcpAddr) checkError(err) for { conn, err : listener.Accept() if err ! nil { continue } daytime : time.Now().String() conn.Write([]byte(daytime)) // dont care about return value conn.Close() // were finished with this client } } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error: %s, err.Error()) os.Exit(1) } }注意for循环内发生错误时服务会继续运行而不是退出——服务器应把错误记入错误日志而不是崩溃。上面的代码还不够好因为它没有利用 goroutine 同时处理多个请求。改进后的并发版本package main import ( fmt net os time ) func main() { service : :1200 tcpAddr, err : net.ResolveTCPAddr(tcp4, service) checkError(err) listener, err : net.ListenTCP(tcp, tcpAddr) checkError(err) for { conn, err : listener.Accept() if err ! nil { continue } go handleClient(conn) } } func handleClient(conn net.Conn) { defer conn.Close() daytime : time.Now().String() conn.Write([]byte(daytime)) // dont care about return value // were finished with this client } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error: %s, err.Error()) os.Exit(1) } }把业务逻辑抽到handleClient函数中配合go关键字就实现了服务并发——这是 goroutine 强大与简洁的典型示范。如果需要在一连接上处理多种请求例如不同的时间格式可以持续读取客户端请求并根据内容返回不同响应。示例中通过conn.SetReadDeadline设置 2 分钟超时超时后自动断开连接request缓冲被限制为 128 字节以防洪泛攻击package main import ( fmt net os time strconv ) func main() { service : :1200 tcpAddr, err : net.ResolveTCPAddr(tcp4, service) checkError(err) listener, err : net.ListenTCP(tcp, tcpAddr) checkError(err) for { conn, err : listener.Accept() if err ! nil { continue } go handleClient(conn) } } func handleClient(conn net.Conn) { conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout request : make([]byte, 128) // set maximum request length to 128B to prevent flood based attacks defer conn.Close() // close connection before exit for { read_len, err : conn.Read(request) if err ! nil { fmt.Println(err) break } if read_len 0 { break // connection already closed by client } else if string(request[:read_len]) timestamp { daytime : strconv.FormatInt(time.Now().Unix(), 10) conn.Write([]byte(daytime)) } else { daytime : time.Now().String() conn.Write([]byte(daytime)) } } } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error: %s, err.Error()) os.Exit(1) } }控制 TCP 连接Go 提供的连接控制函数func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)设置连接超时。以下两个函数在客户端和服务器上均适用用于设置单次连接的读写超时func (c *TCPConn) SetReadDeadline(t time.Time) error func (c *TCPConn) SetWriteDeadline(t time.Time) error此外还有保持连接开关func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error值得花时间思考连接超时时长长连接能减少创建连接的开销适合需要频繁交换数据的应用。更详细的说明请查阅 Gonet包官方文档。UDP SocketUDP Socket 与 TCP Socket 的唯一区别在于服务器端处理多个请求的方式——UDP 没有类似Accept的函数。其余函数只需把TCP替换为UDPfunc ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error) func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error) func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error) func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error) func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error)UDP 客户端示例package main import ( fmt net os ) func main() { if len(os.Args) ! 2 { fmt.Fprintf(os.Stderr, Usage: %s host:port, os.Args[0]) os.Exit(1) } service : os.Args[1] udpAddr, err : net.ResolveUDPAddr(udp4, service) checkError(err) conn, err : net.DialUDP(udp, nil, udpAddr) checkError(err) _, err conn.Write([]byte(anything)) checkError(err) var buf [512]byte n, err : conn.Read(buf[0:]) checkError(err) fmt.Println(string(buf[0:n])) os.Exit(0) } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error , err.Error()) os.Exit(1) } }UDP 服务器示例package main import ( fmt net os time ) func main() { service : :1200 udpAddr, err : net.ResolveUDPAddr(udp4, service) checkError(err) conn, err : net.ListenUDP(udp, udpAddr) checkError(err) for { handleClient(conn) } } func handleClient(conn *net.UDPConn) { var buf [512]byte _, addr, err : conn.ReadFromUDP(buf[0:]) if err ! nil { return } daytime : time.Now().String() conn.WriteToUDP([]byte(daytime), addr) } func checkError(err error) { if err ! nil { fmt.Fprintf(os.Stderr, Fatal error , err.Error()) os.Exit(1) } }小结通过用 TCP 与 UDP Socket 编写简单程序可以看到Go 对 Socket 编程提供了出色的支持且有趣易用Go 还提供了大量构建高性能 Socket 应用的函数。详见 8.1 Sockets。8.2 WebSocketHTML5 全双工通信WebSocket 是 HTML5 的重要特性实现了基于浏览器的远程 Socket让浏览器与服务器可以进行全双工通信。Firefox、Google Chrome、Safari 等主流浏览器都提供了支持。在 WebSocket 诞生之前即时消息服务常采用轮询polling客户端周期性发送 HTTP 请求服务器返回最新数据。这种方式的缺点是客户端需要持续向服务器发送大量请求消耗大量带宽。WebSocket 使用一种特殊头部将浏览器与服务器之间的握手缩减到一次即建立连接连接在整个生命周期内保持活跃你可以像使用传统 TCP Socket 一样用 JavaScript 读写数据。相比传统 HTTPWebSocket 的优势在于单个 Web 客户端只需一条 TCP 连接WebSocket 服务器可以主动向客户端推送数据轻量级的头部降低了数据传输开销。WebSocket URL 以ws://或wss://SSL开头。下图展示了 WebSocket 的通信过程WebSocket 握手原理WebSocket 协议本身相当简单成功完成初始握手后即建立连接后续数据通信都以\x00开头、以\xFF结尾WebSocket 会自动剥离首尾客户端拿到的是原始数据。握手的过程是浏览器发起请求服务器响应随后连接建立。请求与响应中Sec-WebSocket-key是随机生成并经 base64 编码的。服务器收到请求后需要把该 key 拼接到固定字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11假设 key 为f7cb4ezEAl6C3wRaU6JORA拼接后为f7cb4ezEAl6C3wRaU6JORA258EAFA5-E914-47DA-95CA-C5AB0DC85B11用 SHA1 计算二进制值再用 base64 编码得到rE91AJhfC6JdVcVXOGJEADEJdQ将其作为响应头Sec-WebSocket-Accept的值返回。Go 中的 WebSocketGo 标准库本身不直接支持 WebSocket但go.net现为golang.org/x/net的子包websocket提供了官方维护的支持。安装方式go get golang.org/x/net/websocketWebSocket 分客户端与服务端。下面是一个客户端输入信息 - 发送给服务器 - 服务器回推信息的完整示例。客户端HTML JavaScripthtml head/head body script typetext/javascript var sock null; var wsuri ws://127.0.0.1:1234; window.onload function() { console.log(onload); sock new WebSocket(wsuri); sock.onopen function() { console.log(connected to wsuri); } sock.onclose function(e) { console.log(connection closed ( e.code )); } sock.onmessage function(e) { console.log(message received: e.data); } }; function send() { var msg document.getElementById(message).value; sock.send(msg); }; /script h1WebSocket Echo Test/h1 form p Message: input idmessage typetext valueHello, world! /p /form button onclicksend();Send Message/button /body /html客户端打开连接通常绑定四个事件onopen连接建立后触发onmessage收到消息后触发onerror发生错误后触发onclose连接关闭后触发。服务器端Gopackage main import ( golang.org/x/net/websocket fmt log net/http ) func Echo(ws *websocket.Conn) { var err error for { var reply string if err websocket.Message.Receive(ws, reply); err ! nil { fmt.Println(Cant receive) break } fmt.Println(Received back from client: reply) msg : Received: reply fmt.Println(Sending to client: msg) if err websocket.Message.Send(ws, msg); err ! nil { fmt.Println(Cant send) break } } } func main() { http.Handle(/, websocket.Handler(Echo)) if err : http.ListenAndServe(:1234, nil); err ! nil { log.Fatal(ListenAndServe:, err) } }当客户端Send用户输入的信息后服务器Receive它并再次Send返回响应。通过这个例子可以看到WebSocket 在 Go 中的客户端与服务器端实现都非常便捷可以直接配合net/http使用。随着 HTML5 的快速发展WebSocket 在现代 Web 开发中的地位将越来越重要。详见 8.2 WebSocket。8.3 REST基于 HTTP 的软件架构RESTREpresentational State Transfer表现层状态转移是当今互联网上最流行的软件架构因为它建立在定义明确、严格的标准之上易于理解和扩展。越来越多的网站基于 REST 设计。REST 概念于 2000 年由 Roy Thomas Fielding 的博士论文首次提出Fielding 本人也是 HTTP 协议的共同创始人论文规定了该架构的约束与原则任何按此架构实现的东西都可称为 RESTful 系统。三个核心概念资源ResourceREST 是表现层状态转移表现层即资源表现层。图片、文档、视频等都是资源可通过 URI 定位。表示Representation资源是具体的信息实体在表现层可以有多种展示方式。例如一个 TXT 文档可以被表示为 HTML、JSON、XML 等一张图片可以被表示为 jpg、png 等。URI 用于标识资源而具体表现形式由 HTTP 请求头中的Accept与Content-Type两个字段描述。状态转移State Transfer每次访问网站页面都会在客户端与服务器之间发起交互过程中需要保存与当前页面状态相关的数据。但 HTTP 是无状态协议因此必须把客户端状态保存在服务器端。当客户端修改了某些数据并希望持久化时必须有一种方式把新状态告知服务器。大多数情况下客户端用 HTTP 的四个操作告知服务器状态变化GET获取资源POST创建或更新资源PUT更新资源DELETE删除资源。总结上述三点每个 URI 都代表一个资源客户端与服务器之间存在用于传输资源的表现层客户端使用四种 HTTP 方法实现表现层状态转移从而操作远端资源。REST 的重要原则实现 REST 的 Web 应用最重要的原则是客户端与服务器之间的交互是无状态的每个请求都应封装全部所需信息服务器可以在不通知客户端的情况下随时重启请求可以由同一服务的任意服务器响应非常适合云计算同时由于无状态客户端可以缓存数据以提升性能。另一个重要原则是系统分层某一层的组件无法直接与另一层的组件交互这可以限制系统复杂度、促进底层组件独立。下图为 REST 架构示意当 RESTful 约束被合理遵循时Web 应用可以扩展到容纳海量客户端REST 架构还有助于降低客户端与服务器之间的延迟、简化系统架构、提升子系统端点的可见性。在 Go 中实现 RESTfulGo 没有直接内置 REST 支持但 RESTful Web 应用都基于 HTTP因此可以使用net/http包自行实现当然需要先做一些改造。许多号称 RESTful 的应用其实并没有真正实现 REST。可以根据实现的 HTTP 方法把它们划分为若干级别——有些应用不会遵循 REST 的全部规则与约束因为其规则并非适合所有场景。RESTful Web 应用会使用包括DELETE、PUT在内的每一个 HTTP 方法但很多情况下 HTTP 客户端只能发送GET和POSTHTML 标准只允许客户端通过链接和表单发送GET、POST请求没有 AJAX 支持就无法发送PUT、DELETE一些防火墙会拦截PUT和DELETE请求客户端只能用 POST 实现完全 RESTful 的服务负责找出原始 HTTP 方法并恢复它。可以在 POST 请求中添加隐藏的_method字段来模拟PUT与DELETE请求但服务器端必须在处理前转换。作者的个人应用正是用这种工作流实现 REST 接口的。标准的 RESTful 接口在 Go 中很容易实现下面借助第三方路由包github.com/julienschmidt/httprouter演示资源是用户user同一 URI 的不同 HTTP 方法分发到不同处理函数package main import ( fmt github.com/julienschmidt/httprouter log net/http ) func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprint(w, Welcome!\n) } func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { fmt.Fprintf(w, hello, %s!\n, ps.ByName(name)) } func getuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { uid : ps.ByName(uid) fmt.Fprintf(w, you are get user %s, uid) } func modifyuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { uid : ps.ByName(uid) fmt.Fprintf(w, you are modify user %s, uid) } func deleteuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { uid : ps.ByName(uid) fmt.Fprintf(w, you are delete user %s, uid) } func adduser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // uid : r.FormValue(uid) uid : ps.ByName(uid) fmt.Fprintf(w, you are add user %s, uid) } func main() { router : httprouter.New() router.GET(/, Index) router.GET(/hello/:name, Hello) router.GET(/user/:uid, getuser) router.POST(/adduser/:uid, adduser) router.DELETE(/deluser/:uid, deleteuser) router.PUT(/moduser/:uid, modifyuser) log.Fatal(http.ListenAndServe(:8080, router)) }这段示例展示了如何编写一个非常基础的 REST 应用资源是用户不同方法对应不同函数。httprouter实现了非常方便的路由映射规则让 RESTful 架构的实现变得很方便。可以看到REST 要求对同一资源的不同 HTTP 方法实现不同的逻辑。小结REST 是一种 Web 架构风格建立在 WWW 的成功经验之上——无状态、以资源为中心、充分利用 HTTP 与 URI 协议并提供统一接口。这些优秀的设计思想使 REST 成为最流行的 Web 服务标准。目前 Go 对 REST 的支持仍比较基础但通过自定义路由规则和为每种 HTTP 请求类型提供不同的处理器完全可以在 Go Web 应用中实现 RESTful 架构。详见 8.3 REST。8.4 RPC像调用本地函数一样调用远程服务前面几节介绍的都是基于 Socket 与 HTTP 的网络应用它们采用信息交换模型客户端发请求、服务器给响应数据交换依赖特定格式。但许多独立应用并不采用这种模型而是像调用普通函数一样调用服务。RPC远程过程调用正是为网络系统设计的函数调用模式客户端像调用本地函数一样执行 RPC只不过把函数参数打包后通过网络发送给服务器服务器解包参数、处理请求再把执行结果返回客户端。在计算机科学中RPC 是一种进程间通信方式允许程序让子过程在另一个地址空间通常是共享网络上的另一台计算机执行而无需程序员显式编写远程交互细节——无论子过程在本地还是远程程序员编写的代码基本相同。当软件采用面向对象原则时RPC 又被称为远程调用或远程方法调用。下图展示了 RPC 的工作原理一次从客户端到服务器的 RPC 调用通常包含以下十步调用客户端句柄client handle执行传递参数调用本地系统内核发送网络消息将消息发送到远程主机服务器接收句柄和参数执行远程过程将执行结果返回给对应句柄服务器句柄调用远程系统内核消息回传给本地系统内核客户端句柄从系统内核接收消息客户端从对应句柄获得结果。Go RPC 的规则Go 在标准库中提供了三个层面的官方 RPC 支持TCP、HTTP 与 JSON RPC。需要注意Go RPC 与传统的 RPC 系统不同——它使用Gob编码内容因此要求客户端和服务器端都使用 Go 应用。可以被远程访问的 Go RPC 函数必须遵守以下规则否则对应调用会被忽略函数必须是导出的首字母大写函数必须有两个导出类型的参数第一个参数用于接收客户端的输入第二个参数必须是指针用于向客户端回复函数必须返回error类型的值。例如func (t *T) MethodName(argType T1, replyType *T2) error其中T、T1、T2必须能够被package/gob包编码。任何 RPC 都要通过网络传输数据Go RPC 可以使用 HTTP 或 TCP使用 HTTP 的好处是可以复用net/http包的函数。HTTP RPC服务器端代码package main import ( errors fmt net/http net/rpc ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } type Arith int func (t *Arith) Multiply(args *Args, reply *int) error { *reply args.A * args.B return nil } func (t *Arith) Divide(args *Args, quo *Quotient) error { if args.B 0 { return errors.New(divide by zero) } quo.Quo args.A / args.B quo.Rem args.A % args.B return nil } func main() { arith : new(Arith) rpc.Register(arith) rpc.HandleHTTP() err : http.ListenAndServe(:1234, nil) if err ! nil { fmt.Println(err.Error()) } }先注册Arith这个 RPC 服务再通过rpc.HandleHTTP把它注册到 HTTP 上之后就可以通过 HTTP 传输数据。客户端代码package main import ( fmt log net/rpc os ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } func main() { if len(os.Args) ! 2 { fmt.Println(Usage: , os.Args[0], server) os.Exit(1) } serverAddress : os.Args[1] client, err : rpc.DialHTTP(tcp, serverAddress:1234) if err ! nil { log.Fatal(dialing:, err) } // Synchronous call args : Args{17, 8} var reply int err client.Call(Arith.Multiply, args, reply) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d*%d%d\n, args.A, args.B, reply) var quot Quotient err client.Call(Arith.Divide, args, quot) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d/%d%d remainder %d\n, args.A, args.B, quot.Quo, quot.Rem) }分别编译客户端与服务器端先启动服务器再运行客户端输入数据后将得到类似输出$ ./http_c localhost Arith: 17*8136 Arith: 17/82 remainder 1注意client.Call这个关键调用有三个参数第一个是被调用函数的名称第二个是要传递的参数第三个是返回值指针类型。可以看到在 Go 中实现 RPC 非常容易。TCP RPC基于 TCP 的 RPC 服务器端代码package main import ( errors fmt net net/rpc os ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } type Arith int func (t *Arith) Multiply(args *Args, reply *int) error { *reply args.A * args.B return nil } func (t *Arith) Divide(args *Args, quo *Quotient) error { if args.B 0 { return errors.New(divide by zero) } quo.Quo args.A / args.B quo.Rem args.A % args.B return nil } func main() { arith : new(Arith) rpc.Register(arith) tcpAddr, err : net.ResolveTCPAddr(tcp, :1234) checkError(err) listener, err : net.ListenTCP(tcp, tcpAddr) checkError(err) for { conn, err : listener.Accept() if err ! nil { continue } rpc.ServeConn(conn) } } func checkError(err error) { if err ! nil { fmt.Println(Fatal error , err.Error()) os.Exit(1) } }HTTP RPC 与 TCP RPC 的区别在于使用 TCP RPC 时我们必须自己控制连接然后把连接交给 RPC 处理。这显然是一种阻塞模式你可以自由使用 goroutine 扩展这个应用作为进阶实验。客户端唯一区别是HTTP 客户端用DialHTTPTCP 客户端用Dial(tcp, service)package main import ( fmt log net/rpc os ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } func main() { if len(os.Args) ! 2 { fmt.Println(Usage: , os.Args[0], server:port) os.Exit(1) } service : os.Args[1] client, err : rpc.Dial(tcp, service) if err ! nil { log.Fatal(dialing:, err) } // Synchronous call args : Args{17, 8} var reply int err client.Call(Arith.Multiply, args, reply) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d*%d%d\n, args.A, args.B, reply) var quot Quotient err client.Call(Arith.Divide, args, quot) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d/%d%d remainder %d\n, args.A, args.B, quot.Quo, quot.Rem) }JSON RPCJSON RPC 用 JSON 而非 Gob 编码数据。服务器端代码package main import ( errors fmt net net/rpc net/rpc/jsonrpc os ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } type Arith int func (t *Arith) Multiply(args *Args, reply *int) error { *reply args.A * args.B return nil } func (t *Arith) Divide(args *Args, quo *Quotient) error { if args.B 0 { return errors.New(divide by zero) } quo.Quo args.A / args.B quo.Rem args.A % args.B return nil } func main() { arith : new(Arith) rpc.Register(arith) tcpAddr, err : net.ResolveTCPAddr(tcp, :1234) checkError(err) listener, err : net.ListenTCP(tcp, tcpAddr) checkError(err) for { conn, err : listener.Accept() if err ! nil { continue } jsonrpc.ServeConn(conn) } } func checkError(err error) { if err ! nil { fmt.Println(Fatal error , err.Error()) os.Exit(1) } }注意JSON RPC 基于 TCP尚不支持 HTTP。客户端使用jsonrpc.Dial(tcp, service)建立连接其余调用方式与前面一致package main import ( fmt log net/rpc/jsonrpc os ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } func main() { if len(os.Args) ! 2 { fmt.Println(Usage: , os.Args[0], server:port) log.Fatal(1) } service : os.Args[1] client, err : jsonrpc.Dial(tcp, service) if err ! nil { log.Fatal(dialing:, err) } // Synchronous call args : Args{17, 8} var reply int err client.Call(Arith.Multiply, args, reply) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d*%d%d\n, args.A, args.B, reply) var quot Quotient err client.Call(Arith.Divide, args, quot) if err ! nil { log.Fatal(arith error:, err) } fmt.Printf(Arith: %d/%d%d remainder %d\n, args.A, args.B, quot.Quo, quot.Rem) }小结Go 对 HTTP、TCP 与 JSON RPC 提供了良好支持使我们能够轻松开发分布式 Web 应用遗憾的是 Go 没有内置 SOAP RPC 支持不过一些第三方开源包提供了该能力。详见 8.4 RPC。本章小结与延伸阅读本章介绍了四种主流的 Web 应用开发模型Sockets8.1网络编程的基础。网络技术和基础设施快速演进Socket 是这一切的基石掌握 Socket 编程概念是合格 Web 开发者的必备能力。WebSocket8.2HTML5 特性支持客户端与服务器之间的全双工通信消除了 AJAX 轮询的需要。REST8.3实现了简单应用的 RESTful 架构特别适合网络 API 开发随着移动应用的迅速崛起RESTful API 将持续成为趋势。RPC8.4Go 的官方 RPC 支持便于开发分布式应用。Go 对上述四种开发方式都提供了出色的支持。net包及其子包正是 Go 网络编程工具所在之处如果想要更深入地理解相关实现细节建议阅读这些包的源码。本章小结见 8.5 Summary下一章将进入 安全与加密。全书目录与章节导航可参考 en/SUMMARY.md 与 preface本书配套示例代码位于 en/code 目录下可结合前序章节如 第 3 章 Web 基础 中的 HTTP 原理与net/http讲解系统学习。赞分享文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载相关推荐Go Web 服务编程全景Socket、WebSocket、REST 与 RPC 四大网络模型实战导览build-web-application-with-golang 第 8 章Go Web 服务编程全景Socket、WebSocket、REST 与 RPC 四大网络模型实战导览build web application with文档教程Go无服务器架构build-web-application-with-golang中的AWS Lambda实现Go无服务器架构build web application with golang中的AWS Lambda实现 你还在为传统Web服务的服务器维护、资源浪费而文档教程build-web-application-with-golang服务网格Istio与Go微服务治理build web application with golang服务网格Istio与Go微服务治理 随着Go语言在微服务开发中的广泛应用构建可靠、可扩展的文档教程上一篇Cerebro Counter-Strike 2插件CS2启动工具下一篇history库面试题前端路由相关问题与答案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考