0


仓颉语言 -- 网络编程

使用新版本 (2024-07-19 16:10发布的)

1、网络编程概述

**网络通信是

两个设备通过计算机网络进行数据交换的过程

。通过编写软件达成网络通信的行为即为

网络编程

。**

仓颉为开发者提供了基础的网络编程功能,**在仓颉标准库中,用户可使用

std 模块

下的

socket 包

来实现传输层网络通信**。

**在传输层协议中,分为

不可靠传输

可靠传输

两种,仓颉将其抽象为

DatagramSocket

StreamSocket

。其中不可靠传输协议常见的是

UDP

,可靠传输协议常见的是

TCP

,仓颉分别将其抽象为

UdpSocket

TcpSocket

。另外,仓颉也实现了

对传输层 Unix Domain 协议的支持

,并支持其通过可靠和不可靠传输两种方式进行通信。**

而在应用层协议中,较为常见的是 HTTP 协议,常用于开发 Web 应用程序等。**当前 HTTP 协议已有多个版本,仓颉目前支持

HTTP/1.1

HTTP/2.0

等。**

**另外,

WebSocket 

作为一种提升 Web 服务端与客户端间的通信效率的应用层协议,仓颉将其抽象为

WebSocket 对象

,并支持

从 HTTP 协议升级至 WebSocket 协议

**。

**需要注意的是,仓颉的网络编程是

阻塞式的

。但

被阻塞的是仓颉线程

,阻塞中的仓颉线程会将系统线程让渡出去,因此并不会真正阻塞一个系统线程。**

2、Socket 编程

**仓颉的 Socket 编程指的是

基于传输层协议实现网络传输数据包的功能

。**

**

在可靠传输场景下

,仓颉分别启动客户端套接字和服务端套接字。

客户端

套接字

必须指定将要连接的远端地

址,

可选择性地绑定本端地址

,在连接成功后,才可以收发报文。而

服务端

套接字

必须绑定本端地址

,在绑定成功后,才可以收发报文。**

**

在不可靠传输场景下

,套接字

无需区分客户端和服务端

,仓颉分别

启动两个套接字

进行数据传输。套接字

必须绑定本端地址

,绑定成功后,才可以收发报文。并且,套接字也

可选择性地指定远端连接地址

,指定后将仅接受指定的远端地址的报文,同时在 send 时无需指定远端地址,报文将发送至成功连接的地址。**

2.1 Tcp 编程

Tcp 作为一种常见的可靠传输协议,以 Tcp 类型套接字举例,仓颉在可靠传输场景下的可参考的编程模型如下:

  1. 创建服务端套接字,并指定本端绑定地址。
  2. 执行绑定。
  3. 执行 accept 动作,将阻塞等待,直到获取到一个客户端套接字
  4. 连接。
  5. 同步创建客户端套接字,并指定远端的待连接的地址。
  6. 执行连接。
  7. 连接成功后,服务端会在 accept 接口返回一个新的套接字,此时服务端可以通过此套接字进行读写操作,即收发报文。客户端则可以直接进行读写操作。

Tcp 服务端和客户端程序示例如下:

import std.socket.*import std.time.*import std.sync.*letSERVER_PORT: UInt16 =8080

