osc/devices/server.lisp

225 lines
8.7 KiB
Common Lisp
Raw Permalink Normal View History

2015-08-25 19:59:32 +00:00
(cl:in-package #:osc)
(defun make-osc-server (&key (protocol :udp) debug-mode
(buffer-size *default-osc-buffer-size*)
cleanup-fun)
(ecase protocol
(:udp (make-instance 'osc-server-udp
:debug-mode debug-mode
:cleanup-fun cleanup-fun
:buffer-size buffer-size
:socket-buffer (make-socket-buffer buffer-size)))
(:tcp (make-instance 'osc-server-tcp
:debug-mode debug-mode
:cleanup-fun cleanup-fun
:buffer-size buffer-size))))
(defgeneric boot (osc-server port))
(defmethod boot :around ((server osc-server) port)
(if (device-active-p server)
(warn "~%Server ~A already running" (machine-instance)))
(set-socket (make-socket (protocol server)) server)
(socket-bind (socket server) #(0 0 0 0) port)
(call-next-method)
(format t "~%Server ~A listening on port ~A~%"
(machine-instance) port))
(defmethod boot ((server osc-server-udp) port)
(declare (ignore port))
"UDP server sockets are used for receiving and unconnected sending."
(set-listening-thread (make-listening-thread server) server))
(defmethod boot ((server osc-server-tcp) port)
(declare (ignore port))
(set-listening-thread
(sb-thread:make-thread
(lambda ()
(unwind-protect
(progn (socket-listen (socket server) 10)
(loop for socket = (socket-accept (socket server))
for endpoint = (make-osc-client-endpoint-tcp
socket
(debug-mode server)
(buffer-size server)
(address-tree server)
(clients server)
(make-unregister-self-fun server))
do (register-tcp-client server endpoint)))
(osc-device-cleanup server)))
:name (format nil "osc-server-tcp: ~A" (name server)))
server)
server)
(defmethod osc-device-cleanup ((device osc-server-udp))
(loop for client-name being the hash-key in (clients device)
using (hash-value addr+port)
do (notify-quit device client-name)
do (unregister-udp-client device
(first addr+port)
(second addr+port)))
(call-next-method))
(defmethod osc-device-cleanup ((device osc-server-tcp))
(loop for client being the hash-value in (clients device)
do (quit client))
(call-next-method))
(defun make-clients-hash ()
(make-hash-table :test 'equal))
;;;=====================================================================
;;; UDP server functions
;;;=====================================================================
(defmethod initialize-instance :after ((server osc-server-udp) &key)
(make-server-responders server))
(defgeneric make-server-responders (server))
(defmethod make-server-responders ((server osc-server-udp))
(add-osc-responder server "/cl-osc/register"
(cmd args device address port timetag bundle)
(let ((listening-port (car args))) ; Optional port for sending
; return messages.
(register-udp-client device address
(if listening-port listening-port port))))
(add-osc-responder server "/cl-osc/quit"
(cmd args device address port timetag bundle)
(unregister-udp-client device address port)))
(defun register-udp-client (server addr port)
(let ((client-name (make-addr+port-string addr port)))
(format t "Client registered: ~A~%" client-name)
(setf (gethash client-name (clients server))
(list addr port))
(post-register-hook server client-name)))
(defun unregister-udp-client (server addr port)
(let ((client-name (make-addr+port-string addr port)))
(format t "Client quit: ~A~%" client-name)
(remhash client-name (clients server))))
(defgeneric post-register-hook (server client-name)
(:method ((server osc-server-udp) client-name)
(format t "Post-register hook for client: ~A~%" client-name)
(notify-registered server client-name)))
(defun notify-registered (server client-name)
(send-msg-to-client server client-name "/cl-osc/server/registered"))
(defun notify-quit (server client-name)
(send-msg-to-client server client-name "/cl-osc/server/quit"))
;;;=====================================================================
;;; TCP server functions
;;;=====================================================================
(defun register-tcp-client (server transmitter)
(setf (gethash (make-peername-string transmitter)
(clients server))
transmitter))
(defun unregister-tcp-client (server transmitter)
(remhash (make-peername-string transmitter)
(clients server)))
(defun make-unregister-self-fun (server)
#'(lambda (client)
(unregister-tcp-client server client)))
(defun get-tcp-client (server socket-peername)
(gethash socket-peername (clients server)))
(defgeneric print-clients (server))
(defmethod print-clients ((server osc-server-udp))
(loop for addr+port being the hash-value in (clients server)
for i from 1
do (format t "~A. Connected to: ~A~%" i (make-addr+port-string
(first addr+port)
(second addr+port)))))
(defmethod print-clients ((server osc-server-tcp))
(loop for endpoint being the hash-value in (clients server)
for i from 1
do (format t "~A. Connected to: ~A~%" i (make-addr+port-string
(peer-address endpoint)
(peer-port endpoint)))))
;;;=====================================================================
;;; Server sending functions
;;;=====================================================================
;; Send to a client
(defgeneric send-to-client (server client-name data)
(:method :around ((server osc-server) client-name data)
(let ((client (gethash client-name (clients server))))
(if client
(call-next-method server client data)
(warn "No client called ~A~%" client-name)))))
(defmethod send-to-client ((server osc-server-udp) client-name data)
(send-to server (first client-name) (second client-name) data))
(defmethod send-to-client ((server osc-server-tcp) client data)
(send client data))
(defgeneric send-msg-to-client (server client-name command &rest args)
(:method ((server osc-server) client-name command &rest args)
(let ((message (make-message command args)))
(send-to-client server client-name message))))
(defgeneric send-bundle-to-client (server client-name timetag command
&rest args)
(:method ((server osc-server) client-name timetag command &rest
args)
(let ((bundle (bundle timetag
(make-message command args))))
(send-to-client server client-name bundle))))
;; Send all
(defgeneric send-all (server data))
(defmethod send-all ((server osc-server-udp) data)
(loop for addr+port being the hash-value in (clients server)
do (send-to server (first addr+port) (second addr+port) data)))
(defmethod send-all ((server osc-server-tcp) data)
(loop for endpoint being the hash-value in (clients server)
do (send endpoint data)))
(defmethod send-all ((client-endpoint osc-client-endpoint) data)
(loop for endpoint being the hash-value in (clients client-endpoint)
;; FIXME: Don't not reply to the sender in this case so that the
;; behaviour of send-all is uniform for both UDP and TCP. But
;; could be useful to have a means of broadcasting messages to
;; all clients of a server except the client that generated the
;; message.
;;
;; unless (eq endpoint client-endpoint) ; don't send to sender
do (send endpoint data)))
(defgeneric send-msg-all (server command &rest args)
(:method ((server osc-server) command &rest args)
(let ((message (make-message command args)))
(send-all server message)))
(:method ((client-endpoint osc-client-endpoint) command &rest args)
(let ((message (make-message command args)))
(send-all client-endpoint message))))
(defgeneric send-bundle-all (server timetag command &rest args)
(:method ((server osc-server) timetag command &rest args)
(let ((bundle (bundle timetag
(make-message command args))))
(send-all server bundle)))
(:method ((client-endpoint osc-client-endpoint) timetag command
&rest args)
(let ((bundle (bundle timetag
(make-message command args))))
(send-all client-endpoint bundle))))