本章介绍使用usockets 库连创建 tcp/udp 服务。
TCP/IP
首先,先将库加载进来:
(ql:quickload "usocket")
首先是先创建个服务端,这里需要用到两个函数 usocket:socket-listen 和 usocket:socket-accept。
usocket:socket-listen 将绑定一个端口并对该端口进行监听,其返回值为 socket 对象。
返回的 socket 对象会等待客户端的连接。这就要用到 usocket:socket-accept 了,socket-accept 函数会进行阻塞,知道客户端连接,然后与客户端进行通信。
需要注意以下两个容易混淆的地方:
1 - 初次接触 socket-accept 时误以为其返回的是个流(stream object),但其实返回的是一个 socket 对象。socket 对象是有流这个属性的,但是需要进行指定。要查看如何指定的话可以使用 (describe connection)。
2 - 通信完成后,需要将客户端和服务端的 socket 都关闭掉,不然在使用时会出现问题。当然,服务端的 socket 是可以重复利用的。
了解了以上的混淆点后,现在,就开始写一个简单的通信的 demo 吧。
(defun create-server (port)(format t "Starting Server on 127.0.0.1 with ~a~%" port)(let* ((socket (usocket:socket-listen "127.0.0.1" port))(connection (usocket:socket-accept socket :element-type 'character)))(unwind-protect(progn(format (usocket:socket-stream connection) "Hello World~%")(force-output (usocket:socket-stream connection)))(progn(format t "Closing sockets~%")(usocket:socket-close connection)(usocket:socket-close socket)))))(defun create-client (port)(usocket:with-client-socket (socket stream "127.0.0.1" port :element-type 'character)(unwind-protect(progn(usocket:wait-for-input socket)(format t "Input is: ~a~%" (read-line stream)))(usocket:socket-close socket))))
这个需要怎么运行呢?打开两个 REPL,都先将这个文件加载进来,然后一个运行服务端 (create-server 12321),一个运行客户端 (create-client 12321)。你会在客户端的那个 REPL 中看到 “Hello World”。
UDP/IP
众所周知,UDP 是无连接状态的,因此也没有绑定并接受连接的概念。只需要使用 socket-connect,然后指定一些参数,用来在某个端口上接收数据。
下面是 UDP 创建是容易混淆的地方:
1 - 与 TCP 不同的是,socket-connect 之后不要接主机IP和端口号。如果直接在函数后面接 IP 和端口号,那就表示是通过该端口号去连接那个IP。所以,在函数后面添加两个 nil,然后再通过关键词 :local-host 和 :local-port 来指定服务端的IP和端口。更多相关的知识,参见 https://code.google.com/p/blackthorn-engine-3d/source/browse/src/examples/usocket/usocket.lisp 。
同样,因为 UDP 是无连接状态的,所以还需要 socket-receive 来与客户端进行通信。
(defun create-server (port buffer)(let* ((socket (usocket:socket-connect nil nil:protocol :datagram:element-type '(unsigned-byte 8):local-host "127.0.0.1":local-port port)))(unwind-protect(multiple-value-bind (buffer size client receive-port)(usocket:socket-receive socket buffer 8)(format t "~A~%" buffer)(usocket:socket-send socket (reverse buffer) size:port receive-port:host client))(usocket:socket-close socket))))(defun create-client (port buffer)(let ((socket (usocket:socket-connect "127.0.0.1" port:protocol :datagram:element-type '(unsigned-byte 8))))(unwind-protect(progn(format t "Sending data~%")(replace buffer #(1 2 3 4 5 6 7 8))(format t "Receiving data~%")(usocket:socket-send socket buffer 8)(usocket:socket-receive socket buffer 8)(format t "~A~%" buffer))(usocket:socket-close socket))))
与 TCP 一样,运行这个 demo 时需要打开两个 REPL,加载文件,服务端运行:
(create-server 12321 (make-array 8 :element-type '(unsigned-byte 8)))
客户端运行
(create-client 12321 (make-array 8 :element-type '(unsigned-byte 8)))
之后就可以在服务端看到 #(1 2 3 4 5 6 7 8) 在客户端看到 #(8 7 6 5 4 3 2 1) 了。
致谢
本章的教程来源于 https://gist.github.com/shortsightedsid/71cf34282dfae0dd2528
