重学仓颉-15网络编程完全指南

概述

网络编程是现代软件开发中不可或缺的一部分,它使得不同设备之间能够通过网络进行数据交换。仓颉语言为开发者提供了强大而完整的网络编程功能,涵盖了从传输层到应用层的各种协议支持。

仓颉语言的网络编程具有以下特点:

  • 阻塞式设计:虽然网络操作是阻塞的,但被阻塞的是仓颉线程,系统线程会被让渡出去,不会真正阻塞系统资源
  • 分层抽象:提供了清晰的传输层和应用层抽象
  • 协议支持:支持 TCP、UDP、HTTP/1.0/1.1/2.0、WebSocket 等主流协议
  • 类型安全:利用仓颉语言的类型系统确保网络操作的安全性

网络编程架构

传输层抽象

仓颉语言将传输层协议抽象为两个主要类型:

  1. DatagramSocket(数据报套接字):用于不可靠传输,如 UDP
  2. StreamSocket(流套接字):用于可靠传输,如 TCP

应用层协议

  • HTTP 协议:支持 HTTP/1.0、HTTP/1.1、HTTP/2.0
  • WebSocket 协议:支持从 HTTP 协议升级到 WebSocket

Socket 编程详解

TCP 编程(可靠传输)

TCP 是一种面向连接的可靠传输协议,适用于需要保证数据完整性的场景。

TCP 服务端编程模型

cangjie 复制代码
package cangjie_blog
import std.net.{TcpServerSocket, TcpSocket}

// TCP 服务端示例
func runTcpServer(port: UInt16) {
    try (serverSocket = TcpServerSocket(bindAt: port)) {
        // 1. 绑定地址和端口
        serverSocket.bind()
        println("TCP 服务器启动,监听端口: ${port}")

        // 2. 接受客户端连接(阻塞等待)
        try (client = serverSocket.accept()) {
            println("客户端已连接: ${client.remoteAddress}")

            // 3. 读取客户端数据
            let buf = Array<Byte>(1024, repeat: 0)
            let count = client.read(buf)

            // 4. 处理接收到的数据
            let receivedData = buf[..count]
            println("服务端接收到 ${count} 字节数据: ${receivedData}")

            // 5. 发送响应数据
            let response = "Hello from TCP Server!".toArray()
            client.write(response)
            println("已发送响应数据")
        }
    }
}

// TCP 客户端示例
func runTcpClient(host: String, port: UInt16) {
    try (socket = TcpSocket(host, port)) {
        // 1. 连接到服务器
        socket.connect()
        println("已连接到服务器: ${host}:${port}")

        // 2. 发送数据
        let message = "Hello from TCP Client!".toArray()
        socket.write(message)
        println("已发送数据: ${message}")

        // 3. 接收服务器响应
        let buf = Array<Byte>(1024, repeat: 0)
        let count = socket.read(buf)
        let response = String.fromUtf8(buf[..count])
        println("接收到服务器响应: ${response}")
    }
}

// 主函数演示 TCP 通信
main(): Int64 {
    let serverPort: UInt16 = 8080

    // 启动服务端(在后台线程中)
    let serverFuture = spawn {
        runTcpServer(serverPort)
    }

    // 等待服务端启动
    sleep(Duration.millisecond * 500)

    // 启动客户端
    runTcpClient("127.0.0.1", serverPort)

    // 等待服务端完成
    serverFuture.get()

    return 0
}

TCP 编程关键点

  1. 服务端流程

    • 创建 TcpServerSocket 实例
    • 调用 bind() 绑定地址和端口
    • 调用 accept() 等待客户端连接
    • 通过返回的客户端套接字进行数据读写
  2. 客户端流程

    • 创建 TcpSocket 实例
    • 调用 connect() 连接到服务器
    • 直接进行数据读写操作
  3. 数据传输

    • 使用 read() 方法读取数据
    • 使用 write() 方法发送数据
    • 数据以字节数组形式传输

UDP 编程(不可靠传输)

UDP 是一种无连接的不可靠传输协议,适用于对实时性要求高但对可靠性要求不高的场景。

UDP 编程示例

cangjie 复制代码
package cangjie_blog

