All Manuals > LispWorks® User Guide and Reference Manual > 32 The COMM Package

start-up-server Function

Summary

Starts a TCP server.

Package

comm

Signature

start-up-server &key function announce service backlog address local-address local-port nodelay keepalive process-name wait create-stream ipv6 error reuseport => process, startup-condition

Arguments
function
A function name.
announce
An output stream, t, nil or a function.
service
An integer, a string or nil.
backlog
nil or a positive integer.
address
A synonym for local-address.
local-address
An integer, an ipv6-address object, a string or nil.
local-port
A synonym for service.
nodelay
A generalized boolean.
keepalive
A generalized boolean.
process-name
A symbol or string.
wait
A boolean.
create-stream
A boolean, default nil.
ipv6
The keyword :any, the keyword :both, nil or t.
error
A boolean.
reuseport
A boolean. Note: not supported on all platforms.
Values
process
A process, or nil.
startup-condition
A condition object, or nil.
Description

The function start-up-server starts a TCP server. Use open-tcp-stream to send messages from another client to the server.

function provides the name of the function that processes connections. When a connection is made function is called with the connected socket handle or a socket-stream (if create-stream is non-nil), at which point you can use it to communicate with the client. The server does not accept more connections until function returns, so normally it should create another thread to handle the connection. However, the operating system typically provides a small queue of partially accepted connections, which prevents connection failure for new clients until the server is ready to accept more connections. If function is not specified the built-in Lisp listener server is used. See the examples section below.

If create-stream is non-nil, function is called with a socket-stream object using the socket handle and :direction :io. If create-stream is nil (the default), function is called with the socket handle, and if you want a stream you need to use make-instance or create-ssl-socket-stream to create the socket-stream or if you want to use asynchronous I/O then create an async-io-state using create-async-io-state.

If announce is a stream or t (denoting *standard-output*), a message appears on the stream when the server is started.

If announce is a function it is called when the server is started. announce should take two arguments: socket and condition. socket is the socket used by the server: announce can therefore be used to record this socket. condition describes the error if there is one. announce can be called with socket nil and a condition only if error is nil. If the thread is killed, announce is called with socket nil and condition nil.

The default for announce is nil, meaning there is no message.

local-port defaults to service, which defaults to the string "lispworks".

local-port is interpreted as described for service in 25.3 Specifying the target for connecting and binding a socket.

