Hi, USOCKET users
Today I improved exist SOCKET-SERVER function in trunk, made it working for both TCP and UDP. This function can create a simple TCP or UDP server, just like similar facility found in some implementations. I'd like people try it and provide any suggestions on its interface.
First, please update your USOCKET trunk [1] to at least r542. The new SOCKET-SERVER has following definition:
(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 multi-threading) ...)
[required & optional arguments]
HOST: the local address you want to listen on, it could be something like "a.b.c.d" or #(a b c d) or integers. And if you want to listen on 0.0.0.0, you can use #(0 0 0 0), "0.0.0.0", or even NIL here.
PORT: a integer, means the port you want to listen. Notice: on UNIX platform, you cannot listen on port less than 1024 when you're not using "root" user.
FUNCTION: server handler function (will explain below)
ARGUMENTS: a list, which is used for providing extra arguments to above server handler function.
[keywords]
IN-NEW-THREAD: if T is provided, function SOCKET-SERVER will return immediately, and new server will be created in a new thread. Notice: you should have a CL implementation which support creating threads.
PROTOCOL: server type, could be :STREAM (for TCP, the default) or :DATAGRAM (for UDP)
TIMEOUT: UDP server loop interval, no need to be changed. MAX-BUFFER-SIZE: UDP buffer size, use a smaller value when needed. ELEMENT-TYPE: TCP handler's stream element type REUSE-ADDRESS: TCP socket flag
MULTI-THREADING: if T is provided, the created TCP server will create new threads on each connection, causing the TCP server handling multiple connections at the same time. by default (NIL), only one client is supported. Notice: UDP server could always serve as many client as possible, using only one thread.
[Usage of TCP server]
TCP server is based on streams, that is, you provide a handler function which use "stream" as required argument. For a simple TCP server which output a string on connection (and then close), the handler function can be defined like this:
(defun simple-output-handler (stream) (format stream "Hello, world!~%"))
With above function, the way to create a TCP server (on port 0.0.0.0:10000) is:
(usocket:socket-server "0.0.0.0" 10000 #'simple-output-handler nil :in-new-thread t)
Here I use (:in-new-thread t) to make sure the server is created in background thread, and since no :multi-threading keyword is supplied, this server could only serve one client per time. But consider each connection only exist for a very short time (just print a string and end), so this won't be any problem.
Now I can try to connect to it using UNIX "telnet" command:
binghe@binghe-pro:~$ telnet 127.0.0.1 10000 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Hello, world! Connection closed by foreign host.
See, the server is working!
Sometimes, maybe we are using a general handler function which have additional arguments for customization, for example:
(defun general-output-handler (stream string) (format stream "~A~%" string))
Now suppose I want to create two different TCP server which different output string. How can I do this? See following:
(usocket:socket-server "0.0.0.0" 10001 #'general-output-handler (list "Hello at port 10001") :in-new-thread t) (usocket:socket-server "0.0.0.0" 10002 #'general-output-handler (list "Hello at port 10002") :in-new-thread t)
This is how to use the optional ARGUMENTS argument! And let's test them:
binghe@binghe-pro:~$ telnet 127.0.0.1 10001 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Hello at port 10001 Connection closed by foreign host. binghe@binghe-pro:~$ telnet 127.0.0.1 10002 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Hello at port 10002 Connection closed by foreign host.
Isn't that perfect?
[Usage of UDP server]
UDP server is based on simple-array buffer. That is, what ever a client send to a UDP server, it will be stored into a vector which has type of (SIMPLE-ARRAY (UNSIGNED-BYTE 8) *). This vector is the input of your handler function, and you should return another vector as the data to send back to clients.
Let's create a simple UDP echo server. This means we don't need to modify anything, just return the buffer as is. So, function IDENTITY can be used directly:
(usocket:socket-server "0.0.0.0" 10000 #'identity nil :in-new-thread t :protocol :datagram)
We don't have a UNIX command to test it, so we use USOCKET:
? (defvar s (usocket:socket-connect "localhost" 10000 :protocol :datagram)) #<USOCKET:DATAGRAM-USOCKET #xC4C398E> ? (defvar buffer (coerce #(1 2 3) '(simple-array (unsigned-byte 8) *))) BUFFER ? (usocket:socket-send s buffer 3) 3 ? (usocket:socket-receive s nil 256) #(1 2 3) 3 2130706433 10000
Notice: in calling of SOCKET-RECEIVE, last argument (256) means we want to receive at most 256 bytes. And see its four return values:
#(1 2 3) means the return value by the UDP server, 3 means return buffer length. This is useful when you provide a bigger buffer but NIL to SOCKET-RECEIVE, 2130706433 means "127.0.0.1", this is the server (remote) address 10000 means server port.
For bigger example of UDP servers, see my CL-NET-SNMP project. A complicated SNMP server is working in the same way!
[Summary]
SOCKET-SERVER completely based on exist success of USOCET on providing a universal networking library. It's portable. I hope people could enjoy it.
Any question or suggestion are welcome.
Regards,
Chun Tian (binghe) USOCKET maintainer
[1] svn://common-lisp.net/project/usocket/svn/usocket/trunk