import std.net.{UdpSocket, IPSocketAddress}
// UDP 服务端示例
func runUdpServer(port: UInt16) {
    try (serverSocket = UdpSocket(bindAt: port)) {
        // 1. 绑定地址和端口
        serverSocket.bind()
        println("UDP 服务器启动,监听端口: ${port}")

        // 2. 接收数据(包含发送方地址信息)
        let buf = Array<Byte>(1024, repeat: 0)
        let (clientAddr, count) = serverSocket.receiveFrom(buf)

        // 3. 解析发送方地址
        let senderAddress = (clientAddr as IPSocketAddress)?.address.toString() ?? "未知"
        let senderPort = (clientAddr as IPSocketAddress)?.port ?? 0

        println("接收到来自 ${senderAddress}:${senderPort} 的 ${count} 字节数据")

        // 4. 处理接收到的数据
        let receivedData = buf[..count]
        println("数据内容: ${receivedData}")

        // 5. 发送响应数据
        let response = "UDP Server Response".toArray()
        serverSocket.sendTo(clientAddr, response)
        println("已发送响应数据")
    }
}

// UDP 客户端示例
func runUdpClient(serverHost: String, serverPort: UInt16) {
    try (udpSocket = UdpSocket(bindAt: 0)) {
        // 1. 绑定本地端口(0 表示系统自动分配)
        udpSocket.bind()

        // 2. 设置发送超时时间
        udpSocket.sendTimeout = Duration.second * 5

        // 3. 发送数据到指定地址
        let message = "Hello from UDP Client!".toArray()
        let serverAddr = IPSocketAddress(serverHost, serverPort)

        udpSocket.sendTo(serverAddr, message)
        println("已发送数据到 ${serverHost}:${serverPort}")

        // 4. 接收服务器响应
        let buf = Array<Byte>(1024, repeat: 0)
        let (responseAddr, count) = udpSocket.receiveFrom(buf)

        let response = String.fromUtf8(buf[..count])
        println("接收到服务器响应: ${response}")
    }
}

// 主函数演示 UDP 通信
main(): Int64 {
    let serverPort: UInt16 = 8081

    // 启动服务端
    let serverFuture = spawn {
        runUdpServer(serverPort)
    }

    // 等待服务端启动
    sleep(Duration.second)

    // 启动客户端
    runUdpClient("127.0.0.1", serverPort)

    // 等待服务端完成
    serverFuture.get()

    return 0
}

UDP 编程关键点

  1. 无连接特性

    • UDP 套接字无需建立连接
    • 每个数据包都是独立的
    • 可以同时与多个端点通信
  2. 地址管理

    • 使用 receiveFrom() 接收数据时返回发送方地址
    • 使用 sendTo() 发送数据时需要指定目标地址
  3. 超时设置

    • 可以设置 sendTimeoutreceiveTimeout
    • 超时后操作会抛出异常

HTTP 编程详解

HTTP 是一种应用层协议,通过请求-响应的机制实现数据传输。仓颉语言支持 HTTP/1.0、HTTP/1.1、HTTP/2.0 等版本。

HTTP 协议基础

HTTP 请求和响应由报文头和报文体组成:

  • GET 请求:只有报文头,用于请求数据
  • POST 请求:包含报文体,用于提交数据

HTTP 服务端编程

cangjie 复制代码
package cangjie_blog

import stdx.net.http.{Server, ServerBuilder}
import stdx.log.LogLevel

