Go语言网络编程:TCP、HTTP、Gin、WebSocket、RPC、gRPC入门实例

在本文中,我们将介绍Go语言中的网络编程的不同方式,包括TCP、HTTP、Gin框架、WebSocket、RPC、gRPC的介绍与连接实例,并对所有示例代码都给出了详细的注释,最后对每种模式进行了总结。

1. TCP网络编程

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,提供可靠的双向通信。

netnet
netListenDialListenerAcceptConnConn

1.1 TCP服务器

以下是没有相关错误处理代码的一个简单的TCP服务器示例:

package main
import (
	"fmt"
	"io"
	"net"
)
func main() {
	// net.Listen函数创建一个TCP监听器,监听`localhost:8080`,等待连接
	listener, _ := net.Listen("tcp", "localhost:8080")
	defer listener.Close()
	for {
		// 无限循环来接受客户端连接
		conn, _ := listener.Accept()
		// 开启协程,处理连接
		go handleConnection(conn)
	}
}
func handleConnection(conn net.Conn) {
	defer conn.Close()
	for {
		// 读取数据
		buf := make([]byte, 1024)
		n, _ := conn.Read(buf)
		// 处理数据并回复客户端
		fmt.Printf("Received: %s", string(buf[:n]))
		conn.Write([]byte("Message received."))
	}
}
localhost:8080handleConnection

1.2 TCP客户端

以下是一个简单的TCP客户端示例:

package main
import (
	"fmt"
	"net"
)
func main() {
	// net.Dial函数连接 TCP 服务端
	conn, err := net.Dial("tcp", "localhost:8080")
	if err != nil {
		fmt.Println("Error dialing:", err)
		return
	}
	// 延迟关闭连接
	defer conn.Close()
	// 向服务器发送数据
	conn.Write([]byte("Hello, server!"))
	// 读取从服务端返回的数据
	buf := make([]byte, 1024)
	n, _ := conn.Read(buf)
	fmt.Printf("Received: %s", string(buf[:n]))
}

在这个示例中,我们首先连接到TCP服务器,然后向服务器发送一条消息。接着,我们等待服务器的响应,并将其打印到控制台,之后会关闭连接。
若想要一直连接客户端,只需要在连接建立后用一个for循环来一直发送和接收数据即可

2. HTTP网络编程

HTTP是基于TCP的请求-响应协议,只能由客户端向服务器发送请求,并要求服务器回复响应。.

net/httphttp
http.HandleFunc()func(http.ResponseWriter, *http.Request)ResponseWriterRequesthttp.ListenAndServe()
http.HandleFunc()ListenAndServe()

GO web编程实战:https://www.topgoer.cn/docs/golangweb/golangweb-1ck1kt56ksq5u
以下是一个简单的HTTP服务器示例:

package main
import (
	"fmt"
	"net/http" // 导入http包
)
func main() {
	// 定义路由函数, 第一个参数注册了URL 路径“/”,第二个参数则是处理函数 
	//w http.ResponseWriter 是向客户端发送HTTP响应的`ResponseWriter`响应对象
	//r *http.Request 代表客户端请求的`Request`请求对象,包含了关于客户端请求的元数据信息,如请求方法、URL、请求头等
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 
		fmt.Fprintf(w, "Hello, world!") 
	})
	// 开启HTTP服务器,监听本地8080端口
	err := http.ListenAndServe(":8080", nil) 
	if err != nil {
		fmt.Println("Error starting server:", err) 
	}
}

3. Gin框架网络编程

Gin是一个用Go语言编写的Web框架,它提供了许多实用的功能,如路由、中间件、模板渲染等。要使用Gin框架,首先需要安装它并在代码中导入。

go get -u github.com/gin-gonic/gin

Gin中文文档:https://www.kancloud.cn/shuangdeyu/gin_book/949413

以下是一个简单的Gin Web应用示例:

