forked from usocket/usocket
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.lisp
155 lines (144 loc) · 7.29 KB
/
server.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
;;;; See LICENSE for licensing information.
(in-package :usocket)
(defvar *server*)
(defun socket-server (host port function &optional arguments
&key in-new-thread (protocol :stream)
;; for udp
(timeout 1) (max-buffer-size +max-datagram-packet-size+)
;; for tcp
element-type (reuse-address t) multi-threading
name)
"Create a simple TCP or UDP socket server.
`host' and `port' name the local interface and port the server will listen on.
`function' is the handler function. It must take at least one argument: a stream
for a TCP server, or a buffer for a UDP server. For the UDP server, the handler
must also return a buffer.
`arguments' is a list of additional arguments to pass to the handler function,
after the first.
If `in-new-thread' is true, the server will be spawned on a new thread and this
function will return immediately. Otherwise, this function will run
indefinitely. `name' specifies the name of the thread.
`protocol' can be :stream for a TCP server (the default) or :datagram for a UDP server.
For UDP servers:
`timeout' specifies a read timeout, 1 second by default.
`max-buffer-size' specifies the maximum size a UDP packet can take up, `+max-datagram-packet-size+' by default.
For TCP servers:
`element-type' specifies the stream's element type.
If `reuse-address' is true, wildcard hosts and more specific hosts can share a port.
If `multi-threading' is true, each connection will be launched in a new thread,
allowing the server to handle multiple connections in parallel."
(let* ((real-host (or host *wildcard-host*))
(socket (ecase protocol
(:stream
(apply #'socket-listen
`(,real-host ,port
,@(when element-type `(:element-type ,element-type))
,@(when reuse-address `(:reuse-address ,reuse-address)))))
(:datagram
(socket-connect nil nil :protocol :datagram
:local-host real-host
:local-port port)))))
(labels ((real-call ()
(ecase protocol
(:stream
(tcp-event-loop socket function arguments
:element-type element-type
:multi-threading multi-threading))
(:datagram
(udp-event-loop socket function arguments
:timeout timeout
:max-buffer-size max-buffer-size)))))
(if in-new-thread
(values (bt:make-thread #'real-call :name (or name "USOCKET Server")) socket)
(progn
(setq *server* socket)
(real-call))))))
(defvar *remote-host*)
(eval-when (:compile-toplevel :load-toplevel :execute)
(setf (documentation '*remote-host* 'variable)
"The remote host of a TCP or UDP event. This variable is dynamically bound~
in the context of a `socket-server', specifically the handler function."))
(defvar *remote-port*)
(eval-when (:compile-toplevel :load-toplevel :execute)
(setf (documentation '*remote-port* 'variable)
"The remote port of a TCP or UDP event. This variable is dynamically bound~
in the context of a `socket-server', specifically the handler function."))
(defun default-udp-handler (buffer) ; echo
"Example handler for a UDP socket-server. Returns datagrams to sender."
(declare (type (simple-array (unsigned-byte 8) *) buffer))
buffer)
(defun udp-event-loop (socket function &optional arguments
&key timeout max-buffer-size)
(let ((buffer (make-array max-buffer-size :element-type '(unsigned-byte 8) :initial-element 0))
(sockets (list socket)))
(unwind-protect
(loop do
(multiple-value-bind (return-sockets real-time)
(wait-for-input sockets :timeout timeout)
(declare (ignore return-sockets))
(when real-time
(multiple-value-bind (recv n *remote-host* *remote-port*)
(socket-receive socket buffer max-buffer-size)
(declare (ignore recv))
(if (<= 0 n)
(progn
(let ((reply
(apply function (subseq buffer 0 n) arguments)))
(when reply
(replace buffer reply)
(let ((n (socket-send socket buffer (length reply)
:host *remote-host*
:port *remote-port*)))
(when (minusp n)
(error "send error: ~A~%" n))))))
(error "receive error: ~A" n))))
#+scl (when thread:*quitting-lisp* (return))
#+(and cmu mp) (mp:process-yield)))
(socket-close socket)
(values))))
(defun default-tcp-handler (stream) ; null
"Example handler for a TCP socket-server. Sends client 'Hello, world!' then closes the stream."
(declare (type stream stream))
(format stream "Hello world!~%"))
(defun echo-tcp-handler (stream)
"Example handler for a TCP socket-server, being an echo server."
(loop
(when (listen stream)
(let ((line (read-line stream nil)))
(write-line line stream)
(force-output stream)))))
(defun tcp-event-loop (socket function &optional arguments
&key element-type multi-threading)
(let ((real-function #'(lambda (client-socket &rest arguments)
(unwind-protect
(multiple-value-bind (*remote-host* *remote-port*) (get-peer-name client-socket)
(apply function (socket-stream client-socket) arguments))
(close (socket-stream client-socket))
(socket-close client-socket)
nil))))
(unwind-protect
(loop do
(block continue
(let* ((client-socket (apply #'socket-accept
socket
(when element-type
(list :element-type element-type))))
(client-stream (socket-stream client-socket)))
(if multi-threading
(bt:make-thread
(lambda ()
(handler-case (apply real-function client-socket arguments)
#+sbcl
(sb-bsd-sockets:invalid-argument-error ())))
:name "USOCKET Client")
(unwind-protect
(handler-case (apply real-function client-socket arguments)
#+sbcl
(sb-bsd-sockets:invalid-argument-error ()
(return-from continue)))
(close client-stream)
(socket-close client-socket)))
#+scl (when thread:*quitting-lisp* (return))
#+(and cmu mp) (mp:process-yield))))
(socket-close socket)
(values))))