// cjlint-ignore -start !G.FUN.01 !G.OTH.03
// HTTP 服务端示例
func createHttpServer(): Server {
    // 1. 构建服务器实例
    let server = ServerBuilder().addr("127.0.0.1") // 绑定地址
        .port(0) // 端口 0 表示系统自动分配
        .build()

    // 2. 注册路由处理器
    server.distributor.register(
        "/",
        {
            httpContext =>
                // 根路径处理器
                let response = "Welcome to Cangjie HTTP Server!"
                httpContext.responseBuilder.body(response)
        }
    )

    server.distributor.register(
        "/hello",
        {
            httpContext =>
                // /hello 路径处理器
                let response = "Hello, Cangjie!"
                httpContext.responseBuilder.body(response)
        }
    )

    server.distributor.register("/api/users", { httpContext =>
        // API 路径处理器
        let users = ##"[{"id": 1, "name": "张三", "email": "zhangsan@example.com"},{"id": 2, "name": "李四", "email": "lisi@example.com"}]"##
        httpContext.responseBuilder
            .header("Content-Type", "application/json")
            .body(users)
    })

    server.distributor.register("/submit", {
        httpContext =>
        // POST 请求处理器
        if (httpContext.request.method == "POST") {
            let body = httpContext.request.body
            let byteBuffer = Array<Byte>(4096, repeat: 0)
            body.read(byteBuffer)
            let data = String.fromUtf8(byteBuffer)
            println("接收到 POST 数据: ${data}")

            let response = "数据提交成功: ${data}"
            httpContext.responseBuilder.body(response)
        } else {
            // 非 POST 请求返回错误
            httpContext.responseBuilder.status(405).body("Method Not Allowed")
        }
    })

    // 3. 设置日志级别
    server.logger.level = LogLevel.INFO

    return server
}

// 启动 HTTP 服务器
public func startHttpServer(): Unit {
    let server = createHttpServer()
    println("HTTP 服务器启动,端口: ${server.port}")

    // 启动服务器(阻塞调用)
    server.serve()
}

// 在后台线程中启动服务器
public func startServerInBackground(): Server {
    let server = createHttpServer()

    spawn {
        server.serve()
    }
    // 等待服务器启动
    sleep(Duration.millisecond * 500)
    return server
}

// cjlint-ignore -end

HTTP 客户端编程

cangjie 复制代码
package cangjie_blog

import stdx.net.http.{Client, ClientBuilder, HttpRequestBuilder}

// HTTP 客户端示例
public func createHttpClient(): Client {
    // 1. 构建客户端实例
    let client = ClientBuilder().build()
    return client
}

// 发送 GET 请求
public func sendGetRequest(client: Client, url: String): String {
    try {
        // 发送 GET 请求
        let response = client.get(url)

        // 读取响应体
        let buffer = Array<Byte>(4096, repeat: 0)
        let length = response.body.read(buffer)
        let responseBody = String.fromUtf8(buffer[..length])

        println("GET 请求响应状态: ${response.status}")
        println("响应体: ${responseBody}")

        return responseBody
    } catch (e: Exception) {
        println("GET 请求失败: ${e.message}")
        return ""
    }
}

// 发送 POST 请求
func sendPostRequest(client: Client, url: String, data: String): String {
    try {
        // 构建 POST 请求
        let request = HttpRequestBuilder()
            .method("POST")
            .url(url)
            .header("Content-Type", "text/plain")
            .body(data.toArray())
            .build()

        // 发送请求
        let response = client.send(request)

        // 读取响应体
        let buffer = Array<Byte>(4096, repeat: 0)
        let length = response.body.read(buffer)
        let responseBody = String.fromUtf8(buffer[..length])

        println("POST 请求响应状态: ${response.status}")
        println("响应体: ${responseBody}")

        return responseBody
    } catch (e: Exception) {
        println("POST 请求失败: ${e.message}")
        return ""
    }
}

// 主函数演示 HTTP 客户端
main(): Int64 {
    // 启动服务器
    let server = startServerInBackground()
    let serverUrl = "http://127.0.0.1:${server.port}"

    // 创建客户端
    let client = createHttpClient()

    // 发送 GET 请求
    println("=== 发送 GET 请求 ===")
    sendGetRequest(client, "${serverUrl}/hello")

    // 发送 POST 请求
    println("\n=== 发送 POST 请求 ===")
    sendPostRequest(client, "${serverUrl}/submit", "Hello from HTTP Client!")

    // 获取用户列表
    println("\n=== 获取用户列表 ===")
    sendGetRequest(client, "${serverUrl}/api/users")

    // 关闭客户端和服务器
    client.close()
    server.close()

    return 0
}

HTTP 编程关键点

  1. 服务器构建

    • 使用 ServerBuilder 构建服务器
    • 通过 register() 方法注册路由处理器
    • 调用 serve() 启动服务器
  2. 路由处理

    • 每个路由对应一个处理函数
    • 处理函数接收 HttpContext 参数
    • 通过 responseBuilder 构建响应
  3. 客户端操作

    • 使用 ClientBuilder 构建客户端
    • 支持 GET、POST 等 HTTP 方法
    • 可以设置超时时间和请求头

