当前位置: 首页 > news >正文

仓颉语言 -- 网络编程

使用新版本 (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()
}

在这里插入图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 编程入门:大学新生的指南与策略
  • Docker 和 Docker Compose 的区别对比
  • AI学习指南机器学习篇-Q学习的优缺点
  • Python面试宝典第25题:括号生成
  • 反序列化靶机serial
  • ThreadLocal:线程本地变量的作用与应用
  • 8G内存的Mac够用吗 ?苹果电脑内存满了怎么清理?可以有效地管理和优化你的Mac电脑内存,确保设备运行流畅
  • 开源跨平台SQL编辑器:Beekeeper Studio
  • Python中的异常处理除了Try语句,你还会啥?
  • 安装jdk和tomcat
  • KVM+GFS分布式文件系统构建KVM高可用
  • Vue3+TypeScript+printjs 实现标签批量打印功能
  • Spingboot请求tcp 方式
  • 写一个图片裁剪的js,JavaScript图片裁剪插件PlusCropper
  • 【数值计算方法】数值积分微分-python实现-p2
  • 2018一半小结一波
  • Angular4 模板式表单用法以及验证
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • express + mock 让前后台并行开发
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • JavaScript创建对象的四种方式
  • laravel 用artisan创建自己的模板
  • Sequelize 中文文档 v4 - Getting started - 入门
  • vue 个人积累(使用工具,组件)
  • Wamp集成环境 添加PHP的新版本
  • windows下如何用phpstorm同步测试服务器
  • 闭包--闭包作用之保存(一)
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 思否第一天
  • 新版博客前端前瞻
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • 新海诚画集[秒速5センチメートル:樱花抄·春]
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ​埃文科技受邀出席2024 “数据要素×”生态大会​
  • # Redis 入门到精通(七)-- redis 删除策略
  • #14vue3生成表单并跳转到外部地址的方式
  • (2)MFC+openGL单文档框架glFrame
  • (ISPRS,2021)具有遥感知识图谱的鲁棒深度对齐网络用于零样本和广义零样本遥感图像场景分类
  • (二刷)代码随想录第16天|104.二叉树的最大深度 559.n叉树的最大深度● 111.二叉树的最小深度● 222.完全二叉树的节点个数
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (十)Flink Table API 和 SQL 基本概念
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (正则)提取页面里的img标签
  • (转)es进行聚合操作时提示Fielddata is disabled on text fields by default
  • (转)创业家杂志:UCWEB天使第一步
  • ... 是什么 ?... 有什么用处?
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .net 程序发生了一个不可捕获的异常
  • @select 怎么写存储过程_你知道select语句和update语句分别是怎么执行的吗?
  • [ Linux ] git工具的基本使用(仓库的构建,提交)
  • [20140403]查询是否产生日志