cl-async

TCP

This section details sending and receving data over TCP, along with how to deal with cl-async sockets. It also goes over conditions/events one might run into while using the TCP system.

tcp-connect

(defun tcp-connect (host port read-cb event-cb
                    &key data stream
                         connect-cb write-cb
                         (read-timeout -1) (write-timeout -1)
                  (dont-drain-read-buffer nil dont-drain-read-buffer-supplied-p))
  => socket/stream

Open an asynchronous TCP connection to a host (IP or hostname) and port. You can specify data to be sent once the connection is established via the :data keyword. All incoming data will be sent to the read-cb, and any events will be sent to the event-cb.

tcp-connect returns a socket class, which wraps the C socket implementation and also allows storing arbitrary data with the socket.

tcp-connect can also return a stream of type async-io-stream when the keyword argument :stream is T. This allows normal stream operations on top of a non-blocking socket.

Note that tcp-connect always opens a new connection. If you want to send data on and existing connection (and also be able to set new read/write/event callbacks on it), check out write-socket-data, or in the case of a stream, you can use write-sequence to send new data on the stream.

Note that the host can be an IP address or a hostname. The hostname will be looked up asynchronously via cl-async’s DNS implementation.

;; example:
(tcp-connect "www.google.com" 80
             (lambda (socket data)
               (when (pretend-http-package:process-http-stream data) 
                 (close-socket socket)))  ; close the socket if done processing
             #'my-app-error-handler
             :data (format nil "GET /~c~c" #\return #\newline))

See the tcp-stream page for some examples on stream usage.

read-cb definition (default)
(lambda (socket byte-array) ...)

read-cb definition (when tcp-connect’s :stream is t)
(lambda (socket stream) ...)

Note that in this case, stream replaces the data byte array’s position. Also, when calling :stream t in tcp-connect, the read buffer for the socket is not drained and is only done so by reading from the stream.

stream is always the same object returned from tcp-connect with :stream t. It wraps the socket object.

connect-cb definition
(lambda (socket) ...)

The connect-cb will be fired when the connection from tcp-connect has been established. Since sending data over the socket is somewhat transparent (either via :data or write-socket-data), you don’t really have to know when a socket is ready to be written to. In some instances though, it may be useful to know when the connection has been established, which is why :connect-cb is exposed.

write-cb definition
(lambda (socket) ...)

The write-cb will be called after data written to the socket’s buffer is flushed out to the socket. If you want to send a command to a server and immediately disconnect once you know the data was sent, you could close the connection in your write-cb.

tcp-send (deprecated)

This function is a deprecated version of tcp-connect. Use tcp-connect instead, as tcp-send may be removed in later versions.

init-tcp-socket

(defun init-tcp-socket (read-cb event-cb
                        &key data stream
                             connect-cb write-cb
                             (read-timeout -1) (write-timeout -1)
                             (dont-drain-read-buffer nil dont-drain-read-buffer-supplied-p))
  => socket/stream

This function is much like tcp-connect but with a few exceptions:

  1. It only initializes a socket object, it doesn’t connect it.
  2. It doesn’t accept host/port arguments.

In other words, init-tcp-socket is tcp-connect’s lower-level brother. Once initialized, an unconnected socket can be connected using connect-tcp-socket.

connect-tcp-socket

(defun connect-tcp-socket (socket/stream host port &key event-cb))
  => socket/stream

This is mean to be used with an unconnected socket created by init-tcp-socket. If you want to initialize and connect a socket in one function call, use tcp-connect, however if you want more control over when a socket is connected, you can use init-tcp-socket along with connect-tcp-socket.

tcp-server (class)

This is an opaque class which is returned by the function tcp-server to allow closing the server and allowing for future expansion of the server’s abilities. It has no public accessors.

tcp-server

(defun tcp-server (bind-address port read-cb event-cb
                   &key connect-cb (backlog -1) stream))
  => tcp-server

Bind an asynchronous listener to the given bind address/port and start accepting connections on it. It takes read and event callbacks (like tcp-connect). If nil is passed into the bind address, it effectively binds the listener to “0.0.0.0” (listens from any address). A connection backlog can be specified when creating the server via :backlog, which defaults to -1. A connect-cb can be passed in as a keyword arg, which sets a callback to be called whenever a new connection comes in.

tcp-server accepts a :stream arg, which when T will call its read-cb with an async-io-stream instead of a byte array.

This function returns a tcp-server object, which allows you to close the server via close-tcp-server.

If binding to an address/port fails, tcp-server will throw a tcp-server-bind-error exception. Generally this only happens if the port is already in use or the port is “privileged.”

;; example
(tcp-server "127.0.0.1" 8080
            (lambda (socket data)
              (format t "data: ~a~%" data)
              (write-socket-data socket "i noticed you have brathes. i have brathes too. uhhhhuhuhuh."
                                 :write-cb (lambda (socket)
                                             (close-socket socket))))
            nil)  ;; use *default-event-handler* as the event handler for this operation

read-cb definition (default)
(lambda (socket byte-array) ...)

read-cb definition (when tcp-server is called with :stream t)
(lambda (socket stream) ...)

Note that in this case, stream replaces the data byte array’s position. Also, when calling :stream t in tcp-stream, the read buffer for the connecting socket is not drained and is only done so by reading from the stream.

connect-cb definition
(lambda (socket) ...)

Called when a client connects (but not necessarily when it has sent data). If present, is always called before the read-cb.

close-tcp-server

(defun close-tcp-server (tcp-server))
  => nil

Takes a tcp-server object, created by tcp-server and closes the server it wraps gracefully. This can be useful if you want to shut down a TCP server without forcibly closing all its connections (via exit-event-loop, for instance).

If the given server is already closed, this function returns without doing anything.

socket

This class is a wrapper around the libuv socket class. It is passed to tcp callback functions, and allows you to perform certain actions on the socket (such as closing it, setting read/write timeouts, writing data to it, etc).

It also exposes an accessor, socket-data, which allows you to store arbitrary, app-specific data in the socket.

socket-c

This accessor lets you access the underlying libuv stream object for the socket. While this is not immediately useful for any cl-async related purpose (and manipulating it outside of cl-async may make your worst nightmares come true if you aren’t careful), it can be very useful to do your own stream operations through the cl-libuv bindings.

socket-data

This accessor allows you to set arbitrary data into the socket class, which can be useful if your app needs to match specific data to a socket (for instance if you are proxying, you could use socket-data to store a reference to the outgoing socket inside the incoming socket).

write-socket-data

(defun write-socket-data (socket data &key read-cb write-cb event-cb))
  => nil

Write data to an existing socket (such as one passed into a tcp-connect read-cb). Data can be a byte array or string (converted to a byte array via babel). Supports resetting the callbacks on the given socket. The write-cb is useful if you want to close the connection after sending data on the socket but want to make sure the data sent before closing.

Note that if you call this using a socket that has been closed already, it will throw a socket-closed condition.

;; examples
(write-socket-data socket "thanks for connecting. how are you? (good|bad)"
                   :read-cb (lambda (socket data)
                              (my-app:continue-conversation socket data))
                   :event-cb (lambda (err)
                               (format t "condition while having convo: ~a~%" err)))

(write-socket-data socket "invalid command, closing connection"
                   :write-cb (lambda (socket) (close-socket socket)))

If you were to close the socket right after sending the data to the buffer, there’s no guarantee it would be sent out. Setting a write-cb guarantees that the data is sent when called.

Note that write-socket-data’s callbacks are identical to tcp-connect’s and if specified, will override those set by tcp-connect.

set-socket-timeouts

(defun set-socket-timeouts (socket read-sec write-sec))
  => nil

Set the read/write timeouts (in seconds) on a socket. If nil, the timeout is cleared, otherwise if a number, the timeout is set into the socket such that when the socket is active and hasn’t been read from/written to in the specified amount of time, it is closed.

nil for a timeout value unsets the timeout.

Note that if you call this using a socket that has been closed already, it will throw a socket-closed condition.

;; example
(set-socket-timeouts socket 10.5 nil)

enable-socket

(defun enable-socket (socket &key read write))
  => nil

Enable read/write monitoring on a socket. This is done automatically by tcp-connect and write-socket-data so you probably don’t need to worry too much about when to use it. On the other hand, disable-socket will probably be a bit more useful.

;;example
(enable-socket socket :read t :write t)  ; enable read and write monitoring on this socket

disable-socket

(defun disable-socket (socket &key read write))
  => nil

Disable read/write monitoring on a socket. This is useful if you get the data you need from a socket, but while you’re processing the data, you don’t want the socket’s read timeout to fire. This will both disable the timeouts and callbacks associated with the socket until enabled again.

socket-closed-p

(defun socket-closed-p (socket))
  => t/nil

Determines if a socket has been closed already.

close-socket

(defun close-socket (socket))
  => nil

Close a socket and free its callbacks.

Note that if you call this using a socket that has been closed already, it will throw a socket-closed condition.

Conditions

These are the conditions the TCP system can signal in event callbacks.

tcp-info

extends event-info

Base TCP condition, says “something” happened on a TCP connection.

tcp-socket

Holds the TCP socket class. Can be used to write to the socket or close it.

tcp-error

extends event-error and tcp-info

Describes a general error on a TCP connection. If this is triggered, the socket will generally be closed by cl-async, and the app doesn’t need to worry about doing this. If the app does want to close the socket, it can do so by getting it from the tcp-socket accessor on the condition and using close-socket.

tcp-eof

extends tcp-info

Triggered when the peer on a TCP connection closes the socket.

tcp-timeout

extends tcp-error

Triggered when a TCP connection times out.

tcp-refused

extends tcp-error

Triggered when a TCP connection is refused by the peer.

tcp-accept-error

extends tcp-error

Passed to a tcp-server’s event-cb when there is an error accepting a client connection.

tcp-accept-error-listener

The listener c object. Provided in case your app needs to process it in some way.

tcp-accept-error-tcp-server

The tcp-server object that the accept error happened on.

socket-closed

extends tcp-error

This exception is thrown by cl-async when the app tries to perform an operation on a socket that has already been closed via close-socket.

tcp-server-bind-error

extends tcp-error

This exception is thrown when tcp-server fails to bind to the address/port it has been given.