WebSocket 编程详解

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,常用于实时通信应用。

WebSocket 协议特点

  • 持久连接:一次握手后建立长连接
  • 双向通信:服务端可以主动向客户端发送数据
  • 帧传输:数据以帧为单位传输,支持分段传输

WebSocket 帧类型

  1. 控制帧

    • CloseWebFrame:关闭连接
    • PingWebFrame:心跳检测
    • PongWebFrame:心跳响应
  2. 数据帧

    • TextWebFrame:文本数据
    • BinaryWebFrame:二进制数据
    • ContinuationWebFrame:分段数据

WebSocket 服务端编程

cangjie 复制代码
package cangjie_blog

import stdx.net.http.{WebSocket, Server, ServerBuilder, HttpContext, HttpRequest, HttpHeaders, WebSocketFrame,
    WebSocketFrameType}
import stdx.log.LogLevel
import std.collection.ArrayList
import std.time.DateTime
// WebSocket 连接处理器
func handleWebSocket(ctx: HttpContext): Unit {
    println("收到 WebSocket 升级请求")
    // 1. 完成 WebSocket 握手
    let websocket = WebSocket.upgradeFromServer(
        ctx,
        subProtocols: ArrayList<String>(["chat", "game"]),
        userFunc: {
            request: HttpRequest =>
                // 自定义握手响应头
                let headers = HttpHeaders()
                headers.add("X-WebSocket-Version", "1.0")
                headers.add("X-Server-Time", DateTime.now().toString())
                headers
        }
    )

    println("WebSocket 连接已建立")

    // 2. 处理 WebSocket 消息
    handleWebSocketMessages(websocket)
}

// 处理 WebSocket 消息
func handleWebSocketMessages(websocket: WebSocket): Unit {
    try {
        // 发送欢迎消息
        let welcomeMsg = "欢迎连接到 WebSocket 服务器!"
        websocket.write(WebSocketFrameType.TextWebFrame, welcomeMsg.toArray())

        // 消息处理循环
        while (true) {
            let frame = websocket.read()

            match (frame.frameType) {
                case WebSocketFrameType.TextWebFrame =>
                    // 处理文本消息
                    let message = String.fromUtf8(frame.payload)
                    println("收到文本消息: ${message}")
                    // 发送回显消息
                    let echoMsg = "服务器收到: ${message}"
                    websocket.write(WebSocketFrameType.TextWebFrame, echoMsg.toArray())
                    // 如果是关闭命令,则关闭连接
                    if (message == "close") {
                        break
                    }
                case WebSocketFrameType.BinaryWebFrame =>
                    // 处理二进制消息
                    println("收到二进制消息,长度: ${frame.payload.size}")
                    // 发送确认消息
                    let ackMsg = "二进制消息已接收"
                    websocket.write(WebSocketFrameType.TextWebFrame, ackMsg.toArray())
                case WebSocketFrameType.PingWebFrame =>
                    // 响应 Ping 帧
                    websocket.writePongFrame(frame.payload)
                    println("响应 Ping 帧")
                case CloseWebFrame =>
                    // 处理关闭帧
                    println("收到关闭帧,关闭连接")
                    websocket.write(WebSocketFrameType.CloseWebFrame, frame.payload)
                    break
                case WebSocketFrameType.ContinuationWebFrame =>
                    // 处理分段数据
                    println("收到分段数据帧")
                case _ => println("收到未知帧类型: ${frame.frameType}")
            }
        }
    } catch (e: Exception) {
        println("WebSocket 处理异常: ${e.message}")
    } finally {
        // 关闭 WebSocket 连接
        websocket.closeConn()
        println("WebSocket 连接已关闭")
    }
}
// WebSocket 服务端示例
func createWebSocketServer(): Server {
    let server = ServerBuilder().addr("127.0.0.1").port(0).build()
    // 注册 WebSocket 处理器
    server.distributor.register("/websocket", handleWebSocket)
    server.logger.level = LogLevel.INFO
    return server
}

