Ramarren / sykosomatic forked from sykopomp/sykosomatic

Sykopomp's Somewhat Masterful Text in Console (MUD engine)

This URL has Read+Write access

sykosomatic / server.lisp
100644 164 lines (145 sloc) 5.869 kb
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
156
157
158
159
160
161
162
163
164
;; Copyright 2008 Josh Marchan
 
;; This file is part of sykosomatic
 
;; sykosomatic is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
 
;; sykosomatic is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
 
;; You should have received a copy of the GNU General Public License
;; along with sykosomatic. If not, see <http://www.gnu.org/licenses/>.
 
;; server.lisp
;;
;; Contains <server> class, which holds the server socket, client list, and connection thread.
;; Also has the start-server and stop-server functions, and supporting functions for those.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(in-package #:sykosomatic)
 
;;;
;;; Server class
;;;
 
(defclass <server> ()
  ((stop-server-p
    :accessor stop-server-p
    :initform nil
    :documentation "Is the server stopping?")
   (socket
    :accessor socket
    :initarg :socket
    :initform nil
    :documentation "Contains the server's usocket-listener.")
   (clients
    :accessor clients
    :initform nil
    :documentation "List of connected clients.")
   (client-list-lock
    :accessor client-list-lock
    :initarg :client-list-lock
    :initform (bordeaux-threads:make-recursive-lock "client-list-lock")
    :documentation "Locks access to the clients list.")
   (client-cleanup-queue
    :accessor client-cleanup-queue
    :initform (make-empty-queue)
    :documentation "A queue of client threads that need to be killed.")
   (connection-thread
    :accessor connection-thread
    :initarg :connection-thread
    :documentation "Thread that runs the function to connect new clients.")
   (clients-thread
    :accessor clients-thread
    :documentation "Thread that runs the client i/o stuff.")
   (ticks-per-second
    :initform 30
    :accessor ticks-per-second
    :documentation "Ticks define how many times per second client i/o is processed.")
   (last-tick-time
    :initform (get-internal-real-time)
    :accessor last-tick-time))
  (:documentation "Server class that contains the listener socket, the current list of clients,
the i/o processing ticks, and related slots."))
 
 
;;;
;;; Init/Destruct
;;;
 
(defvar *default-server-address* "localhost")
(defvar *default-server-port* 4000)
(defvar *server* nil)
 
;;; Clients thread
 
(defun make-clients-thread (server)
  "Returns a lambda that can be popped into a thread. The function loops through all the clients
connected to *server* and handles their input once per tick. Stops with stop-server-p in <server>."
  (lambda ()
    (loop
       until (stop-server-p server)
       do
(bordeaux-threads:with-recursive-lock-held ((client-list-lock server))
(loop for client in (clients server)
for stream = (usocket:socket-stream (socket client))
do
(handler-case
(progn
(when (and (open-stream-p stream)
(listen stream))
(update-activity client)
(maybe-read-line-from-client client))
(funcall (client-step client)))
(client-disconnected-error ()
(progn
(log-message :CLIENT "Client disconnected: ~a" (ip client))
(remove-client client))))))
(let ((next-tick (+ (last-tick-time server)
(/ internal-time-units-per-second (ticks-per-second server))))
(now (get-internal-real-time)))
(setf (last-tick-time server) now)
(when (> next-tick now)
(sleep (/ (- next-tick now)
internal-time-units-per-second)))))))
 
;;; Start
 
(defun start-server (&key (address *default-server-address*) (port *default-server-port*))
  "Takes care of starting up the server."
  (log-message :SERVER "Starting server...")
  (let* ((socket (usocket:socket-listen address port :reuse-address t :element-type '(unsigned-byte 8)))
(server (make-instance '<server>
:socket socket)))
    (setf *server* server)
    (log-message :SERVER "Creating server connection thread.")
    (setf (connection-thread *server*)
(bordeaux-threads:make-thread
(lambda () (loop
(handler-case (connect-new-client)
(sb-bsd-sockets:not-connected-error ()
(log-message :HAX "Got a not-connected-error.")))))
:name "sykosomatic-server-connection-thread"))
    (setf (clients-thread *server*)
(bordeaux-threads:make-thread (make-clients-thread *server*)))
    (log-message :SERVER "Server started successfully.")))
 
;;; Stop
 
(defun stop-server ()
  "Stops the server, disconnecting everything."
  (if (not *server*)
      (log-message :SERVER "Tried to stop server, but no server running.")
      (progn
(setf (stop-server-p *server*) t)
(log-message :SERVER "Stopping server...")
(remove-all-clients)
(destroy-connection-thread)
(usocket:socket-close (socket *server*))
(setf *server* nil)
(log-message :SERVER "Server stopped."))))
 
(defun remove-all-clients ()
  "Disconnects and removes all clients from the current server."
  (if (clients *server*)
      (progn
(log-message :SERVER "Disposing of clients.")
(mapcar #'remove-client (clients *server*))
(log-message :SERVER "Clients removed."))
      (log-message :SERVER "No clients to remove. Skipping client removal.")))
 
(defun destroy-connection-thread ()
  "Destroys the server's connection thread, if it's running."
  (if (and (connection-thread *server*)
(bordeaux-threads:thread-alive-p (connection-thread *server*)))
      (progn
(bordeaux-threads:destroy-thread (connection-thread *server*))
(log-message :SERVER "Connection thread successfully shut down."))
      (log-message :SERVER "No thread running, skipping thread destruction...")))