package main
import (
	"github.com/gin-gonic/gin"
)
func main() {
	// 创建一个默认 Gin 路由器
	router := gin.Default()
	// 定义处理器函数,匹配 HTTP GET 请求,当请求的 URL 路径为"/"时,执行指定的处理函数func输出Hello, world!。
	router.GET("/", func(c *gin.Context) {
		c.String(200, "Hello, world!")
	})
	// 启动 HTTP 服务器并监听来自8080端口的请求
	router.Run(":8080")
router.Run8080

4. WebSocket网络编程

WebSocket建立在HTTP基础上,建立连接时需要进行一次HTTP握手,之后就可以进行双向通信。它实现了基于浏览器的远程socket,使浏览器和服务器可以进行全双工通信。

与HTTP不同,WebSocket是一种持久化的连接,因此可以用于实时数据传输,比如即时聊天、实时位置追踪等,它使得浏览器和服务器之间可以进行实时通信。WebSocket协议通过HTTP协议的升级实现,客户端和服务器之间的通信是基于消息的,可以发送文本、二进制数据等。

它解决了Web实时化的问题,相比传统HTTP有如下好处:

  • 一个Web客户端只建立一个TCP连接
  • Websocket服务端可以推送(push)数据到web客户端.
  • 有更加轻量级的头,减少数据传送量

WebSocket介绍:https://www.topgoer.cn/docs/golangweb/golangweb-1ck1l3rhnfn0l

github.com/gorilla/websocket
package main

import (
	"fmt"
	"net/http"
	"github.com/gorilla/websocket"
)

func main() {
	// 定义WebSocket请求处理函数
	upgrader := websocket.Upgrader{}
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// 通过Upgrader升级HTTP连接为WebSocket连接
		conn, _ := upgrader.Upgrade(w, r, nil)
		// 向客户端发送消息
		conn.WriteMessage(websocket.TextMessage, []byte("Hello, client!"))
		// 接收客户端消息并打印
		_, msg, _ := conn.ReadMessage()
		fmt.Printf("Received message: %s\n", string(msg))
	})

	// 启动Web服务器并监听端口
	err := http.ListenAndServe(":8080", nil)
	fmt.Println(err)
}

该示例代码中,通过使用github.com/gorilla/websocket包提供的Upgrader结构体将HTTP连接升级为WebSocket连接。然后向客户端发送一条文本消息,并接收客户端返回的消息,其余操作与http基本一致。

5. RPC网络编程

Socket和HTTP采用的是类似"信息交换"模式,即客户端发送一条信息到服务端,然后服务器端都会返回一定的信息以表示响应。客户端和服务端之间约定了交互信息的格式,以便双方都能够解析交互所产生的信息。

但是很多独立的应用并没有采用这种模式,而是采用类似常规的函数调用的方式来完成想要的功能。RPC就是想实现函数调用模式的网络化,该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。客户端就像调用本地函数一样,然后客户端把这些参数打包之后通过网络传递到服务端,服务端解包到处理过程中执行,然后执行的结果反馈给客户端。

net/rpc
rpc.Register()func (t *T) MethodName(argType T1, replyType *T2) errornet/rpcDial()RPC.ClientCall()

Go RPC开发指南文档:https://www.topgoer.cn/docs/rpc/rpc-1c5ck8luqjf3l

5.1 服务端

以下是使用Go语言建立RPC连接的示例代码:

package main

import (
	"fmt"
	"net"
	"net/rpc"
)
// 定义数据结构Args,表示两个整数相乘的乘数和被乘数
type Args struct {
	A, B int
}
// 定义数据结构Arith,表示支持对Args类型进行RPC调用的服务端
type Arith struct{}

// 为Arith服务端定义方法Multiply,用于实现两个数相乘的逻辑
func (t *Arith) Multiply(args *Args, reply *int) error {
	*reply = args.A * args.B
	return nil
}
func main() {
	// 创建一个新的Arith对象
	arith := new(Arith)
	// 通过rpc.Register将arith注册到RPC服务器中
	rpc.Register(arith)
	// 监听TCP网络地址,等待客户端连接
	l, _:= net.Listen("tcp", ":1234")
	// 程序结束前关闭监听
	defer l.Close()
	for {
		// 接收客户端连接请求,并将连接作为参数调用RPC服务器
		conn, _ := l.Accept()
		go rpc.ServeConn(conn)
	}
}

ArithMultiply

5.2客户端:

package main

import (
	"fmt"
	"net/rpc"
)
type Args struct {
	A, B int
}
func main() {
	// 连接RPC服务器
	client, _ := rpc.Dial("tcp", "localhost:1234")
	args := Args{7, 8}
	var reply int
	// 通过client调用远程的"Arith.Multiply"函数,并传递参数args,将结果存储于reply中
	client.Call("Arith.Multiply", args, &reply)
	fmt.Printf("Multiply: %d * %d = %d", args.A, args.B, reply)
	client.Close()
}

该代码使用 Args 结构体来传递参数,并且只定义了一个变量 reply 来存储相乘的结果。在客户端调用远程服务端的 Arith.Multiply 函数时,将 args 和 &reply 作为参数传递给 Call 方法,这样远程函数就可以计算它们的积并将结果存储到 reply 变量中。最后输出计算的结果,并关闭连接。

6. gRPC网络编程