// 启动 WebSocket 服务器
public func startWebSocketServer(): Server {
    let server = createWebSocketServer()
    spawn {
        server.serve()
    }
    // 等待服务器启动
    sleep(Duration.millisecond * 500)
    return server
}

WebSocket 客户端编程

cangjie 复制代码
package cangjie_blog

import stdx.net.http.{ClientBuilder, WebSocket, HttpHeaders, WebSocketFrameType}
import stdx.encoding.url.{URL}
import std.collection.{ArrayList, collectString, map}

// WebSocket 客户端示例
func createWebSocketClient(serverUrl: String): WebSocket {
    let client = ClientBuilder().build()
    let url = URL.parse(serverUrl)
    // 设置子协议和自定义头
    let subProtocols = ArrayList<String>(["chat"])
    let headers = HttpHeaders()
    headers.add("User-Agent", "Cangjie-WebSocket-Client")
    // 1. 完成 WebSocket 握手
    let (websocket, responseHeaders) = WebSocket.upgradeFromClient(
        client,
        url,
        subProtocols: subProtocols,
        headers: headers
    )

    let responseHeadersStr = responseHeaders |> map {
        t: (String, Collection<String>) => return "${t[0]}: ${t[1] |> collectString(delimiter: ",")}"
    } |> collectString(delimiter: "\n")

    println("WebSocket 握手成功")
    println("选择的子协议: ${websocket.subProtocol}")
    println("服务器响应头: ${responseHeadersStr}")

    // 关闭 HTTP 客户端(WebSocket 使用独立连接)
    client.close()
    return websocket
}

// 发送消息到 WebSocket 服务器
func sendWebSocketMessage(websocket: WebSocket, message: String): Unit {
    try {
        // 发送文本消息
        websocket.write(TextWebFrame, message.toArray())
        println("已发送消息: ${message}")
    } catch (e: Exception) {
        println("发送消息失败: ${e.message}")
    }
}

// 接收 WebSocket 消息
func receiveWebSocketMessages(websocket: WebSocket): Unit {
    try {
        while (true) {
            let frame = websocket.read()

            match (frame.frameType) {
                case WebSocketFrameType.TextWebFrame =>
                    let message = String.fromUtf8(frame.payload)
                    println("收到消息: ${message}")

                    // 如果收到关闭消息,则退出循环
                    if (message.contains("关闭")) {
                        break
                    }
                case WebSocketFrameType.BinaryWebFrame => println("收到二进制消息,长度: ${frame.payload.size}")
                case WebSocketFrameType.PongWebFrame => println("收到 Pong 响应")
                case WebSocketFrameType.CloseWebFrame =>
                    println("收到关闭帧,关闭连接")
                    websocket.write(CloseWebFrame, frame.payload)
                    break
                case _ => println("收到其他类型帧: ${frame.frameType}")
            }
        }
    } catch (e: Exception) {
        println("接收消息异常: ${e.message}")
    }
}

// 主函数演示 WebSocket 客户端
public func webDemo(): Int64 {
    // 启动服务器
    let server = startWebSocketServer()
    let wsUrl = "ws://127.0.0.1:${server.port}/websocket"

    // 创建 WebSocket 客户端
    let websocket = createWebSocketClient(wsUrl)

    // 启动消息接收线程
    let receiveFuture = spawn {
        receiveWebSocketMessages(websocket)
    }

    // 等待连接建立
    sleep(Duration.millisecond * 200)

    // 发送消息
    sendWebSocketMessage(websocket, "Hello, WebSocket Server!")
    sleep(Duration.millisecond * 100)

    sendWebSocketMessage(websocket, "这是一条中文消息")
    sleep(Duration.millisecond * 100)

    // 发送关闭命令
    sendWebSocketMessage(websocket, "close")

    // 等待接收线程完成
    receiveFuture.get()

    // 关闭连接和服务器
    websocket.closeConn()
    server.close()

    return 0
}

WebSocket 编程关键点

  1. 握手过程

    • 客户端通过 HTTP 升级请求建立 WebSocket 连接
    • 服务端响应升级请求完成握手
    • 握手成功后开始 WebSocket 通信
  2. 消息处理

    • 使用 read() 方法读取帧
    • 使用 write() 方法发送帧
    • 根据帧类型进行不同处理
  3. 连接管理

    • 支持子协议协商
    • 可以设置自定义握手头
    • 正确处理连接关闭

