热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[golangnote]网络编程RPC编程

•官方文档      http:godoc.golangtc.compkgnet      Packagenetprovidesaportableinterfacefornetwo

• 官方文档

       http://godoc.golangtc.com/pkg/net/

       Package net provides a portable interface for network I/O, including TCP/IP, UDP, domain name resolution, and Unix domain sockets.

       net包中提供了一系列可移植的网络I/O接口,其中包含了TCP/IPUDP域名解析Unix域套接字

RPC


• RPC定义

       RPC,Remote Procedure Call Protocol,远程过程调用协议。RPC是一种通过网络从远程计算机程序上请求服务,但不需要了解底层网络技术的一种协议。RPC协议基于某些传输协议(如TCP和UDP协议等)而存在,为通信程序之间携带信息数据。

       在传统计算机编程语言中,譬如C和C++,实现RPC是一件不容易的事情。为了实现RPC,首先得基于不同的操作系统提供的网络模型实现网络通信,然后需要自己封装协议来实现RPC,通常为了方便使用还结合使用Lua进行脚本调用。而golang语言原生支持RPC,极大地提高了开发效率。

• net/rpc包

       在golang中,标准库提供的net/rpc包实现了RPC协议的相关细节,开发者可以方便地使用该包编写出RPC服务端和客户端程序,这使得用golang开发多个进程之间通信变得非常简单。

       官网介绍:rpc包提供了基于网络或其他I/O连接来访问某个对象的导出函数的方法。服务端需要注册提供RPC服务的对象,并以该对象类型的名称作为可见的服务名。对象注册完成之后,该对象的导出函数将可以被远程访问。务端可以注册多个不同类型的对象作为服务,但是需要注意的是,注册同一类型的多个对象将引发错误

▶ 导出函数需满足的条件

       [golang note] 网络编程 - RPC编程

       • 函数的类型需要导出。

       • 函数需要导出。

       • 函数必须拥有两个参数,参数必须是导出类型或内建类型。

       • 函数的第二个参数必须是一个指针。

       • 函数必须返回一个error类型的值。

 

       满足上述条件的函数可以简单表示成:

       [golang note] 网络编程 - RPC编程

       • 类型T、T1和T2默认使用golang内置的encoding/gob包进行编码解码。

       • 第一个参数argType表示由RPC客户端传入的参数。

       • 第二个参数replyType表示要返回给RPC客户端的结果。

       • 函数最后返回一个error类型的值。如果一个error值返回,replyType参数将不会发送给RPC客户端,而error值将会作为一个字符串发送给RPC客户端。

▶ RPC服务端

       • RPC服务端可以通过调用ServeConn处理单个连接上的请求。

       • 多数情况下,RPC服务端将创建一个TCP网络监听器并调用Accept,或创建一个HTTP监听器并调用HandleHTTPhttp.Serve

       • 如果没有明确指定RPC传输过程中使用何种编码解码器,默认将使用标准库提供的encoding/gob包进行数据传输的编解码器。

▶ RPC客户端

       • 将要使用RPC服务的客户端需要建立连接,然后在连接上调用NewClient函数。

       • net/rpc包提供了便利的rpc.Dial()rpc.DialHTTP()方法来与指定的RPC服务端建立连接。

       • net/rpc包允许客户端使用同步或异步的方式接收RPC服务端的处理结果:调用RPC客户端的Call()方法将进行同步处理,客户端程序顺序执行,只有接收完RPC服务端的处理结果之后才可继续执行后面的程序;调用RPC客户端的Go()方法时将进行异步处理,RPC客户端程序无需等待服务端的结果即可执行后面的程序,而当接收到RPC服务端的处理结果时,再对其进行相应的处理。

       • 如果没有明确指定RPC传输过程中使用何种编码解码器,默认将使用标准库提供的encoding/gob包进行数据传输的编解码器。

HTTP RPC使用


• HTTP RPC服务端

▶ 目录结构

       [golang note] 网络编程 - RPC编程

