仓颉语言 -- 网络编程

embedded/2024/10/18 20:46:22/

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

1、网络编程概述

网络通信是两个设备通过计算机网络进行数据交换的过程。通过编写软件达成网络通信的行为即为网络编程

仓颉为开发者提供了基础的网络编程功能,仓颉标准库中,用户可使用 std 模块下的 socket 包来实现传输层网络通信

在传输层协议中,分为不可靠传输可靠传输两种,仓颉将其抽象为 DatagramSocketStreamSocket。其中不可靠传输协议常见的是 UDP,可靠传输协议常见的是 TCP仓颉分别将其抽象为 UdpSocketTcpSocket。另外,仓颉也实现了对传输层 Unix Domain 协议的支持,并支持其通过可靠和不可靠传输两种方式进行通信。

而在应用层协议中,较为常见的是 HTTP 协议,常用于开发 Web 应用程序等。当前 HTTP 协议已有多个版本,仓颉目前支持 HTTP/1.1HTTP/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.*let SERVER_PORT: UInt16 = 8080func 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]))}return 0
}

在这里插入图片描述

2.2 Udp 编程

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

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

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

import std.socket.*
import std.time.*
import std.sync.*let SERVER_PORT: UInt16 = 8080func 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()return 0
}

在这里插入图片描述

3、HTTP 编程

HTTP 作为一种通用的应用层协议,通过请求-响应的机制实现数据传输,客户端发送请求,服务端返回响应。请求和响应的格式是固定的,由报文头和报文体组成。

常用的请求类型为 GETPOSTGET 请求只有报文头,用于向服务器请求应用层数据,POST 请求带有报文体,以一个空行与报文头进行分隔,用于向服务器提供应用层数据。

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

以下示例展示了如何使用仓颉进行客户端和服务端编程,实现的功能是客户端发送请求头为 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. 读取responseresp.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 的响应类型,另一类是传输应用数据的帧,应用数据帧支持分段传输。

仓颉三个属性构成,其中 finframeType 共同说明了帧是否分段和帧的类型,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: WebSocketlet respHeaders: HttpHeaders(websocket, respHeaders) = WebSocket.upgradeFromClient(client, u, subProtocols: subProtocol, headers: headers)client.close()println("subProtocol: ${websocket.subProtocol}")      // fool1println(respHeaders.getFirst("rsp") ?? "") // echo// 3 消息收发// 发送 hellowebsocket.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()) {throw Exception("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,// 收发 CloseFramewebsocket.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 注册 handlerserver.distributor.register("/webSocket", handler1)server.logger.level = OFFserver.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()) {throw Exception("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 个 awebsocketServer.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, 232websocketServer.write(CloseWebFrame, websocketFrame.payload)// 关闭底层连接websocketServer.closeConn()
}

在这里插入图片描述


http://www.ppmy.cn/embedded/88846.html

相关文章

Educational Codeforces Round 168 E. Level Up

原题链接&#xff1a;Problem - E - Codeforces 题意&#xff1a;有n个怪物&#xff0c;每个怪物都有一个等级&#xff0c;主角从左到右打怪物&#xff0c;如果主角的等级严格大于了怪物的等级&#xff0c;那么怪物就会逃跑&#xff0c;主角每升一级需要杀死k个怪物&#xff0…

pt模型转onnx模型,onnx模型转engine模型,pt模型转engine模型详细教程(TensorRT,jetpack)

背景 背景是需要在nvidia jetpack4.5.1的arm64设备上跑yolov8,用TensorRT加速&#xff0c;需要用*.engine格式的模型&#xff0c;但是手头上的是pt格式模型&#xff0c;众所周知小板子的内存都很小&#xff0c;连安装ultralytics依赖库的容量都没有&#xff0c;所以我想到在wi…

jdk1.8中HashMap为什么不直接用红黑树

最开始使用链表的时候&#xff0c;空间占用比较少&#xff0c;而且由于链表短&#xff0c;所以查询时间也没有太大的问题。可是当链表越来越长&#xff0c;需要用红黑树的形式来保证查询的效率。 参考资料&#xff1a; https://blog.51cto.com/u_13294304/3075723

Windows内核态开发笔记

文章目录 r3/r0通信x64 HOOK回调监控进程强杀minifilterObRegisterCallbacksWFP后记 r3/r0通信 用户态 #include <Windows.h> #include <stdio.h>#define SENDSTR CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) void main() {HANDLE …

零基础入门转录组数据分析——机器学习算法之SVM-RFE(筛选特征基因)

零基础入门转录组数据分析——机器学习算法之SVM-RFE&#xff08;筛选特征基因&#xff09; 目录 零基础入门转录组数据分析——机器学习算法之SVM-RFE&#xff08;筛选特征基因&#xff09;1. SVM-RFE基础知识2. SVM-RFE&#xff08;Rstudio&#xff09;——代码实操2. 1 数据…

基于YOLOv8的船舶检测系统

基于YOLOv8的船舶检测系统 (价格85) 包含 【散货船&#xff0c;集装箱船&#xff0c;渔船&#xff0c;杂货船&#xff0c;矿砂船&#xff0c;客船】 6个类 通过PYQT构建UI界面&#xff0c;包含图片检测&#xff0c;视频检测&#xff0c;摄像头实时检测。 &#xff08;该…

【虚拟仿真】Unity3D中实现2DUI显示在3D物体旁边

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群:398291828大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 这篇文章来实现2DUI显示在3D物体旁边,当我们需要在3D模型旁边显示2DUI的时候,比如人物的对…

VBA字典与数组第十七讲:工作表数组大小的扩展及意义

《VBA数组与字典方案》教程&#xff08;10144533&#xff09;是我推出的第三套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;字典是VBA的精华&#xff0c;我要求学员必学。7.1.3.9教程和手册掌握后&#xff0c;可以解决大多数工作中遇到的实际问题。…