网络编程最佳实践

错误处理

cangjie 复制代码
import std.net.{TcpSocket}
import stdx.net.http.{WebSocketException}

// 网络操作错误处理示例
func safeNetworkOperation(): Unit {
    try {
        // 创建 TCP 套接字
        let socket = TcpSocket("127.0.0.1", 8080)
        // 尝试连接
        socket.connect()
        println("连接成功")

        // 数据传输
        let data = "Hello Server".toArray()
        socket.write(data)

        // 关闭连接
        socket.close()
    } catch (e: WebSocketException) {
        // 处理网络相关异常
        println("网络异常: ${e.message}")
    } catch (e: TimeoutException) {
        // 处理超时异常
        println("操作超时: ${e.message}")
    } catch (e: Exception) {
        // 处理其他异常
        println("未知异常: ${e.message}")
    }
}

资源管理

cangjie 复制代码
import std.net.{TcpSocket}

// 使用 try-with-resources 管理网络资源
func manageNetworkResources(): Unit {
    // 自动管理套接字资源
    try (socket = TcpSocket("127.0.0.1", 8080)) {
        socket.connect()

        // 数据传输
        let data = "Hello".toArray()
        socket.write(data)

        // 不需要手动关闭,try-with-resources 会自动处理
    }

    // 套接字已自动关闭
    println("套接字资源已自动释放")
}

并发处理

cangjie 复制代码
import std.net.{TcpServerSocket, TcpSocket}

// 处理单个客户端连接
func handleClient(client: TcpSocket, clientId: Int): Unit {
    spawn {
        try {
            let buf = Array<Byte>(1024, repeat: 0)
            let count = client.read(buf)

            let message = String.fromUtf8(buf[..count])
            println("客户端 ${clientId} 发送: ${message}")

            // 发送响应
            let response = "Hello Client ${clientId}!".toArray()
            client.write(response)
        } catch (e: Exception) {
            println("处理客户端 ${clientId} 异常: ${e.message}")
        } finally {
            client.close()
            println("客户端 ${clientId} 连接已关闭")
        }
    }
}
// 并发处理多个客户端连接
func handleMultipleClients(serverPort: UInt16): Unit {
    try (serverSocket = TcpServerSocket(bindAt: serverPort)) {
        serverSocket.bind()
        println("服务器启动,端口: ${serverPort}")

        // 接受多个客户端连接
        var clientCount = 0
        while (clientCount < 5) { // 最多处理 5 个客户端
            try (client = serverSocket.accept()) {
                clientCount++
                println("客户端 ${clientCount} 已连接")
                // 为每个客户端创建独立线程
                handleClient(client, clientCount)
            }
        }
    }
}

参考资料

相关推荐
安卓开发者7 小时前
鸿蒙Next媒体展示组件实战:Video与动态布局全解析
华为·harmonyos·媒体
HarderCoder8 小时前
重学仓颉-14I/O 系统完全指南
harmonyos
森之鸟9 小时前
开发中使用——鸿蒙CoreSpeechKit语音识别
华为·语音识别·harmonyos
爱笑的眼睛1110 小时前
HarmonyOS 应用开发:基于API 12+的现代开发实践
华为·harmonyos
安卓开发者10 小时前
鸿蒙NEXT表单选择组件详解:Radio与Checkbox的使用指南
华为·harmonyos
爱笑的眼睛1110 小时前
HarmonyOS 应用开发深度实践:深入 Stage 模型与 ArkTS 声明式 UI
华为·harmonyos
爱笑的眼睛1110 小时前
HarmonyOS应用开发深度解析:基于Stage模型与ArkTS的现代实践
华为·harmonyos
HarderCoder10 小时前
重学仓颉-13并发编程完全指南
harmonyos
开发小能手嗨啊10 小时前
「鸿蒙系统的编程基础」——探索鸿蒙开发
harmonyos·鸿蒙·鸿蒙开发·开发教程·纯血鸿蒙·南向开发·北向开发