Subject: Re: Newbie Q. How do I use sockets?
From: Erik Naggum <erik@naggum.no>
Date: 2000/06/28
Newsgroups: comp.lang.lisp
Message-ID: <3171189497310759@naggum.no>

* Richard James Panturis Giuly <no@spam.com>
| How do I access tcp sockets with lisp?

  There's a lot of stuff you can do, and then you can avoid most of
  the problems with a few simple macros:

(defmacro with-network-server ((variable &key keep-open (host 0) port log-error options)
			       &body body)
  "Set up a simple network server.
The server socket is opened on HOST:PORT, default *:PORT if no HOST is specified.  Further socket
options may be supplied as a list of keyword-value pairs in OPTIONS.
The BODY is executed with VARIABLE (a symbol) bound to each incoming connection, which is gauranteed
to be closed upon exit from BODY unless KEEP-OPEN is true.
If a socket error occurs during socket creation, it is logged as a critical error (unless LOG-ERROR
is false) and the error is re-signaled.  If not caught elsewhere, this form returns the error object.
An error when accepting a connection is ignored.
Errors in the body invoke the debugger for now, as do non-socket errors elsewhere."
  (let ((server (gensym)))
    `(let ((,server (handler-case (make-socket :connect :passive
					       :local-host ,host
					       :local-port ,port
					       ,@options)
		      (socket-error (error)
			(case (socket-error-code error)
			  ((:address-in-use :address-not-available)
			   (signal error)
			   error)
			  (t
			   ,(when log-error
			      `(write-system-log :critical
				 "Could not create server on ~A:~D:~%~A" ,host ,port error))
			   (signal error)
			   error))))))
       (if (typep ,server 'socket)
	 (unwind-protect
	     (loop
	       (let ((,variable (accept-connection ,server)))
		 ,(if keep-open
		    `(when (typep ,variable 'socket)
		       ,@body)
		    `(when (typep ,variable 'socket)
		       (unwind-protect
			   (progn ,@body)
			 (when (typep ,variable 'socket)
			   (close ,variable)))))))
	   (when (typep ,server 'socket)
	     (close ,server)))
	 ,server))))

(defmacro with-network-client ((variable &key keep-open (host 0) port log-error options)
			       &body body)
  "Set up a simple network client.
The client socket is opened to HOST:PORT, default <localhost>:PORT if no host is specified
and bound to VARIABLE across BODY, to be closed on exist, unless kEEP-OPEN is true.
If a socket error occurs during socket creation, it is logged as a notice (unless LOG-ERROR is
false) and the error is re-signaled.  If not caught elsewhere, this form returns the error object."
  `(let ((,variable (handler-case (make-socket :connect :active
					       :remote-host ,host
					       :remote-port ,port
					       ,@options)
		      (socket-error (error)
			(case (socket-error-code error)
			  ((:network-down :network-unreachable :network-reset
			    :host-down :host-unreachable)
			   (signal error)
			   error)
			  ((:connection-timed-out :connection-refused)
			   nil)
			  (t
			   ,(when log-error
			      `(write-system-log :notice
				 "Could not open connection to ~A:~D:~%~A" ,host ,port error))
			   (signal error)
			   error))))))
     ,(if keep-open
	`(if (typep ,variable 'socket)
	   (progn ,@body)
	   ,variable)
	`(if (typep ,variable 'socket)
	   (unwind-protect
	       (progn ,@body)
	     (when (typep ,variable 'socket)
	       (close, variable)))
	   ,variable))))

  Let's ignore the write-system-log call for now.

  I have a slightly modified version to obey the TELNET protocol, but
  it's too much to post here.  The whois client goes like this:

  Example:

(with-telnet-client (whois :host "whois.internic.net" :port "whois")
  (write-line "clemacs.org" whois)		  ;Automatic CRLF and force-output
  (ignore-errors (loop (write-line (read-line whois))))
  (values))

#:Erik
-- 
  If this is not what you expected, please alter your expectations.