赞
踩
使用新版本 (2024-07-19 16:10发布的)
网络通信是两个设备通过计算机网络进行数据交换的过程
。通过编写软件达成网络通信的行为即为网络编程
。
仓颉为开发者提供了基础的网络编程功能,在仓颉标准库中,用户可使用 std 模块
下的 socket 包
来实现传输层网络通信。
在传输层协议中,分为不可靠传输
和可靠传输
两种,仓颉将其抽象为 DatagramSocket
和 StreamSocket
。其中不可靠传输协议常见的是 UDP
,可靠传输协议常见的是 TCP
,仓颉分别将其抽象为 UdpSocket
和 TcpSocket
。另外,仓颉也实现了对传输层 Unix Domain 协议的支持
,并支持其通过可靠和不可靠传输两种方式进行通信。
而在应用层协议中,较为常见的是 HTTP 协议,常用于开发 Web 应用程序等。当前 HTTP 协议已有多个版本,仓颉目前支持 HTTP/1.1
、HTTP/2.0
等。
另外,WebSocket
作为一种提升 Web 服务端与客户端间的通信效率的应用层协议,仓颉将其抽象为 WebSocket 对象
,并支持从 HTTP 协议升级至 WebSocket 协议
。
需要注意的是,仓颉的网络编程是阻塞式的
。但被阻塞的是仓颉线程
,阻塞中的仓颉线程会将系统线程让渡出去,因此并不会真正阻塞一个系统线程。
仓颉的 Socket 编程指的是基于传输层协议实现网络传输数据包的功能
。
在可靠传输场景下
,仓颉分别启动客户端套接字和服务端套接字。客户端
套接字必须指定将要连接的远端地
址,可选择性地绑定本端地址
,在连接成功后,才可以收发报文。而服务端
套接字必须绑定本端地址
,在绑定成功后,才可以收发报文。
在不可靠传输场景下
,套接字无需区分客户端和服务端
,仓颉分别启动两个套接字
进行数据传输。套接字必须绑定本端地址
,绑定成功后,才可以收发报文。并且,套接字也可选择性地指定远端连接地址
,指定后将仅接受指定的远端地址的报文,同时在 send 时无需指定远端地址,报文将发送至成功连接的地址。
Tcp 作为一种常见的可靠传输协议,以 Tcp 类型套接字举例,仓颉在可靠传输场景下的可参考的编程模型如下:
Tcp 服务端和客户端程序示例如下:
import std.socket.* import std.time.* import std.sync.* let SERVER_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])) } return 0 }
Udp 作为一种常见的不可靠传输协议,以 Udp 类型套接字举例,仓颉在不可靠传输场景下的可参考的编程模型如下:
Udp 收发报文程序示例如下:
import std.socket.* import std.time.* import std.sync.* let SERVER_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.1 println("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 * 2 udpSocket.bind() udpSocket.sendTo( SocketAddress("127.0.0.1", SERVER_PORT), Array<Byte>([1, 2, 3]) ) } future.get() return 0 }
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. 发送 request let 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() }
在网络编程中,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}") // fool1 println(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()) { throw Exception("invalid frame") } data.appendAll(frame.payload) if (frame.fin) { break } case CloseWebFrame => websocket.write(CloseWebFrame, frame.payload) break case PingWebFrame => websocket.writePongFrame(frame.payload) case _ => () } frame = websocket.read() } println("data size: ${data.size}") // 4097 println("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}") // CloseWebFrame println("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 消息收发 // 收 hello let 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) break case 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, // 收发 CloseFrame let websocketFrame = websocketServer.read() println("close frame type: ${websocketFrame.frameType}") // CloseWebFrame println("close frame payload: ${websocketFrame.payload}") // 3, 232 websocketServer.write(CloseWebFrame, websocketFrame.payload) // 关闭底层连接 websocketServer.closeConn() }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。