本章介绍使用usockets 库连创建 tcp/udp 服务。

TCP/IP

首先,先将库加载进来:

  1. (ql:quickload "usocket")

首先是先创建个服务端,这里需要用到两个函数 usocket:socket-listenusocket: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 吧。

  1. (defun create-server (port)
  2. (format t "Starting Server on 127.0.0.1 with ~a~%" port)
  3. (let* ((socket (usocket:socket-listen "127.0.0.1" port))
  4. (connection (usocket:socket-accept socket :element-type 'character)))
  5. (unwind-protect
  6. (progn
  7. (format (usocket:socket-stream connection) "Hello World~%")
  8. (force-output (usocket:socket-stream connection)))
  9. (progn
  10. (format t "Closing sockets~%")
  11. (usocket:socket-close connection)
  12. (usocket:socket-close socket)))))
  13. (defun create-client (port)
  14. (usocket:with-client-socket (socket stream "127.0.0.1" port :element-type 'character)
  15. (unwind-protect
  16. (progn
  17. (usocket:wait-for-input socket)
  18. (format t "Input is: ~a~%" (read-line stream)))
  19. (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 来与客户端进行通信。

  1. (defun create-server (port buffer)
  2. (let* ((socket (usocket:socket-connect nil nil
  3. :protocol :datagram
  4. :element-type '(unsigned-byte 8)
  5. :local-host "127.0.0.1"
  6. :local-port port)))
  7. (unwind-protect
  8. (multiple-value-bind (buffer size client receive-port)
  9. (usocket:socket-receive socket buffer 8)
  10. (format t "~A~%" buffer)
  11. (usocket:socket-send socket (reverse buffer) size
  12. :port receive-port
  13. :host client))
  14. (usocket:socket-close socket))))
  15. (defun create-client (port buffer)
  16. (let ((socket (usocket:socket-connect "127.0.0.1" port
  17. :protocol :datagram
  18. :element-type '(unsigned-byte 8))))
  19. (unwind-protect
  20. (progn
  21. (format t "Sending data~%")
  22. (replace buffer #(1 2 3 4 5 6 7 8))
  23. (format t "Receiving data~%")
  24. (usocket:socket-send socket buffer 8)
  25. (usocket:socket-receive socket buffer 8)
  26. (format t "~A~%" buffer))
  27. (usocket:socket-close socket))))

与 TCP 一样,运行这个 demo 时需要打开两个 REPL,加载文件,服务端运行:

  1. (create-server 12321 (make-array 8 :element-type '(unsigned-byte 8)))

客户端运行

  1. (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