pax_global_header00006660000000000000000000000064134134110170014505gustar00rootroot0000000000000052 comment=b848a46d36b17adb86dda8539776596da9ad5566 usocket-trivial-sockets-b848a46/000077500000000000000000000000001341341101700166455ustar00rootroot00000000000000usocket-trivial-sockets-b848a46/LICENSE000066400000000000000000000021341341341101700176520ustar00rootroot00000000000000MIT License Copyright (c) 2004 Daniel Barlow and contributors Copyright (c) 2019 Chun Tian Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. usocket-trivial-sockets-b848a46/README.md000066400000000000000000000033411341341101700201250ustar00rootroot00000000000000# trivial-sockets A trivial networking library for undemanding Internet applications ## Usage examples: ``` (with-open-stream (s (trivial-sockets:open-stream "www.google.com" 80)) (format s "HEAD / HTTP/1.0~%Host: www.google.com~%~%") (force-output s) (loop (let ((l (read-line s nil nil))) (unless l (return)) (princ l) (terpri)))) (trivial-sockets:with-server (s (:port 8913 :reuse-address t)) (loop (with-open-stream (c (trivial-sockets:accept-connection s)) (read-line c) (format c "Hi there!~%")))) ``` Proper documentation is in trivial-sockets.texi. If you have Texinfo installed you can convert this to DVI or PDF using texi2dvi or texi2pdf, or use makeinfo to create an Info file for use with Emacs or the standalone info reader. ## Installation: Use asdf-install. * `(asdf:operate 'asdf:load-op 'asdf-install)` * `(asdf-install:install 'trivial-sockets)` Or if you don't have asdf-install but you do have asdf, create a symlink from a directory in your asdf:*central-registry* and run * `(asdf:operate 'asdf:load-op 'trivial-sockets)` Or if you don't have asdf, either (a) get it, or (b) compile the files by hand in an order that satisfies the dependencies in trivial-sockets.asd ## References: http://www.cliki.net/asdf-install http://www.cliki.net/asdf ## Thanks to: (alphabetical order) - Andras Simon for Armed Bear CL support - Edi Weitz, by whose asdf-install work some of the code was inspired - Oliver Markovic, for OpenMCL support - Rudi Schlatte, for a ton of stuff including OpenMCL and CMUCL server support, work on the manual, and also the Stevens justification I needed to make SO_REUSEADDR default - Sven Van Caekenberghe provided Lispworks support usocket-trivial-sockets-b848a46/abcl.lisp000066400000000000000000000045351341341101700204460ustar00rootroot00000000000000 (in-package :trivial-sockets) (defun resolve-hostname (name) (cond ((eql name :any) "0.0.0.0") ((typep name '(vector number 4)) (format nil "~{~A~^.~}" (coerce name 'list))) (t name))) (defun open-stream (peer-host peer-port &key (local-host :any) (local-port 0) (external-format :default) (element-type 'character) (protocol :tcp)) (unless (and (eql local-host :any) (eql local-port 0)) (error 'unsupported :feature :bind)) (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (ext:get-socket-stream (ext:make-socket (resolve-hostname peer-host) peer-port) :element-type element-type))) (defun open-server (&key (host :any) (port 0) (reuse-address t) (backlog 50) (protocol :tcp)) "Returns a SERVER object and the port that was bound, as multiple values" (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (unless (equal (resolve-hostname host) "0.0.0.0") (error 'unsupported :feature :bind)) (unless (= backlog 50) ;; the default, as of jdk 1.4.2 (error 'unsupported :feature :backlog)) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (let ((sock (ext:make-server-socket port))) (java:jcall (java:jmethod "java.net.ServerSocket" "setReuseAddress" "boolean") sock (java:make-immediate-object reuse-address :boolean)) (values sock (java:jcall (java:jmethod "java.net.ServerSocket" "getLocalPort") sock))))) (defun close-server (server) (ext:server-socket-close server)) (defun accept-connection (socket &key (external-format :default) (element-type 'character)) (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (ext:get-socket-stream (ext:socket-accept socket) :element-type element-type))) usocket-trivial-sockets-b848a46/allegro.lisp000066400000000000000000000040471341341101700211700ustar00rootroot00000000000000(in-package :trivial-sockets) (defun resolve-hostname (name) (cond ((eql name :any) "0.0.0.0") ((typep name '(vector number 4)) (format nil "~{~A~^.~}" (coerce name 'list))) (t name))) (defun open-stream (peer-host peer-port &key (local-host :any) (local-port 0) (external-format :default) (element-type 'character) (protocol :tcp)) (declare (ignore element-type)) (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (socket:make-socket :address-family :internet :connect :active :type :stream :remote-host (resolve-hostname peer-host) :remote-port peer-port :local-host (resolve-hostname local-host) :local-port local-port))) (defun open-server (&key (host :any) (port 0) (reuse-address t) (backlog 1) (protocol :tcp)) "Returns a SERVER object and the port that was bound, as multiple values" (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (let* ((host (if (eql host :any) nil host)) (socket (socket:make-socket :address-family :internet :type :stream :connect :passive :local-host host :local-port port :reuse-address reuse-address :backlog backlog))) (values socket (socket:local-port socket))))) (defun close-server (server) (close server)) (defun accept-connection (socket &key (external-format :default) (element-type 'character)) (declare (ignore element-type)) ; bivalent streams (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (socket:accept-connection socket :wait t))) usocket-trivial-sockets-b848a46/clisp.lisp000066400000000000000000000040061341341101700206500ustar00rootroot00000000000000(in-package :trivial-sockets) (defun resolve-hostname (name) (cond ((eql name :any) "0.0.0.0") ((typep name '(vector number 4)) (format nil "~{~A~^.~}" (coerce name 'list))) (t name))) (defun open-stream (peer-host peer-port &key (local-host :any) (local-port 0) (external-format :default) (element-type 'character) (protocol :tcp)) (unless (and (eql local-host :any) (eql local-port 0)) (error 'unsupported :feature :bind)) (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) ;; FIXME I wish there were a smarter way to detect only the errors ;; we're interested in, but CLISP impnotes don't say what to look for (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (socket:socket-connect peer-port (resolve-hostname peer-host) :element-type element-type :external-format external-format :buffered nil ))) (defun open-server (&key (host :any) (port 0) (reuse-address t) (backlog 1) (protocol :tcp)) (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (unless (equal (resolve-hostname host) "0.0.0.0") (error 'unsupported :feature :bind)) (unless (= backlog 1) ;; we established that the default backlog is 1 by stracing clisp ;; 2.33.2 (2004-06-02) (built 3304881526) (error 'unsupported :feature :backlog)) (unless reuse-address (error 'unsupported :feature :nil-reuse-address)) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (let ((s (socket:socket-server port))) (values s (socket:socket-server-port s))))) (defun close-server (server) (socket:socket-server-close server)) (defun accept-connection (socket &key (external-format :default) (element-type 'character)) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (socket:socket-accept socket :external-format external-format :element-type element-type :buffered nil))) usocket-trivial-sockets-b848a46/cmucl.lisp000066400000000000000000000070051341341101700206430ustar00rootroot00000000000000(in-package :trivial-sockets) (defun resolve-hostname (name) (cond ((eql name :any) "0.0.0.0") ((typep name '(vector number 4)) (format nil "~{~A~^.~}" (coerce name 'list))) (t name))) (defun pretty-stream-name (host port) (format nil "~A:~A" (resolve-hostname host) port)) (defun open-stream (peer-host peer-port &key (local-host :any) (local-port 0) (external-format :default) (element-type 'character) (protocol :tcp)) (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (unless (and (eql local-host :any) (eql local-port 0)) (error 'unsupported :feature :bind)) (unless (member protocol '(:tcp :udp)) (error 'unsupported :feature `(:protocol ,protocol))) ;; connect-to-inet-socket signals simple-erors. not great (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (let ((s (ext:connect-to-inet-socket (resolve-hostname peer-host) peer-port (case protocol (:tcp :stream) (:udp :datagram))))) (sys:make-fd-stream s :input t :output t :element-type element-type :buffering :full :name (pretty-stream-name peer-host peer-port))))) (defun open-server (&key (host :any) (port 0) (reuse-address t) (backlog 1) (protocol :tcp)) "Returns a SERVER object and the port that was bound, as multiple values" (unless (member protocol '(:tcp :udp)) (error 'unsupported :feature `(:protocol ,protocol))) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (let ((socket (if (equal (resolve-hostname host) "0.0.0.0") ;; create-inet-listener barfs on `:host nil' (ext:create-inet-listener port (case protocol (:tcp :stream) (:udp :datagram)) :reuse-address reuse-address :backlog backlog) (ext:create-inet-listener port (case protocol (:tcp :stream) (:udp :datagram)) :reuse-address reuse-address :backlog backlog :host host)))) (multiple-value-bind (host port) (ext:get-socket-host-and-port socket) (declare (ignore host)) (values socket port))))) (defun close-server (server) (unix:unix-close server)) (defun accept-connection (socket &key (external-format :default) (element-type 'character) (buffering :full)) ; (member :full :line :none) (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (let ((fd (ext:accept-tcp-connection socket))) (multiple-value-bind (peer-host peer-port) (ext:get-peer-host-and-port fd) (sys:make-fd-stream fd :input t :output t :element-type element-type :auto-close t :buffering buffering :name (pretty-stream-name peer-host peer-port)))))) usocket-trivial-sockets-b848a46/defpackage.lisp000066400000000000000000000003571341341101700216150ustar00rootroot00000000000000(in-package :cl-user) (defpackage trivial-sockets (:use :CL) (:export #:open-stream #:socket-error #:socket-nested-error #:unsupported #:unsupported-feature #:open-server #:close-server #:accept-connection #:with-server)) usocket-trivial-sockets-b848a46/errors.lisp000066400000000000000000000006311341341101700210520ustar00rootroot00000000000000(in-package :trivial-sockets) ;; you're using a part of the interface that the implementation doesn't do (define-condition unsupported (error) ((feature :initarg :feature :reader unsupported-feature))) ;; all-purpose error: host not found, host not responding, ;; no service on that port, etc (define-condition socket-error (error) ((nested-error :initarg :nested-error :reader socket-nested-error))) usocket-trivial-sockets-b848a46/lispworks.lisp000066400000000000000000000102211341341101700215670ustar00rootroot00000000000000(in-package :trivial-sockets) (defun resolve-hostname (name) (cond ((eql name :any) "0.0.0.0") ((typep name '(vector number 4)) (format nil "~{~A~^.~}" (coerce name 'list))) (t name))) (defun open-stream (peer-host peer-port &key (local-host :any) (local-port 0) (external-format :default) (element-type 'base-char) (protocol :tcp)) (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (unless (eql external-format :default) (error 'unsupported :feature `(:external-format ,external-format))) (unless (eql local-host :any) (error 'unsupported :feature `(:local-host ,local-host))) (unless (eql local-port 0) (error 'unsupported :feature `(:local-port ,local-port))) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (comm:open-tcp-stream (resolve-hostname peer-host) peer-port :element-type element-type :errorp t))) ;; there is no (published) way to make a server socket in lispworks ;; this server implementation is a hack around the otherwise elegant ;; lispworks #'comm:start-up-server functionality (defun make-queue () (cons nil nil)) (defun queue-empty-p (queue) (null (car queue))) (defun enqueue (x queue) (if (null (car queue)) (setf (cdr queue) (setf (car queue) (list x))) (setf (cdr (cdr queue)) (list x) (cdr queue) (cdr (cdr queue)))) (car queue)) (defun dequeue (queue) (pop (car queue))) (defclass server () ((process :reader get-process) (lock :initform (mp:make-lock)) (clients :initform (make-queue)))) (defun open-server (&key (host :any) (port 0) (reuse-address t) (backlog 5) (protocol :tcp)) "Returns a SERVER object and the port that was bound, as multiple values" (unless (eql host :any) ;; not in the manual, appears in arglist, maybe not on all platforms (error 'unsupported :feature `(:host ,host))) (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (unless (eql backlog 5) ;; not in the manual, appears in arglist, maybe not on all platforms (error 'unsupported :feature `(:backlog ,backlog))) (let ((server (make-instance 'server))) (with-slots (process lock clients) server (multiple-value-bind (new-process condition) ;; we enqueue all incoming connections until #'accept-connection retrieves them (let ((comm::*use_so_reuseaddr* reuse-address)) (comm:start-up-server :function #'(lambda (socket) (mp:with-lock (lock) (enqueue socket clients))) :service port :wait t)) (when condition (error 'socket-error :nested-error condition)) (setf process new-process))) (values server port))) ;; we do not return the actual port when port was 0 (defun close-server (server) (with-slots (process) server (mp:process-kill process) (setf process nil))) (defun accept-connection (server &key (external-format :default) (element-type 'base-char)) (unless (eql external-format :default) (error 'unsupported :feature `(:external-format, external-format))) (let (client-socket) (with-slots (process lock clients) server (unless process (error 'socket-error :nested-error (make-instance 'simple-error :format-string "Server closed"))) (loop (mp:with-lock (lock) (unless (queue-empty-p clients) (setf client-socket (dequeue clients)) (return))) (mp:process-wait "Waiting for incoming connections" #'(lambda (server) (with-slots (lock clients) server (mp:with-lock (lock) (not (queue-empty-p clients))))) server))) (make-instance 'comm:socket-stream :socket client-socket :direction :io :element-type element-type))) usocket-trivial-sockets-b848a46/mcl.lisp000066400000000000000000000061101341341101700203070ustar00rootroot00000000000000;;;; This is a port to MCL 5.0 using the opentransport library. ;;; Unfortunately opentransport has a different way of dealing with ;;; passive (server) side. The semantics of open-server and ;;; accept-connection is therefor not quite right. It won't actually start ;;; listening until the first accept-connection. (in-package :trivial-sockets) (require "OPENTRANSPORT") (defclass MCL-LISTENER-SOCKET () ((port :initarg :port :accessor mcl-listener-port) (stream :initform nil :accessor listener-stream) (reuse-address :initarg :reuse-address :accessor reuse-address))) (defun open-stream (peer-host peer-port &key (local-host :any) (local-port 0) (external-format :default) (element-type 'base-character) (protocol :tcp)) (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (unless (eql local-host :any) (error 'unsupported :feature :local-host)) (unless (eql local-port 0) (error 'unsupported :feature :local-port)) (handler-bind ((error (lambda (c) (error 'socket-error :nested-error c)))) (ccl::open-tcp-stream peer-host peer-port :element-type element-type :connect-timeout 60))) (defun open-server (&key (host :any) (port 0) (reuse-address t) (backlog 1) (protocol :tcp)) "Returns a SERVER object" (declare (ignore backlog)) (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (unless (eql host :any) (error 'unsupported :feature :host)) (when (eql port 0) (error 'unsupported :feature `(:port 0))) (let ((listener (make-instance 'mcl-listener-socket :port port :reuse-address reuse-address))) ;;(accept-connection listener) listener)) (defun close-server (server) (when (listener-stream server) (close (listener-stream server)))) (defun accept-connection (server &key (external-format :default) (element-type 'base-character)) (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (do ((s (listener-stream server)) (new nil)) (new new) (when s (flet ((ready-p (s) (not (eql (ccl::opentransport-stream-connection-state s) :unbnd)))) (unless (ready-p s) (ccl:process-wait "waiting" #'ready-p s)))) (let ((state (and s (ccl::opentransport-stream-connection-state s)))) (when (member state '(:incon :dataxfer)) (setq new s state nil)) (when (member state '(nil :uninit :closed)) (or (mcl-listener-port server) (error "MCL OpenTransport needs explicit port number for listener stream")) (setq s (setf (listener-stream server) (ccl::open-tcp-stream nil (mcl-listener-port server) :element-type element-type :reuse-local-port-p (reuse-address server)))))))) usocket-trivial-sockets-b848a46/openmcl.lisp000066400000000000000000000044231341341101700211760ustar00rootroot00000000000000(in-package :trivial-sockets) (defun resolve-hostname (name) (cond ((eql name :any) "0.0.0.0") ((typep name '(vector number 4)) (format nil "~{~A~^.~}" (coerce name 'list))) (t name))) (defun open-stream (peer-host peer-port &key (local-host :any) (local-port 0) (external-format :default) (element-type 'character) (protocol :tcp)) (declare (ignore element-type)) (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (handler-bind ((ccl::socket-creation-error (lambda (c) (error 'socket-error :nested-error c)))) (ccl:make-socket :address-family :internet :connect :active :type :stream :remote-host (resolve-hostname peer-host) :remote-port peer-port :local-host (resolve-hostname local-host) :local-port local-port))) (defun open-server (&key (host :any) (port 0) (reuse-address t) (backlog 1) (protocol :tcp)) "Returns a SERVER object and the port that was bound, as multiple values" (unless (eql protocol :tcp) (error 'unsupported :feature `(:protocol ,protocol))) (handler-bind ((ccl::socket-creation-error (lambda (c) (error 'socket-error :nested-error c)))) (let* ((host (if (eql host :any) nil host)) (socket (ccl:make-socket :address-family :internet :type :stream :connect :passive :local-host host :local-port port :reuse-address reuse-address :backlog backlog))) (values socket (ccl:local-port socket))))) (defun close-server (server) (close server)) (defun accept-connection (socket &key (external-format :default) (element-type 'character)) (declare (ignore element-type)) ; openmcl streams are bivalent. (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (handler-bind ((ccl:socket-error (lambda (c) (error 'socket-error :nested-error c)))) (ccl:accept-connection socket :wait t))) usocket-trivial-sockets-b848a46/sbcl.lisp000066400000000000000000000043141341341101700204630ustar00rootroot00000000000000(in-package :trivial-sockets) (defun resolve-hostname (name) (cond ((eql name :any) #(0 0 0 0)) ((typep name '(vector number 4)) name) (t (car (sb-bsd-sockets:host-ent-addresses (sb-bsd-sockets:get-host-by-name name)))))) (defun open-stream (peer-host peer-port &key (local-host :any) (local-port 0) (external-format :default) (element-type 'character) (protocol :tcp)) (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (handler-bind ((sb-bsd-sockets:socket-error (lambda (c) (error 'socket-error :nested-error c))) (sb-bsd-sockets:name-service-error (lambda (c) (error 'socket-error :nested-error c)))) (let ((s (make-instance 'sb-bsd-sockets:inet-socket :type :stream :protocol protocol)) (me (resolve-hostname local-host))) (unless (and (equal me #(0 0 0 0)) (eql local-port 0)) (sb-bsd-sockets:socket-bind s me local-port)) (sb-bsd-sockets:socket-connect s (resolve-hostname peer-host) peer-port) (sb-bsd-sockets:socket-make-stream s :input t :output t :element-type element-type :buffering :full)))) (defun open-server (&key (host :any) (port 0) (reuse-address t) (backlog 1) (protocol :tcp)) "Returns a SERVER object and the port that was bound, as multiple values" (let ((sock (make-instance 'sb-bsd-sockets:inet-socket :type :stream :protocol protocol))) (when reuse-address (setf (sb-bsd-sockets:sockopt-reuse-address sock) t)) (sb-bsd-sockets:socket-bind sock (resolve-hostname host) port) (sb-bsd-sockets:socket-listen sock backlog) (multiple-value-bind (h p) (sb-bsd-sockets:socket-name sock) (declare (ignore h)) (values sock p)))) (defun close-server (server) (sb-bsd-sockets:socket-close server)) (defun accept-connection (socket &key (external-format :default) (element-type 'character)) (unless (eql external-format :default) (error 'unsupported :feature :external-format)) (let ((s (sb-bsd-sockets:socket-accept socket))) (sb-bsd-sockets:socket-make-stream s :input t :output t :element-type element-type :buffering :full))) usocket-trivial-sockets-b848a46/server.lisp000066400000000000000000000004001341341101700210360ustar00rootroot00000000000000(in-package :trivial-sockets) (defmacro with-server ((name arguments) &body forms) `(let (,name) (unwind-protect (progn (setf ,name (open-server ,@arguments)) (locally ,@forms)) (when ,name (close-server ,name))))) usocket-trivial-sockets-b848a46/trivial-sockets.asd000066400000000000000000000013541341341101700224640ustar00rootroot00000000000000;;; -*- Lisp -*- (defpackage #:trivial-sockets-system (:use #:asdf #:cl)) (in-package #:trivial-sockets-system ) (defsystem trivial-sockets :version "0.4" :author "Daniel Barlow" :maintainer "Chun Tian (binghe)" :licence "MIT" :description "A trivial networking library for undemanding Internet applications" :depends-on (#+sbcl sb-bsd-sockets) :components ((:file "defpackage") (:file "errors" :depends-on ("defpackage")) (:file #+sbcl "sbcl" #+cmu "cmucl" #+clisp "clisp" #+acl-socket "allegro" #+openmcl "openmcl" #+lispworks "lispworks" #+armedbear "abcl" #+Digitool "mcl" :depends-on ("defpackage")) (:file "server" :depends-on ("defpackage")) )) usocket-trivial-sockets-b848a46/trivial-sockets.texi000066400000000000000000000337041341341101700226720ustar00rootroot00000000000000\input texinfo @c -*- texinfo -*- @c %**start of header @setfilename trivial-sockets.info @settitle TRIVIAL-SOCKETS Manual @c %**end of header @c merge type index into function index @syncodeindex tp fn @c ... and concept index, too. @synindex cp fn @c for install-info @dircategory Software development @direntry * trivial-sockets: (trivial-sockets). CL socket interface for scripting/interactive use @end direntry @copying This manual describes TRIVIAL-SOCKETS, a simple socket interface for Common Lisp programs and libraries. Copyright @copyright{} 2004 Daniel Barlow and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ``Software''), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @end copying @titlepage @title TRIVIAL-SOCKETS @c The following two commands start the copyright page. @page @vskip 0pt plus 1filll @insertcopying @end titlepage @c Output the table of contents at the beginning. @contents @c ------------------- @ifnottex @node Top @top TRIVIAL-SOCKETS: a socket interface for scripting and interactive use @insertcopying @menu * Introduction:: Design goals and target audience * Installation:: How to download and install * API:: * Index:: @end menu @end ifnottex @c ------------------- @node Introduction @chapter Introduction TRIVIAL-SOCKETS is a portable socket interface that allows CL programs to open connected (client) stream sockets to network services (e.g. HTTP, FTP, SMTP servers) and communicate with them. It's intended mostly for use by small ``script'' programs and for interactive use where the effort involved in writing one's own portable wrapper layer for several Lisp implementations would outweigh that spent on the actual application. In the interests of simplicity and ease of porting, the functionality available through TRIVIAL-SOCKETS has been deliberately restricted. For a more general sockets interface which may allow access to more functionality, the reader is encouraged to consult his Lisp implementation's documentation. @node Installation @chapter Installation @cindex{Installation} TRIVIAL-SOCKETS is distributed via asdf-install. If you are on the Internet and your Lisp implementation has asdf-install available, you may download and compile this package with an invocation like @lisp (asdf-install:install 'trivial-sockets) @end lisp The trivial-sockets package has been PGP-signed by Daniel Barlow, and asdf-install will by default check that the signature is good and that a trust path exists between you and him. If not, you will be prompted for a decision on whether to install anyway. See asdf-install documentation for more details on how this works. Once you have installed trivial-sockets, the next time you wish to load it you need only evaluate @lisp (asdf:operate 'asdf:load-op 'trivial-sockets) @end lisp or if you have an asdf system that uses it, add @code{trivial-sockets} to the @code{:depends-on} clause of that system and it will be loaded whenever your system is. @chapter API @node API @section Types @cindex{Host designator} @cindex{IP address} @cindex{Address} @cindex{Protocol} A @emph{host designator} is one of the following: @enumerate @item A string, which is resolved as a hostname by the system resolver, typically using DNS or YP or some other implementation-defined mechanism. For example, @code{"www.google.com"} @item An IPv4 address in "dotted quad" notation: e.g. @code{"127.0.0.1"} @item (Implementation-defined): An IPv4 address in whatever ``native'' format the implementation uses to represent same, if applicable. For example, @code{#(127 0 0 1)} or @code{2130706433} @item The keyword @code{:ANY}, which corresponds to INADDR_ANY or "0.0.0.0" @end enumerate A @emph{protocol specifier} is a keyword naming an @uref{http://www.iana.org/assignments/protocol-numbers,,IANA protocol number} (as typically found in @file{/etc/protocols} on Unix-like systems) or the corresponding number. Implementations must support @code{:TCP} at a minimum. @section Functions @anchor{Function open-stream} @defun open-stream peer-host peer-port &key local-host local-port external-format element-type protocol @result{} stream @strong{Arguments and Values:} @var{peer-host}--a host designator. @var{peer-port}--an integer. @var{local-host}--a host designator. The default is @code{:any}. @var{local-port}--an integer. The default is @code{0}. @var{external-format}--an external file format designator. The default is @code{:default}. @var{element-type}--a type specifier; see the Common Lisp function @code{open} for valid values. The default is @code{'character}. @var{protocol}--a protocol specifier. The default is @code{:tcp}. @strong{Description:} Return a stream to the named service, open for both reading and writing. The stream is usually buffered, so be sure to use @code{force-output} where necessary. If the stream cannot be created for any reason, an error of type @code{socket-error} is signaled. The stream should be closed in the usual way when no longer needed: see the Common Lisp functions @code{close}, @code{with-open-stream} @end defun @anchor{Function open-server} @defun open-server &key host port reuse-address backlog protocol @result{} server socket @strong{Arguments and Values:} @var{host}--a host designator. The default is @code{:any}. @var{port}--an integer. The default is @code{0}. @var{reuse-address}--@code{t} or @code{nil}. The default is @code{t}. @var{backlog}--an integer. The default is @code{1}. @var{protocol}--a protocol specifier. The default is @code{:tcp}. @strong{Description:} Create a listening server socket. If @var{port} is 0, an unused port will be chosen by the implementation/operating system. @var{Host} may be set to the address of any local network interface to restrict the socket to that interface. If @var{reuse-address} is true (the default, as recommended by Stevens) then the @code{SO_REUSEADDR} socket option will be set, which allows the the port to be reused immediately after it has been closed, without waiting for a timeout (``2*MSL'') to expire. @var{Backlog} sets how many pending connections are queued by the operating system. If the socket cannot be created for any reason, an error of type @code{socket-error} is signaled. The nature of the object returned is implementation-dependent. When the socket is no longer needed it should be closed with @code{close-server}. @xref{Macro with-server}. @end defun @c <3dqes6$e49@bosnia.pop.psu.edu> or see 242-246 of @c "TCP/IP Illustrated, Volume 1" @anchor{Function close-server} @defun close-server server @result{} result @strong{Arguments and Values:} @var{server}--a server socket. @var{result}--implementation-dependent. @strong{Description:} Close @var{server} and release all resources associated with it. Note that opening a new server on the same address/port will not be immediately possible unless the earlier server was created with the @code{:reuse-address} argument. @end defun @anchor{Macro with-server} @defmac with-server (server args) declaration* form* @result{} results @strong{Arguments and Values:} @var{server}--a variable. @var{args}--a list of arguments. @var{declaration}--a declare expression. @var{forms}--an implicit @code{progn}. @var{results}--the values returned by the @var{forms}. @strong{Description:} @code{with-server} uses @code{open-server} to create a server socket named by @var{server}. @var{Args} are used as keyword arguments to @code{open-server}. @code{with-server} evaluates the @var{forms} as an implicit progn with @var{server} bound to the value returned by @code{open-server}. When control leaves the body, either normally or abnormally (such as by use of @code{throw}), the server socket is automatically closed. The consequences are undefined if an attempt is made to assign to the variable @var{server} within the body forms. @end defmac @anchor{Function accept-connection} @defun accept-connection server &key external-format element-type @result{} stream @strong{Arguments and Values:} @var{server}--a server socket. @var{external-format}--an external file format designator. The default is @code{:default}. @var{element-type}--a type specifier; see the Common Lisp function @code{open} for valid values. The default is @code{'character}. @strong{Description:} Accept a connection to @var{server}, returning a stream connected to the client which is open for both reading and writing. The stream is usually buffered, so be sure to use @code{force-output} where necessary. If no connection is pending, @code{accept-connection} waits until one arrives. If anything goes wrong, an error of type @code{socket-error} is signaled. @end defun @section Examples @subsection Simple client @lisp ;; this is not HTTP compliant, really. But it's good enough ;; for a demonstration (with-open-stream (s (trivial-sockets:open-stream "www.google.com" 80)) (format s "HEAD / HTTP/1.0~%Host: www.google.com~%~%") (force-output s) (loop (let ((l (read-line s nil nil))) (unless l (return)) (princ l) (terpri)))) @end lisp @subsection Simple (single-threaded) server @lisp (trivial-sockets:with-server (s (:port 8913 :reuse-address t)) (loop (with-open-stream (c (trivial-sockets:accept-connection s)) (read-line c) (format c "This is a compliant though pointless implementation ~ of the finger protocol~%")))) @end lisp @section Errors @anchor{Condition unsupported} @deftp {Condition} unsupported Class precedence list: @w{error} This exists so that partial implementations of this interface may be created for environments which are incapable of supporting the full API. An @code{unsupported} error is signaled if the user requests functionality that is not implemented for the Lisp environment in use. @end deftp @anchor{Condition socket-error} @deftp {Condition} socket-error Class precedence list: @w{error} A @code{socket-error} error is signaled when an error situation occurs during opening of the stream. If you need more detail, this is probably a sign that you have outgrown this interface and will have to resort to unportable code (error codes vary between systems:were you expecting @code{HOST_UNREACH} or @code{NET_UNREACH}?). With that in mind, you can access the implementation-specific error using @lisp (socket-nested-error condition) @end lisp @end deftp @chapter Implementation-dependent @node Implementation-dependent Not all features in this interface are supported on all platforms, owing to deficiencies in the underlying socket layers that it uses. Many implementations signal socket-related errors using non-specific error classes such as ERROR or SIMPLE-ERROR. (Some others, perhaps, signal more specific errors but the code in trivial-sockets does not know that. Patches welcome). Where we don't know of a specific error, we catch the general ones and resignal @code{SOCKET-ERROR}, so it's possible sometimes that errors shich are nothing at all to do with sockets (e.g. keyboard interrupts or external signals) also get presented as SOCKET-ERRORs. This applies in all implementations listed except where noted. @itemize @item Armed Bear CL currently supports only client sockets, and only for TCP, with unspecified local endpoint, and with the default external-format. @item Allegro CL (tested in Allegro 6.2. trial) has no support for protocols other than @code{:tcp} or non-default external-formats. Allegro sockets are multivalent, so it ignores the @code{:element-type}. @item CLISP has no support for protocols that are not @code{:tcp}, or for binding the local address/port. Its streams are unbuffered, as CLISP buffered streams do not return any data at all on reads until the buffer is full - making them no use for any protocol in which one side sends less than 4k at a time. (CLISP ``interactively buffered'' streams are likely to fix this, but as of October 2004 have not yet been implemented). @item CMUCL has no support for external-formats other than @code{:default}, for protocols that are not @code{:tcp}, or for binding the local address/port. @item Lispworks supports TCP only, It doesn't do non-default local address in server sockets, or listen backlog length. It doesn't do non-default external-formats. If the local port is 0, @code{open-server} doesn't return the real port number. It also uses an odd construction involving multiple threads for server sockets which in principle should be transparent but don't say we didn't warn you. @item OpenMCL socket support is very similar to that of Allegro: all implementation notes applicable to Allegro also hold for OpenMCL. Additionally, errors signaled by instances of @code{ccl:socket-error} are caught and resignaled as @code{socket-error}. @item SBCL has no support for external-formats other than @code{:default}. Errors signaled by @code{sb-bsd-sockets:socket-error} and @code {sb-bsd-sockets:name-service-error} are caught and resignaled as @code{socket-error}. @end itemize Patches to improve per-implementation support for this interface are welcome. Patches which include an appropriate update for the manual are doubly if not sevenfoldly so. @c ------------------- @node Index, @unnumbered Index @printindex fn @bye