本章介绍使用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