▶ 源码如下

package main

import (
    "errors"
    "log"
    "net"
    "net/http"
    "net/rpc"
    "time"
)

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()

    l, e := net.Listen("tcp", ":1234")
defer l.Close()
if e != nil { log.Fatal("listen error:", e)
return } go http.Serve(l, nil) log.Println(
"rpc server started!") for { time.Sleep(1 * time.Second) } }

• HTTP RPC客户端

▶ 目录结构

       [golang note] 网络编程 - RPC编程

▶ 源码如下

package main

import (
    "log"
    "net/rpc"
)

type Args struct {
    A, B int
}

type Quotient struct {
    Quo, Rem int
}

func main() {
    client, err := rpc.DialHTTP("tcp", "127.0.0.1"+":1234")
    defer client.Close()
if err != nil { log.Fatal("dialing error:", err)
return } args1 :
= &Args{2, 3} args2 := &Args{7, 2} args3 := &Args{7, 0} reply1 := 0 reply2 := Quotient{} reply3 := Quotient{} err = client.Call("Arith.Multiply", args1, &reply1) if err != nil { log.Fatal("Arith error:", err)
        return
} log.Println(reply1)
// 6 err = client.Call("Arith.Divide", args2, &reply2) if err != nil { log.Fatal("Arith error:", err)
        return
} log.Println(reply2)
// {3 1} err = client.Call("Arith.Divide", args3, &reply3) if err != nil { log.Fatal("Arith error:", err) // arith error:divide by zero
        return
} log.Println(reply3) }

TCP RPC使用


• TCP RPC服务端

▶ 目录结构

       [golang note] 网络编程 - RPC编程

▶ 源码如下

package main

import (
    "errors"
    "log"
    "net"
    "net/rpc"
    "time"
)

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)
    server := rpc.NewServer()
    server.Register(arith)

    l, e := net.Listen("tcp", ":1234")
    defer l.Close()

    if e != nil {
        log.Fatal("listen error:", e)
        return
    }

    go server.Accept(l)
    log.Println("rpc server started!")

    for {
        time.Sleep(1 * time.Second)
    }
}

• TCP RPC客户端 

▶ 目录结构

       [golang note] 网络编程 - RPC编程

▶ 源码如下

package main

import (
    "log"
    "net/rpc"
)

type Args struct {
    A, B int
}

type Quotient struct {
    Quo, Rem int
}

func main() {
    client, err := rpc.Dial("tcp", "127.0.0.1:1234")
    defer client.Close()

    if err != nil {
        log.Fatal("dialing error:", err)
        return
    }

    args1 := &Args{2, 3}
    args2 := &Args{7, 2}
    args3 := &Args{7, 0}

    reply1 := 0
    reply2 := Quotient{}
    reply3 := Quotient{}

    // 同步方式RPC
    err = client.Call("Arith.Multiply", args1, &reply1)
    if err != nil {
        log.Fatal("Arith error:", err)
        return
    }
    log.Println(reply1) // 6

    // 异步方式RPC
    call2 := client.Go("Arith.Divide", args2, &reply2, nil)
    if call2 != nil {
        if replyCall, ok := <-call2.Done; ok {
            if replyCall.Error != nil {
                log.Fatal("Arith error:", replyCall.Error)
                return
            }
            log.Println(reply2) // {3 1}
        }
    }

    // 异步方式RPC
    call3 := client.Go("Arith.Divide", args3, &reply3, nil)
    if call3 != nil {
        if replyCall, ok := <-call3.Done; ok {
            if replyCall.Error != nil {
                log.Fatal("Arith error:", replyCall.Error) // Arith error:divide by zero
                return
            }
            log.Println(reply3) // {3 1}
        }
    }
}

Protobuf RPC使用


