golang网络编程day6

  • golang websocket编程
  • golang rpc编程
  • 最终总结

golang websocket编程

什么是websocket?,和socket是一回事吗?
websocket和传统的socket有些相似,又有些重要区别
1.WebSocket:
(1)websocket提供了在单个TCP连接上进行全双工通信的能力,特别用于浏览器和服务器之间的交互。(2)它是一种在单个长期连接上提供实时双向数据传输的网络协议。(3)websocket通常用于网页应用中,允许服务器主动向客户端发送数据,非常适合需要频繁和实时交互的应用场景。

2.socket:
(1)通常指的是网络编程中的一个概念,指的是提供两个结点之间数据交换的端点。(2)sockets可以用于不同类型的网络(包括TCP/UDP),支持多种通信协议。(3)它们是进行网络通信的基础,被用于构建包括webSocket在内的多种网络应用。

总结websocket是建立在socket基础上的一种特定协议,专为网络应用提供实时双向通信。

下面来看一个golang websocket服务端示例:

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
)

var (
	upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}
)

func main() {
	http.HandleFunc("/ws", handleWebSocket)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
	// 将HTTP连接升级为WebSocket连接
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println("upgrade error: ", err)
		return
	}
	defer conn.Close()

	for {
		// 从WebSocket连接中读取消息
		_, message, err := conn.ReadMessage()
		if err != nil {
			log.Println("read error: ", err)
			break
		}

		// 打印接收到的消息
		log.Printf("recv: %s\n", message)

		// 将当前时间作为响应消息发送给客户端
		resp := []byte(time.Now().String())
		err = conn.WriteMessage(websocket.TextMessage, resp)
		if err != nil {
			log.Println("write error: ", err)
			break
		}
	}
}

要搞懂这个代码先了解里面用到的函数
WebSocket Upgrader:是Gorilla Websocket包中的一个结构体,用于将HTTP请求升级到WebSocket连接。这是实现WebSocket通信的关键步骤。
详细解读字段和方法:
ReadBufferSizeWriteBufferSize:这些字段设置了 WebSocket 连接的读写缓冲区大小。缓冲区大小影响数据传输的性能。
Upgrade 方法,Upgrader结构体的Upgrade方法负责执行从HTTP到WebSocket的升级过程。这个方法接收响应写入器,HTTP请求指针,以及一个可选响应头(http.Header)作为参数,并返回一个websocket连接,这个连接是*websocket.Conn类型的。如果升级失败则返回错误。

*websocket.Conn类型:是WebSocket连接的核心类型
得到了这个连接,就可以进行数组操作了,这个和TCP那个Conn的用法很像。这里之间来看这个连接的方法和字段。
具体属性:
1.RemoteAddr和LocalAddr
2.Subprotocol:返回协商的webSocket子协议
3.underlyingConn:返回底层的网络连接
方法:
ReadMessage:用于从WebSocket连接中读取数据,没有参数,返回值有三个,消息类型(文本或二进制消息),消息内容(字节切片),和error。
WriteMessage:用于向WebSocket连接中写入一个消息,参数:消息类型(文本或二进制,传参这么传websocket.TextMessage或websocket.BinaryMessage)和要发送的消息内容(字节切片)。返回值只有个error。

使用场景:在处理HTTP请求的函数中使用Upgrader可以轻松的将HTTP连接转换为WebSocket连接,从而允许双向通信。
总之:Upgrader提供了WebSocket协议所需的协议切换机制,使得你可以在web应用程序中实现实时,双向的通信功能。

了解完这些这个代码那看懂肯定轻轻松松。

我这里来总结以下代码的流程
上了看到了一个var 定义了upgrader结构体,设置了websocket连接的读写缓冲区大小。

main函数:
注册函数,一旦请求/ws,就会由handlewebsocket函数来进行处理。然后打开服务器接一下错误就完事了。

注册函数:
先调用upgrader的upgrade方法,将HTTP升级成websocket,拿到websocket.Conn就可以进行数据操作了。这个连接也是要记得关。

有个无限循环,因为注册函数是服务器端的,直接等待客户端发信息,所以readmessage一直在等请求信息。接到信息后进行数据发送给客户端。然后基本结束。

思考:从代码上看,显然websocket编程是需要建立连接的。websocket是一种在单个TCP连接上提供全双工通信信道的协议,在通信双方(客户端和服务器)首先通过HTTP协议握手,然后这个连接会被升级到Websocket连接。这个过程中conn代表的就是这个持久化的WebSocket连接,它允许双发在连续持续打开的情况下互相发送数据和接受数据。

引发的一个疑问:

HTTP 协议本身是无状态的,通常被认为是不持久化连接的。它基于请求-响应模式,意味着每次通信都是独立的:客户端向服务器发送一个请求,服务器处理请求并返回响应,然后连接关闭。这种模式通常称为“无连接”或“短连接”。

