概述
网络编程是现代软件开发中不可或缺的一部分,它使得不同设备之间能够通过网络进行数据交换。仓颉语言为开发者提供了强大而完整的网络编程功能,涵盖了从传输层到应用层的各种协议支持。
仓颉语言的网络编程具有以下特点:
- 阻塞式设计:虽然网络操作是阻塞的,但被阻塞的是仓颉线程,系统线程会被让渡出去,不会真正阻塞系统资源
- 分层抽象:提供了清晰的传输层和应用层抽象
- 协议支持:支持 TCP、UDP、HTTP/1.0/1.1/2.0、WebSocket 等主流协议
- 类型安全:利用仓颉语言的类型系统确保网络操作的安全性
网络编程架构
传输层抽象
仓颉语言将传输层协议抽象为两个主要类型:
- DatagramSocket(数据报套接字):用于不可靠传输,如 UDP
- 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 编程关键点
-
服务端流程:
- 创建
TcpServerSocket
实例 - 调用
bind()
绑定地址和端口 - 调用
accept()
等待客户端连接 - 通过返回的客户端套接字进行数据读写
- 创建
-
客户端流程:
- 创建
TcpSocket
实例 - 调用
connect()
连接到服务器 - 直接进行数据读写操作
- 创建
-
数据传输:
- 使用
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 编程关键点
-
无连接特性:
- UDP 套接字无需建立连接
- 每个数据包都是独立的
- 可以同时与多个端点通信
-
地址管理:
- 使用
receiveFrom()
接收数据时返回发送方地址 - 使用
sendTo()
发送数据时需要指定目标地址
- 使用
-
超时设置:
- 可以设置
sendTimeout
和receiveTimeout
- 超时后操作会抛出异常
- 可以设置
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 编程关键点
-
服务器构建:
- 使用
ServerBuilder
构建服务器 - 通过
register()
方法注册路由处理器 - 调用
serve()
启动服务器
- 使用
-
路由处理:
- 每个路由对应一个处理函数
- 处理函数接收
HttpContext
参数 - 通过
responseBuilder
构建响应
-
客户端操作:
- 使用
ClientBuilder
构建客户端 - 支持 GET、POST 等 HTTP 方法
- 可以设置超时时间和请求头
- 使用
WebSocket 编程详解
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,常用于实时通信应用。
WebSocket 协议特点
- 持久连接:一次握手后建立长连接
- 双向通信:服务端可以主动向客户端发送数据
- 帧传输:数据以帧为单位传输,支持分段传输
WebSocket 帧类型
-
控制帧:
CloseWebFrame
:关闭连接PingWebFrame
:心跳检测PongWebFrame
:心跳响应
-
数据帧:
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 编程关键点
-
握手过程:
- 客户端通过 HTTP 升级请求建立 WebSocket 连接
- 服务端响应升级请求完成握手
- 握手成功后开始 WebSocket 通信
-
消息处理:
- 使用
read()
方法读取帧 - 使用
write()
方法发送帧 - 根据帧类型进行不同处理
- 使用
-
连接管理:
- 支持子协议协商
- 可以设置自定义握手头
- 正确处理连接关闭
网络编程最佳实践
错误处理
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)
}
}
}
}