gRPC和传统的RPC都是实现远程过程调用的协议。它们的主要区别在于采用不同的传输协议和数据格式。

  • gRPC使用HTTP/2作为底层协议,支持多路复用和流控制等功能,并使用Protocol Buffers(一种语言中立、平台中立、可扩展且高效的序列化机制)作为默认的数据编解码协议。相比之下,传统的RPC协议通常使用TCP或UDP进行通信,而数据格式则更加灵活多样。
  • gRPC 支持多种编程语言,且 gRPC客户端和服务端可以在多种环境中运行和交互,例如用java写一个服务端,可以用go语言写客户端调用,即服务端与客户端可使用不同语言环境。
  • gRPC可以实现微服务,将大的项目拆分为多个小且独立的业务模块,也就是微服务,各服务间使用高效的protobuf协议进行RPC调用,gRPC默认使用protocol buffers,当然也可以使用其他数据格式如JSON。可以用proto files创建gRPC服务,用message类型来定义方法参数和返回类型。

gRPC文档:https://www.topgoer.cn/docs/grpc/grpc-1d2ud3fh1d74h

6.1 gRPC 服务端和客户端示例:

服务端代码:

package main
import (
	"context"
	"fmt"
	"net"
	"google.golang.org/grpc"
	//引用 hello-grpc 项目中定义的 Protocol Buffers(protobuf)协议。
	pb "github.com/user/hello-grpc/hello"
)
// 定义服务
type HelloServer struct{} 
// 实现SayHello函数
func (s *HelloServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
	return &pb.HelloResponse{Message: "Hello " + in.Name}, nil
}
func main() {
	// 创建grpc监听器
	lis, _ := net.Listen("tcp", ":50051")
	// 创建grpc服务器		
	grpcServer := grpc.NewServer()
	// 注册HelloServer服务
	pb.RegisterHelloServer(grpcServer, &HelloServer{})
	// 启动服务
	if err := grpcServer.Serve(lis); err != nil {
		fmt.Printf("failed to serve: %s\n", err)
	}
}

客户端代码:

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	pb "github.com/user/hello-grpc/hello"
)

func main() {
	// 连接GRPC服务
	conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
	defer conn.Close()
	// 创建GRPC客户端	
	client := pb.NewHelloClient(conn)
	// 输入参数
	req := &pb.HelloRequest{Name: "gRPC"}
	// 调用远程函数,获取响应结果	
	resp, _ := client.SayHello(context.Background(), req)
	fmt.Printf(resp.Message)
}

上述代码中服务端注册了 HelloServer 结构体下的 SayHello() 方法。当客户端通过会话连接调用该方法时,服务端将依据其输入值返回一个包含欢迎信息的响应。客户端向服务端发起请求和调用服务,通过创建grpc和protobuf协议的客户端/服务器端代码。同时标准化定义protobuf文件或proto文件,并生成相关代码。通过这样的方式我们就能够像调用本地函数一样来调用在不同语言和平台之间运行的远程函数,极大方便了分布式系统的开发与部署。

6.2 protobuf协议介绍

pb "github.com/user/hello-grpc/hello"
 protobuf 则是一种序列化/反序列化协议

在 gRPC 中,将定义服务所使用的 protobuf 文件,然后通过预处理工具将其转换为不同编程语言所对应的源代码,然后再使用生成的代码来实现服务端和客户端程序。

hello-grpc项目中定义的 Protocol Buffers(protobuf)协议是用于定义 SayHello 服务接口的。

hello.proto文件定义如下:

syntax = "proto3";  // 设置使用的协议版本

package hello;      // 设置包名

service Hello {      // 定义服务
    rpc SayHello (HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {     // 定义请求消息
    string name = 1;
}
message HelloResponse {    // 定义响应消息
    string message = 1;
}
servicerpcmessageHelloRequestHelloResponse
protochello.proto
protoc -I . hello.proto --go_out=plugins=grpc:.

生成的 Go 代码类似于以下内容:

// HelloMessage 是消息定义
type HelloMessage struct {
	Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}

// HelloResponse 是响应消息定义
type HelloResponse struct {
	Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"`
}

// 定义 Hello 服务接口
type HelloServer interface {
	// SayHello 是一个简单的RPC方法
	SayHello(context.Context, *HelloMessage) (*HelloResponse, error)
}

上述代码展示了通过 protobuf 协议定义的服务接口中所定义的请求和响应消息结构体,以及在 HelloServer 接口中声明的函数。这些 Go 结构体和函数的实现将会由 protoc 命令生成的代码实现。在实际使用时,我们只需要导入对应的包,并在自己的程序中实现服务接口即可。

7.总结

netnet.Listen()net.Dial()conn.Read()conn.Write()http.NewServeMux()http.HandleFunc()http.ListenAndServe()gin.Default()router.GET()router.POST()router.Run()github.com/gorilla/websocketUpgrader.Upgrade()websocket.Dial()conn.ReadMessage()conn.WriteMessage()net/rpcrpc.Register()rpc.Dial()client.Call()google.golang.org/grpc.protoprotoc