backlog specifies the maximum number of pending connections for the socket in the operating system (see your operating system's documentation for the function listen). The default value of backlog is 5.

local-address defaults to address, which defaults to nil.

If local-address is nil then the server will receive connections to all IP addresses on the computer. If local-address is non-nil then the server only receives connections for the IP address that local-address specifies. The default value of local-address is nil.

local-address also determines which family is used when making the socket. AF_INET6 is used in these cases:

Otherwise AF_INET is used. When local-address is not supplied, AF_INET is used. To open a server with AF_INET6 listening to any address, either use the keyword argument ipv6 or pass the zero IPv6 address "::".

If keepalive is true, SO_KEEPALIVE is set on the socket. The default value of keepalive is nil.

If nodelay is true, TCP_NODELAY is set on the socket. The default value of nodelay is t.

process-name specifies the process name. The default is constructed from the service name in the following fashion:

(format nil "~S server" service)

wait argument controls whether start-up-server waits for the server to start or returns immediately. When wait is non-nil and an error was signaled, process is nil and the error is returned in startup-condition Otherwise just one value, the server process, is returned. The default for wait is nil.

ipv6 affects the resolution of local-address if it is a string or nil. When ipv6 is nil, it forces IPv4 addresses, and if ipv6 is t it forces IPv6 addresses. When ipv6 is :any the system tries either IPv4 or IPv6 and uses the first socket that it succeeds to bind. When ipv6 is :both the system uses IPv6 (like the value t) but allows connection requests in IPv4. Note that with t only IPv6 connections are allowed. The default value of ipv6 is :any.

error controls what happens if an error is signaled in the server thread. If error is nil then the thread is terminated. If error is non-nil then the debugger is entered. The default value for error is (not wait).

reuseport can be used only on operating systems that support SO_REUSEPORT, which are currently FreeBSD, macOS and Linux kernels newer than 3.9. If reuseport is true, then SO_REUSEPORT is set in the socket, which allows the same port to be reused for listening. That will allow you to use start-up-server (and accept-tcp-connections-creating-async-io-states) with the same port more than once at the same time, either in the same process or other processes (which must be run by the same user). The default value of reuseport is nil.

Notes
  1. Some versions of Microsoft Windows fail to detect the case where more than one server binds a given port, so an error will not be raised in this situation.
  2. When the server is not needed any more, terminate it by calling server-terminate with process (returned by start-up-server) as its argument, or call server-terminate from within function.
  3. When using using ipv6 t, it is possible to listen separately for IPv4 connections on the same service (by another service or using the Asynchronous I/O API). When using :both, it is not possible to listen separately to IPv4 on the same service.
  4. The server has a mechanism that checks for repeated unexplained failures associated with accepting sockets, and if that happens too often it closes the accepting socket and opens it again. When that happens, announce is called again with the same arguments. If service was nil, the port that the underlying system assigned to the first socket is used for opening the socket again. One situation that invokes that mechanism is putting an iOS device to sleep, which causes the accepting socket to become broken in a non-obvious way.
  5. start-up-server starts its own thread. Typically this is not an issue, because you call it a small number of times in each invocation of an image, so the overhead is not large. If you do call it many times in the same invocation, it may be better to use accept-tcp-connections-creating-async-io-states instead. On the other hand, connections to the server may happen very often, so making a thread for each one is a substantial overhead. You can avoid this either by using async-io-state objects rather than socket-stream, or have a pool of "worker threads" to do the actual communication.
  6. The socket handle that function receives is a native TCP socket handle, and can be used in the native TCP socket interface instead of using socket-stream or async-io-state.
  7. If socket handle that function receives is used in a socket-stream or async-io-state, it will be closed when the object is closed. Otherwise, you need to close it yourself by calling close-socket-handle when you have finished with it.
Compatibility note

In LispWorks 6.1 and previous versions, the argument ipv6 t means either accepting IPv4 or not, depending on the default of the operating system. In LispWorks 7.0 and later ipv6 t means never allow IPv4 connections.

Examples

The following example uses the built-in Lisp listener server:

(comm:start-up-server :service 10243)

It makes a Lisp listener server on port 10243 (check with local network managers that this port number is safe to use). When a client connects to this, Lisp calls read. The client should send a string using Common Lisp syntax followed by a newline. This string is used to name a new light-weight process that runs a Lisp listener. When this has been created, the server waits for more connections.

The next example illustrates the use of function. For each line of input read by the server it writes the line back with a message. The stream generates end of file if the other end closes the connection.

(defvar *talk-port* 10244)  ; a free TCP port number
(defun make-stream-and-talk (handle)
  (let ((stream (make-instance 'comm:socket-stream
                               :socket handle
                               :direction :io
                               :element-type
                                  'base-char)))
  (mp:process-run-function (format nil "talk ~D"
                                   handle)
                           '()
                           'talk-on-stream stream)))
(defun talk-on-stream (stream)
  (unwind-protect
      (loop for line = (read-line stream nil nil)
            while line
            do
            (format stream "You sent: '~A'~%" line)
            (force-output stream))
      (close stream)))
(comm:start-up-server :function 'make-stream-and-talk
                      :service *talk-port*)

This is a client which uses the talk server:

(defun talking-to-myself ()
  (with-open-stream 
      (talk (comm:open-tcp-stream "localhost" 
                                  *talk-port*))
    (dolist (monolog 
             '("Hello self."
               "Why don't you say something original?"
               "Talk to you later then.  Bye."))
      (write-line monolog talk)
      (force-output talk)
      (format t "I said: \"~A\"~%"
              monolog)
      (format t "Self replied: \"~A\"~%"
              (read-line talk nil nil)))))
(talking-to-myself)
=>
I said: "Hello self."
Self replied: "You sent: 'Hello self.'"
I said: "Why don't you say something original?"
Self replied: "You sent: 'Why don't you say something original?'"
I said: "Talk to you later then.  Bye."
Self replied: "You sent: 'Talk to you later then.  Bye.'"

This example illustrates a server which picks a free port and records the socket. The last form queries the socket for the port used.

(defvar *my-socket* nil)
 
(defun my-announce-function (socket condition)
  (if socket
      (setf *my-socket* socket)
    (my-log-error condition)))
 
(comm:start-up-server :service nil
                      :error nil
                      :announce 'my-announce-function)
 
(multiple-value-bind (address port)
    (comm:get-socket-address *my-socket*)
  port)

For an example of a server that use SSL connections, see:

(example-edit-file "ssl/ssl-server")
See also

open-tcp-stream
server-terminate
socket-stream
create-ssl-socket-stream
accept-tcp-connections-creating-async-io-states
async-io-state
create-async-io-state
close-socket-handle
25 TCP and UDP socket communication and SSL


LispWorks® User Guide and Reference Manual - 01 Dec 2021 19:30:26