我对连接这个点有疑问
回答:这里我混淆了无连接的性质,当时没有理解好。
HTTP协议本身是无状态的,通常是基于请求-响应模式,但是每次请求通常会打开一个新的TCP连接,然后才关闭,这个才是无连接的真正理解。
在webSocket的握手过程,websocket握手基于一个标注的HTTP请求,这个请求包含了特定的头部信息,表明客户端希望将连接升级到websocket,如果服务器支持websoket,它会发送一个特殊的HTTP响应,同意这个升级。一旦握手完成,同一TCP连接就被升级为WebSocket连接。从那时起,这个连接就转换为持久的全双工通信通道,不再是原先的无连接HTTP模式。
因此:HTTP协议虽然本身无连接,但是Websocket利用HTTP的初始握手建立的持久化连接。


golang rpc编程

ps:golang会专门学grpc,这里只是一个有关rpc的简单了解学习。

RPC是什么?
RPC(远程过程调用)是一种允许程序调用另一台计算机上的程序或服务的奇数,就好像这些程序或服务在本地一样。RPC抽象了网络通信的复杂性,使开发者能够像调用本地函数一样调用远程函数。它通常涉及客户端-服务器架构,客户端发送请求到服务器斌等待响应。

RPC实现通常负责序列化(将对象转换为可传输的格式)和反序列化(将传输的数据转换回对象)以及网络通信。RPC提供了一种透明的方式来进行跨网络的服务调用,使得分布式系统的开发和维护更加简单高效

golang提供了标准库中的net/rpc包来实现远程过程调用RPC编程。通过RPC,我们可以让客户端之间调用服务端的函数就像调用本地的函数一样。

下面是一个简单的golang RPC示例,包含服务端和客户端两部分。
服务端:

package main

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

type Args struct {
	A, B int
}

type Arith struct{}

func (t *Arith) Multiply(args *Args, reply *int) error {
	*reply = args.A * args.B
	return nil
}

func (t *Arith) Divide(args *Args, reply *float64) error {
	if args.B == 0 {
		return errors.New("divide by zero")
	}
	*reply = float64(args.A) / float64(args.B)
	return nil
}

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

	l, err := net.Listen("tcp", ":1234")
	if err != nil {
		log.Fatal("listen error: ", err)
	}

	log.Println("rpc server listening on :1234 ...")
	rpc.Accept(l)
}

先把每部分啥是啥看懂:
1.定义数据结构和服务
Args 结构体定义了 RPC 方法的参数,包含两个整数 A 和 B
Arith 类型定义了 RPC 服务,可以看到它有两个方法:Multiply 和 Divide
2.实现RPC方法:
Multiply 方法接收 Args 类型的参数和一个整数指针作为回复。它将 A 和 B 相乘的结果赋给回复。
Divide 方法也接收 Args 类型的参数和一个浮点数指针作为回复。它执行除法操作,并处理除以零的错误情况。
3.启动 RPC 服务
使用 new(Arith) 创建一个 Arith 实例,并通过 rpc.Register 注册为 RPC 服务。
通过 net.Listen 在 TCP 协议的 1234 端口上监听传入的连接。
使用 rpc.Accept 接收连接并为每个连接提供服务。

rpc.Register()是net/rpc包中的函数,用于注册一个RPC服务。这个函数的主要目的是将给定的对象的方法注册为远程过程调用的服务。
当调用这个函数时,它会检查提供的对象的公开方法,并将它们注册为可通过网络远程调用的方法。这些方法必须满足特定的签名要求,比如必须有两个参数,第二个参数是指针类型,并且有一个error类型的返回值。
通过这种方式,rpc.Register使得对象的方法可以从远程客户端通过RPC调用,从而实现跨网络的函数调用。这对构建分布式系统和服务至关重要。

rpc.Accept()是net/rpc包中的函数,这个函数用于接收指定监听器上的连接的请求,并为每个连接请求提供RPC服务。当服务器调用rpc.Accept时,它会阻塞并等待新的连接。一旦有新的连接,服务器会处理该连接上的RPC调用。这个函数是启动RPC服务并响应远程调用的关键步骤。通过调用这个函数,服务器能够不断接收并处理来自客户端的远程调用请求。这使得服务器能够长时间允许,持续的响应客户端请求。

服务端代码总结:
该服务端定义了一个Arith结构体,其中包含Multiply和Divide两个方法。Multiply方法实现了两个整数相乘的功能,Divide方法实现了两个整数相除的功能。当除数为0时,Divide方法将返回一个错误。
服务端在启动时,首先使用 rpc.Register函数注册了Arith类型,并监听端口号为1234的TCP连接,当有客户端连接时,服务端会收到请求并进行处理

客户端代码

epackage main

import (
	"fmt"
	"log"
	"net/rpc"
)

type Args struct {
	A, B int
}

