• [golang note] 网络编程


    net包


    • 官方文档

           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服务的对象,并以该对象类型的名称作为可见的服务名。对象注册完成之后,该对象的导出函数将可以被远程访问。务端可以注册多个不同类型的对象作为服务,但是需要注意的是,注册同一类型的多个对象将引发错误

    ▶ 导出函数需满足的条件

          

           • 函数的类型需要导出。

           • 函数需要导出。

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

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

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

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

          

           • 类型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服务端

    ▶ 目录结构

          

    ▶ 源码如下

    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客户端

    ▶ 目录结构

          

    ▶ 源码如下

    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服务端

    ▶ 目录结构

          

    ▶ 源码如下

    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客户端 

    ▶ 目录结构

          

    ▶ 源码如下

    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)介绍的安装步骤如下:

          

    ▶ 安装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路径),以便该工具文件使用。

          

    • 编写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

           对应的目录结构:       
          

    • Protobuf 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客户端

    ▶ 目录结构

          

    ▶ 源码如下

    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())
    }
  • 相关阅读:
    坐标
    firewallcmd常用命令
    sublime text 配置Latex
    winformDataGridView常用设置
    OutLook配置腾讯企业邮箱
    C#4种定时器Timer的用法
    C#监控Enter和Esc事件
    c#使用SqlSugar动态切换数据库
    WinformDataGridViewDataGridViewComboBoxColumn无法获取值问题
    C#无法将“******.dll”复制到“..*****.dll”。超出了重试计数 10。失败。文件被Mirosoft vs2017(10932)锁定
  • 原文地址:https://www.cnblogs.com/heartchord/p/5337863.html
Copyright © 2020-2023  润新知