• 环境准备

       官网(https://github.com/golang/protobuf)介绍的安装步骤如下:

       [golang note] 网络编程 - RPC编程

▶ 安装protobuf

       下载地址:https://developers.google.com/protocol-buffers/

▪ windows下安装

       下载地址:https://developers.google.com/protocol-buffers/docs/downloads

       ▪ 下载protoc-2.6.1-win32.zip并解压;

       ▪ 将protoc.exe路径加入系统路径;

▪ linux下安装

▶ 安装goprotobuf插件

Tips:
    需要注意的是,使用go get命令之前需要安装git for windows,否则命令将不起作用。

       ▪ 在命令行下运行如下命令:

go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
go install github.com/golang/protobuf/proto
go install github.com/golang/protobuf/protoc-gen-go

        ▪ 工程bin目录下会生成protoc-gen-go.exe文件,将工程bin目录加入系统路径(windows下path路径),以便该工具文件使用。

       [golang note] 网络编程 - RPC编程

• 编写proto文件

       在src目录下新建pbprotocol目录,并在该目录下新建一个arith.proto的文本文件,编辑该文件内容如下:

package arith;

option cc_generic_services   = true;
option java_generic_services = true;
option py_generic_services   = true;

message ArithRequest {
    optional int32 a = 1;
    optional int32 b = 2;
}

message ArithResponse {
    optional int32 c = 1;
}

service ArithService {
    rpc Multiply (ArithRequest) returns (ArithResponse);
    rpc Divide   (ArithRequest) returns (ArithResponse);
}

       在命令行进入pbprotocol目录,运行下面命令,生成目标文件arith.pb.go。

protoc --go_out=. arith.proto

       对应的目录结构:       
       [golang note] 网络编程 - RPC编程

• Protobuf RPC服务端

▶ 目录结构

       [golang note] 网络编程 - RPC编程

▶ 源码如下

package main

import (
    "errors"
    "log"
    "net"
    "net/http"
    "net/rpc"
    "pbprotocol"
    "time"

    "github.com/golang/protobuf/proto"
)

type Arith int

func (t *Arith) Multiply(args *arith.ArithRequest, reply *arith.ArithResponse) error {
    reply.C = proto.Int32(args.GetA() * args.GetB())
    return nil
}

func (t *Arith) Divide(args *arith.ArithRequest, reply *arith.ArithResponse) error {
    if args.GetB() == 0 {
        return errors.New("divide by zero")
    }

    reply.C = proto.Int32(args.GetA() / args.GetB())
    return nil
}

func main() {
    arith := new(Arith)
    rpc.Register(arith)
    rpc.HandleHTTP()

    l, e := net.Listen("tcp", ":1234")
    defer l.Close()

    if e != nil {
        log.Fatal("listen error:", e)
        return
    }

    go http.Serve(l, nil)
    log.Println("rpc server started!")

    for {
        time.Sleep(1 * time.Second)
    }
}

• Protobuf RPC客户端

▶ 目录结构

       [golang note] 网络编程 - RPC编程

▶ 源码如下

package main

import (
    "log"
    "net/rpc"
    "pbprotocol"

    "github.com/golang/protobuf/proto"
)

func main() {
    client, err := rpc.DialHTTP("tcp", "127.0.0.1"+":1234")
    defer client.Close()

    if err != nil {
        log.Fatal("dialing error:", err)
        return
    }

    var args arith.ArithRequest
    var reply arith.ArithResponse

    // Multiply
    args.A = proto.Int32(1)
    args.B = proto.Int32(2)

    err = client.Call("Arith.Multiply", &args, &reply)
    if err != nil {
        log.Fatal("Arith error:", err)
        return
    }
    log.Println(reply.GetC()) // 2

    // Divide
    args.A = proto.Int32(12)
    args.B = proto.Int32(6)

    err = client.Call("Arith.Divide", &args, &reply)
    if err != nil {
        log.Fatal("Arith error:", err)
        return
    }
    log.Println(reply.GetC()) // 2

    // Divide zero
    args.A = proto.Int32(12)
    args.B = proto.Int32(0)

    err = client.Call("Arith.Divide", &args, &reply)
    if err != nil {
        log.Fatal("Arith error:", err) // arith error:divide by zero
        return
    }
    log.Println(reply.GetC())
}

 


推荐阅读
  • EasyMock实战指南
    本文介绍了如何使用EasyMock进行单元测试,特别是当测试对象的合作者依赖于外部资源或尚未实现时。通过具体的示例,展示了EasyMock在模拟对象行为方面的强大功能。 ... [详细]
  • 本指南详细介绍了如何在 CentOS 7.0 系统上部署 Spring Boot 2.x 应用程序,包括必要的配置和步骤。 ... [详细]
  • R语言基础入门指南
    本文介绍R语言的基本概念,包括其作为区分大小写的解释型语言的特点、主要的数据结构类型如向量、矩阵、数据框及列表等,并探讨了R语言中对象的灵活性与函数的应用。此外,文章还提供了关于如何使用R进行基本操作的示例,以及解决常见编程问题的方法。 ... [详细]
  • Spring Boot 入门指南
    本文介绍了Spring Boot的基本概念及其在现代Java应用程序开发中的作用。Spring Boot旨在简化Spring应用的初始设置和开发过程,通过自动配置和约定优于配置的原则,帮助开发者快速构建基于Spring框架的应用。 ... [详细]
  • Eclipse 下 JavaFX 程序开发指南
    本文介绍了 JavaFX,这是一个用于创建富客户端应用程序的 Java 图形和媒体工具包,并详细说明了如何在 Eclipse 环境中配置和开发 JavaFX 应用。 ... [详细]
  • 本文介绍如何在Ubuntu环境下为OpenWrt系统构建并安装首个'Hello World'应用程序的IPK包。文章不仅涵盖了基本的环境搭建,还详细说明了代码编写、Makefile配置及最终的IPK包生成与安装过程。 ... [详细]
  • NFS(Network File System)即网络文件系统,是一种分布式文件系统协议,主要用于Unix和类Unix系统之间的文件共享。本文详细介绍NFS的配置文件/etc/exports和相关服务配置,帮助读者理解如何在Linux环境中配置NFS客户端。 ... [详细]
  • Go语言开发中的常见陷阱与解决方案
    本文探讨了在使用Go语言开发过程中遇到的一些典型问题,包括Map遍历的不确定性、切片操作的潜在风险以及并发处理时的常见错误。通过具体案例分析,提供有效的解决策略。 ... [详细]
  • Java实现文本到图片转换,支持自动换行、字体自定义及图像优化
    本文详细介绍了如何使用Java实现将文本转换为图片的功能,包括自动换行、自定义字体加载、抗锯齿优化以及图片压缩等技术细节。 ... [详细]
  • 本文将详细探讨 Linux 系统中的 netstat 命令,该命令用于查看网络状态和连接情况。通过了解 IP 地址和端口的基本概念,我们将更好地理解如何利用 netstat 命令来监控和管理网络服务。 ... [详细]
  • Java EE CDI:解决依赖关系冲突的实例
    在本教程中,我们将探讨如何在Java EE的CDI(上下文和依赖注入)框架中有效解决依赖关系的冲突问题。通过学习如何使用限定符,您将能够为应用程序的不同客户端提供多种接口实现,并确保每个客户端都能正确调用其所需的实现。 ... [详细]
  • Webpack中实现环境与代码的有效分离
    本文探讨了如何在Webpack中有效地区分开发与生产环境,并实现代码的合理分离,以提高项目的可维护性和加载性能。 ... [详细]
  • 本文探讨了在 Xamarin 表单中使用 .NET Standard 2.0 库时遇到的兼容性问题及解决方案。 ... [详细]
  • 本文详细探讨了Xshell6评估版到期后无法使用的常见问题,并提供了有效的解决方案,包括如何合法购买授权以继续使用。 ... [详细]
  • 本文详细介绍了如何在Linux系统中安装和配置GitLab Runner,包括添加YUM源、安装GitLab Runner以及注册Runner的具体步骤。 ... [详细]
author-avatar
手机用户2502897855
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有