func main() {
	client, err := rpc.Dial("tcp", "localhost:1234")
	if err != nil {
		log.Fatal("dialing error: ", err)
	}

	args := Args{4, 5}
	var reply int

	// 调用Multiply方法
	err = client.Call("Arith.Multiply", &args, &reply)
	if err != nil {
		log.Fatal("arith error: ", err)
	}

	fmt.Printf("Multiply: %d * %d = %d\n", args.A, args.B, reply)

	args = Args{10, 0}
	var fReply float64

	// 调用Divide方法
	err = client.Call("Arith.Divide", &args, &fReply)
	if err != nil {
		log.Fatal("arith error: ", err)
	}

	fmt.Printf("Divide: %d / %d = %f\n", args.A, args.B, fReply)
}

先看懂代码由哪几部分组成
也定义了一个Args结构体,参数和服务端一样。

1.建立连接:使用rpc.Dial连接到RPC服务器(这个例子中,指定连接的是地址为localhost:1234,会返回一个RPC客户端实例和可能的错误。
2.准备参数和接收回复的变量:定义了Args结构体,并创建了一个变量args用于存放方法参数和一个变量reply用于接收方法的返回值。
3.调用远程方法:使用client.Call调用远程服务器上的方法。方法的名称(如Arith.Multiply),参数(&args)和回复指针(&reply)作为参数传递给Call方法.
4.处理错误和输出结果:检查Call方法返回的错误,并输出RPC调用的结果。

可能看着有点不懂,这里对里面用到的函数进一步解读

client, err := rpc.Dial(“tcp”, “localhost:1234”)
是go语言net/rpc包中的函数,用于创建一个到RPC服务器的客户端连接,参数:1,网络协议,2,服务器的地址和端口。
返回值:*rpc.Client对象,用于后续的RPC调用,使用这个客户端对象,你可以调用服务器上注册的远程过程,就像在本地调用普通函数一样。

*rpc.Client对象解读:
这个类型代表一个RPC客户端实例。这个客户端实例用于RPC服务器进行通信。
1.它有Client.Call方法,你可以发起一个同步的远程过程调用。这意味着Call方法会阻塞,直到远程调用返回结果或发生错误。Client.Call()接收三个参数,serviceMethod字符串,格式为"Service.Method",这制定了要调用的远程服务及其方法。args参数,这是要传递给远程方法的参数。reply参数,这是一个指向回复值得指针,远程方法调用得结果将被写入到这个位置。返回值:该方法返回一个error类型得值。Client.Call方法通常用于需要等待结果才能继续指向得场景,例如当客户端需要处理服务器返回得数据后者需要根据服务器响应来做出决策时。
2.还提供了Go方法,用于异步调用远程方法。这种方式不会阻塞调用者,允许你在等待远程调用结果的同时执行其他操作。
3.连接管理,客户端维护于RPC服务器的网络连接,负责数据的发送和接收。
总之该对象时实现客户端和服务器之间通信的核心,它抽象了底层的网络操作,使得开发者可以像调用本地方法一样调用远程方法。

继续看上面代码,通过代码来看这个Call函数怎么用的:

args := Args{4, 5}
	var reply int

	// 调用Multiply方法
	err = client.Call("Arith.Multiply", &args, &reply)
	if err != nil {
		log.Fatal("arith error: ", err)
	}

	fmt.Printf("Multiply: %d * %d = %d\n", args.A, args.B, reply)

err = client.Call(“Arith.Multiply”, &args, &reply)先单独分析这一句,来看看这个call函数到底怎么用的。
”Arith.Multiply" 是要调用的远程函数。它由两部分组成:服务名(Arith)和
方法名
(Multiply),这个服务我在服务端注册了。
&args 是传递给远程函数 Multiply 的参数,args 是 Args 类型的变量包含了该方法所需的所有输入参数
&reply 是一个指向回复值的指针,远程方法执行后的结果将写入到这个变量中
返回值只有一个错误。注意这个调用过程是阻塞的,程序会在这一行代码等待,直到远程调用完成并返回结果。

限制看懂这个段代码应该不难了,创建了Args结构体然后进行了实例化,然后定义了reply,这个是拿来接结果的。然后就可以通过call方法调用服务端上的Arith服务的Multiply函数了。下面那段代码如法炮制 。一样的流程。这样就实现了调用服务端上的函数了。

客户端代码流程总结:
客户端通过rpc.Dial函数连接到服务端,这个函数会返回client对象,这个对象就是操作的关键,然后通过client.Call函数调用服务端的方法,客户端首先调用了Multiply方法,传入参数4,5,并接收服务段返回的结果,然后调用Divide方法,传入参数10和0,由于除数为0,服务端将返回一个错误。

有一点需要注意Golang的RPC编程中,客户端,服务端的方法名、参数和返回值类型必须一致,否则会出现调用失败。


golang网络编程学习总结:

刚刚接触网络编程时,我是啥也看不懂,空有计算机网络的知识,这些函数我都不知道有什么用,但是经过理解例子,不断地对这些函数进行学习,和代码地逻辑流程,现在回头再看第一天地实践例子,已经感觉是非常轻松了。这也让我感觉到写博客一方面帮我记忆,写的过程中也记下了思考。

下一个板块就是gin框架和grpc的学习了。

02-03 11:46