;;; ;;; network.lisp ;;; ;;; Network Interface ;;; ;;; Copyright (C) 2005, Alastair Bridgewater ;;; (in-package :clxs) ;;; FIXME: Far too sparse, needs documentation. ;;; FIXME: Disconnect callbacks? ;;; FIXME: Output path? ;;; FIXME: Closing a connection from this side? (defconstant +request-buffer-size+ 16384 "Minimum size (in octets) allowed for the upper limit on X request sizes") (defclass network-server () (connect-callback state host port socket)) (defclass network-client () (socket packet-size packet-data packet-received-callback)) (defun make-network-server (host port &key mp-style connect-callback) (declare (type (unsigned-byte 16) port)) (declare (type (or function null) connect-callback)) (unless (eq mp-style :serve-event) (error "MP-STYLE not :SERVE-EVENT (required)")) (let ((retval (make-instance 'network-server))) ;; FIXME: There's probably a better way to do this. (setf (slot-value retval 'connect-callback) connect-callback) (setf (slot-value retval 'state) :stopped) (setf (slot-value retval 'host) host) (setf (slot-value retval 'port) port) (setf (slot-value retval 'socket) nil) retval)) (defgeneric network-server-connect-callback (network-server)) (defgeneric (setf network-server-connect-callback) (callback network-server)) (defgeneric network-server-invoke-connect-callback (network-server)) (defmethod network-server-connect-callback ((network-server network-server)) (slot-value network-server 'connect-callback)) (defmethod (setf network-server-connect-callback) (callback (network-server network-server)) (when (and (null callback) (eq (slot-value network-server 'state) :running)) (error "Can't set NIL callback while server running")) (setf (slot-value network-server 'connect-callback) callback)) (defmethod network-server-invoke-connect-callback ((network-server network-server)) (funcall (network-server-connect-callback network-server) network-server)) (defgeneric network-server-start (network-server)) (defgeneric network-server-stop (network-server)) (defgeneric network-server-status (network-server)) (defmethod network-server-start ((network-server network-server)) (with-slots (host port socket state connect-callback) network-server (when (eq state :running) (error "server already running")) (unless connect-callback (error "no connect callback")) (setf state :running) (setf socket (make-instance 'sb-bsd-sockets:inet-socket :type :stream :protocol :tcp)) (setf (sb-bsd-sockets:sockopt-reuse-address socket) t) (sb-bsd-sockets:socket-bind socket host port) (sb-bsd-sockets:socket-listen socket 5) (sb-sys:add-fd-handler (sb-bsd-sockets:socket-file-descriptor socket) :input (lambda (x) (declare (ignorable x)) (network-server-invoke-connect-callback network-server)))) t) (defmethod network-server-stop ((network-server network-server)) (with-slots (socket state) network-server (unless (eq state :running) (error "server not running")) (setf state :stopped) (sb-sys:invalidate-descriptor (sb-bsd-sockets:socket-file-descriptor socket)) (sb-bsd-sockets:socket-close socket) (setf socket nil)) t) (defmethod network-server-status ((network-server network-server)) (slot-value network-server 'state)) (defgeneric network-server-accept (network-server)) (defmethod network-server-accept ((network-server network-server)) (let ((network-client (make-instance 'network-client)) (socket (sb-bsd-sockets:socket-accept (slot-value network-server 'socket)))) (setf (slot-value network-client 'socket) socket) (setf (slot-value network-client 'packet-data) (make-array +request-buffer-size+ :element-type '(unsigned-byte 8) :fill-pointer 0)) (setf (slot-value network-client 'packet-size) 0) (setf (slot-value network-client 'packet-received-callback) nil) (sb-sys:add-fd-handler (sb-bsd-sockets:socket-file-descriptor socket) :input (lambda (x) (declare (ignorable x)) (network-client-receive-data network-client))) network-client)) (defgeneric network-client-disconnect (network-client)) (defmethod network-client-disconnect ((network-client network-client)) (with-slots (socket) network-client (sb-sys:invalidate-descriptor (sb-bsd-sockets:socket-file-descriptor socket)) (sb-bsd-sockets:socket-close socket))) (defun network-client-receive-data (network-client) (declare (type network-client network-client)) ;; Okay, how's this for amusing? Looks like we need to use the posix ;; interfaces to do the actual reads, since standard CL doesn't ;; provide the semantics we want. ;; Core logic lifted from fd-stream.lisp in the SBCL sources. (with-slots (packet-data packet-size socket) network-client (multiple-value-bind (count errno) (sb-unix:unix-read (sb-bsd-sockets:socket-file-descriptor socket) (sb-sys:int-sap (+ (sb-sys:sap-int (sb-sys:vector-sap (sb-impl::%array-data-vector packet-data))) (fill-pointer packet-data))) (- packet-size (length packet-data))) (cond ((null count) (if (eql errno sb-unix:ewouldblock) (progn) ;; Will be sb-unix:econnreset if the remote disconnects. (error "couldn't read from ~S: ~S" network-client errno))) ((zerop count) ;; Two options: EOF or we just asked for zero bytes. ;; Asking for zero bytes is a bug, plain and simple. It could ;; happen, but it needs to be detected at the packet-received ;; callback level. We shall concern ourselves with EOF now. (network-client-disconnect network-client) ;; FIXME: A disconnect callback, either here or in the disconnect ;; handler above, is definately in order. ) (t (incf (fill-pointer packet-data) count)))) (when (= (fill-pointer packet-data) packet-size) (network-client-invoke-packet-received-callback network-client packet-data)))) (defgeneric network-client-packet-size (network-client)) (defgeneric (setf network-client-packet-size) (new-size network-client)) (defmethod network-client-packet-size ((network-client network-client)) (slot-value network-client 'packet-size)) (defmethod (setf network-client-packet-size) (new-size (network-client network-client)) (setf (slot-value network-client 'packet-size) new-size)) (defgeneric network-client-packet-received-callback (network-client)) (defgeneric (setf network-client-packet-received-callback) (callback network-client)) (defgeneric network-client-invoke-packet-received-callback (network-client packet-data)) (defmethod network-client-packet-received-callback ((network-client network-client)) (slot-value network-client 'packet-received-callback)) (defmethod (setf network-client-packet-received-callback) (callback (network-client network-client)) (setf (slot-value network-client 'packet-received-callback) callback)) (defmethod network-client-invoke-packet-received-callback ((network-client network-client) packet-data) (funcall (network-client-packet-received-callback network-client) network-client packet-data)) (defgeneric network-client-packet-complete (network-client)) (defmethod network-client-packet-complete ((network-client network-client)) (setf (fill-pointer (slot-value network-client 'packet-data)) 0) nil) ;; FIXME: Cheap hack interface, should be formalized. (defun send-packet (network-client packet) (sb-unix:unix-write (sb-bsd-sockets:socket-file-descriptor (slot-value network-client 'socket)) (sb-sys:vector-sap packet) 0 (length packet))) ;;; EOF