func runTcpServer(){try(serverSocket =TcpServerSocket(bindAt:SERVER_PORT)){serverSocket.bind()try(client = serverSocket.accept()){let buf =Array<Byte>(10, item:0)let count = client.read(buf)// 服务端读取到的数据为: [1, 2, 3, 4, 5, 0, 0, 0, 0, 0]println("Server read ${count} bytes: ${buf}")}}}main(): Int64 {
    spawn {runTcpServer()}sleep(Duration.millisecond *500)try(socket =TcpSocket("127.0.0.1",SERVER_PORT)){
        socket.connect()
        socket.write(Array<Byte>([1,2,3,4,5]))}return0}

在这里插入图片描述

2.2 Udp 编程

Udp 作为一种常见的不可靠传输协议,以 Udp 类型套接字举例,仓颉在不可靠传输场景下的可参考的编程模型如下:

  1. 创建套接字,并指定本端绑定地址。
  2. 执行绑定。
  3. 指定远端地址进行报文发送。
  4. 不连接远端地址场景下,可以收取来自不同远端地址的报文,5. 5. 并返回远端地址信息。

Udp 收发报文程序示例如下:

import std.socket.*import std.time.*import std.sync.*letSERVER_PORT: UInt16 =8080

func runUpdServer(){try(serverSocket =UdpSocket(bindAt:SERVER_PORT)){serverSocket.bind()let buf =Array<Byte>(3, item:0)let(clientAddr, count)= serverSocket.receiveFrom(buf)let sender = clientAddr.hostAddress

        // 套接字收取到的报文以及远端地址: [1, 2, 3], 127.0.0.1println("Server receive ${count} bytes: ${buf} from ${sender}")}}main(): Int64 {let future = spawn {runUpdServer()}sleep(Duration.second)try(udpSocket =UdpSocket(bindAt:0)){
        udpSocket.sendTimeout = Duration.second *2udpSocket.bind()
        udpSocket.sendTo(SocketAddress("127.0.0.1",SERVER_PORT),Array<Byte>([1,2,3]))}

    future.get()return0}

在这里插入图片描述

3、HTTP 编程

**HTTP 作为一种

通用的应用层协议

,通过

请求-响应的机制

实现数据传输,客户端发送请求,服务端返回响应**。请求和响应的格式是固定的,由报文头和报文体组成。

**常用的请求类型为

GET

POST

GET 请求只有报文头

,用于向服务器请求应用层数据,

POST 请求带有报文体

,以一个空行与报文头进行分隔,用于向服务器提供应用层数据。**

请求-响应的报文头字段内容较多,此处不再一一赘述,**仓颉支持 HTTP 1.0/1.1/2.0 等协议版本,开发者可以基于协议 RFC 9110、9112、9113、9218、7541 以及仓颉所提供的

HttpRequestBuilder

HttpResponseBuilder

类构造请求及响应报文。**

以下示例展示了如何使用仓颉进行客户端和服务端编程,实现的功能是客户端发送请求头为 GET /hello 的请求,服务端返回响应,响应体为 “Hello Cangjie!”,代码如下:

import net.http.*import std.time.*import std.sync.*

func startServer(): Unit {// 1. 构建 Server 实例let server =ServerBuilder().addr("127.0.0.1").port(8080).build()// 2. 注册请求处理逻辑
    server.distributor.register("/hello",{httpContext =>
        httpContext.responseBuilder.body("Hello Cangjie!")})// 3. 启动服务
    server.serve()}

func startClient(): Unit {let buf =Array<UInt8>(32, item:UInt8(0))// 1. 构建 client 实例let client =ClientBuilder().build()// 2. 发送 requestlet resp = client.get("http://127.0.0.1:8080/hello")// 3. 读取response
    resp.body.read(buf)println(String.fromUtf8(buf))// 4. 关闭连接
    client.close()}main(){
    spawn {startServer()}sleep(Duration.second)startClient()}

在这里插入图片描述

4、WebSocket 编程

**在网络编程中,WebSocket 也是一种

常用的应用层协议

,与 HTTP 一样,它也

基于 TCP 协议

之上,并且

常用于 web 服务端应用开发

。**

**不同于 HTTP 的是, WebSocket 只需要

客户端和服务端进行一次握手

,即可

创建长久的连接

,并且

进行双向的数据传输

。即,基于 WebSocket 实现的服务端可以主动传输数据给客户端,从而

实现实时通讯

。**

**WebSocket 是

一个独立的协议

,它与 HTTP 的关联在于,

它的握手被 HTTP 服务端解释为一个升级请求

。因此,

仓颉将 WebSocket 包含在 http 包中

。**

**仓颉将 WebSocket 协议通信机制抽象为

WebSocket 类

**,提供方法将一个 http/1.1 或 http/2.0 服务端句柄升级到 WebSocket 协议实例,通过返回的 WebSocket 实例进行 WebSocket 通信,例如数据报文的读写。

**在仓颉中,WebSocket 所传输的

数据基本单元

称为

,帧分为两类,

一类为传输控制信息的帧

,即 Close Frame 用于关闭连接, Ping Frame 用于实现 Keep-Alive , Pong Frame 是 Ping Frame 的响应类型,

另一类是传输应用数据的帧

,应用数据帧支持分段传输。**

**仓颉的

三个属性构成

,其中

fin

frameType

共同说明了帧是否分段和帧的类型,

payload

为帧的载荷,除此之外开发者无需关心其他属性即可进行报文传输。**

如下示例展示了 WebSocket 的握手以及消息收发过程:创建 HTTP 客户端和服务端,分别发起 WebSocket 升级(或握手),握手成功后开始帧的读写。

import net.http.*import encoding.url.*import std.time.*import std.sync.*import std.collection.*import std.log.*let server =ServerBuilder().addr("127.0.0.1").port(0).build()// client:main(){// 1 启动服务器
    spawn {startServer()}sleep(Duration.millisecond *200)let client =ClientBuilder().build()let u =URL.parse("ws://127.0.0.1:${server.port}/webSocket")let subProtocol =ArrayList<String>(["foo1","bar1"])let headers =HttpHeaders()
    headers.add("test","echo")// 2 完成 WebSocket 握手,获取 WebSocket 实例let websocket: WebSocket
    let respHeaders:HttpHeaders(websocket, respHeaders)= WebSocket.upgradeFromClient(client, u, subProtocols: subProtocol, headers: headers)
    client.close()println("subProtocol: ${websocket.subProtocol}")// fool1println(respHeaders.getFirst("rsp")??"")// echo// 3 消息收发// 发送 hello
    websocket.write(TextWebFrame,"hello".toArray())// 收let data =ArrayList<UInt8>()var frame = websocket.read()while(true){match(frame.frameType){case ContinuationWebFrame =>
                data.appendAll(frame.payload)if(frame.fin){break}case TextWebFrame | BinaryWebFrame =>if(!data.isEmpty()){throwException("invalid frame")}
                data.appendAll(frame.payload)if(frame.fin){break}case CloseWebFrame =>
                websocket.write(CloseWebFrame, frame.payload)breakcase PingWebFrame =>
                websocket.writePongFrame(frame.payload)case _ =>()}
        frame = websocket.read()}println("data size: ${data.size}")// 4097println("last item: ${String.fromUtf8(Array(data)[4096])}")// a// 4 关闭 websocket,// 收发 CloseFrame
    websocket.writeCloseFrame(status:1000)let websocketFrame = websocket.read()println("close frame type: ${websocketFrame.frameType}")// CloseWebFrameprintln("close frame payload: ${websocketFrame.payload}")// 3, 232// 关闭底层连接
    websocket.closeConn()

    server.close()}

func startServer(){// 1 注册 handler
    server.distributor.register("/webSocket", handler1)
    server.logger.level =OFF
    server.serve()}// server:
func handler1(ctx: HttpContext): Unit {// 2 完成 websocket 握手,获取 websocket 实例let websocketServer = WebSocket.upgradeFromServer(ctx, subProtocols:ArrayList<String>(["foo","bar","foo1"]),
        userFunc:{request: HttpRequest =>let value = request.headers.getFirst("test")??""let headers =HttpHeaders()
            headers.add("rsp", value)
            headers
        })// 3 消息收发// 收 hellolet data =ArrayList<UInt8>()var frame = websocketServer.read()while(true){match(frame.frameType){case ContinuationWebFrame =>
                data.appendAll(frame.payload)if(frame.fin){break}case TextWebFrame | BinaryWebFrame =>if(!data.isEmpty()){throwException("invalid frame")}
                data.appendAll(frame.payload)if(frame.fin){break}case CloseWebFrame =>
                websocketServer.write(CloseWebFrame, frame.payload)breakcase PingWebFrame =>
                websocketServer.writePongFrame(frame.payload)case _ =>()}
        frame = websocketServer.read()}println("data: ${String.fromUtf8(Array(data))}")// hello// 发 4097 个 a
    websocketServer.write(TextWebFrame,Array<UInt8>(4097, item:97))// 4 关闭 websocket,// 收发 CloseFramelet websocketFrame = websocketServer.read()println("close frame type: ${websocketFrame.frameType}")// CloseWebFrameprintln("close frame payload: ${websocketFrame.payload}")// 3, 232
    websocketServer.write(CloseWebFrame, websocketFrame.payload)// 关闭底层连接
    websocketServer.closeConn()}

在这里插入图片描述


本文转载自: https://blog.csdn.net/chinusyan/article/details/140772781
版权归原作者 chinusyan 所有, 如有侵权,请联系我们删除。

“仓颉语言 -- 网络编程”的评论:

还没有评论