Skip to content

Instantly share code, notes, and snippets.

@shortsightedsid
Last active January 15, 2024 02:36
Show Gist options
  • Star 39 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save shortsightedsid/71cf34282dfae0dd2528 to your computer and use it in GitHub Desktop.
Save shortsightedsid/71cf34282dfae0dd2528 to your computer and use it in GitHub Desktop.
Short guide to TCP/IP Client/Server programming in Common Lisp using usockets
; Short guide to TCP/IP Client/Server programming in Common Lisp using usockets
;
; The main reason for this guide is because there are very few examples that
; explain how to get started with socket programming with Common Lisp that I
; could understand. After spending a day trying, I finally came up with a small
; bit of code that makes it easy to understand the basics. I've written this
; primarily for myself, but should help others get started as well.
; As usual, we will use quicklisp to load usocket.
(ql:quickload "usocket")
; Now we need to create a server. There are 2 primary functions that we need
; to call. usocket:socket-listen and usocket:socket-accept.
;
; usocket:socket-listen binds to a port and listens on it. It returns a socket
; object. We need to wait with this object until we get a connection that we
; accept. That's where usocket:socket-accept comes in. It's a blocking call
; that returns only when a connection is made. This returns a new socket object
; that is specific to that connection. We can then use that connection to
; communicate with our client.
;
; So, what were the problems I faced due to my mistakes?
; Mistake 1 - My initial understanding was that socket-accept would return
; a stream object. NO.... It returns a socket object. In hindsight, its correct
; and my own mistake cost me time. So, if you want to write to the socket, you
; need to actually get the corresponding stream from this new socket. The socket
; object has a stream slot and we need to explicitly use that. And how does one
; know that? (describe connection) is your friend!
;
; Mistake 2 - You need to close both the new socket and the server socket.
; Again this is pretty obvious but since my initial code was only closing
; the connection, I kept running into a socket in use problem. Of course
; one more option is to reuse the socket when we listen.
;
; Once you get past these mistakes, it's pretty easy to do the rest. Close
; the connections and the server socket and boom you are done!
(defun create-server (port)
(let* ((socket (usocket:socket-listen "127.0.0.1" port))
(connection (usocket:socket-accept socket :element-type 'character)))
(unwind-protect
(progn
(format (usocket:socket-stream connection) "Hello World~%")
(force-output (usocket:socket-stream connection)))
(progn
(format t "Closing sockets~%")
(usocket:socket-close connection)
(usocket:socket-close socket)))))
; Now for the client. This part is easy. Just connect to the server port
; and you should be able to read from the server. The only silly mistake I
; made here was to use read and not read-line. So, I ended up seeing only a
; "Hello" from the server. I went for a walk and came back to find the issue
; and fix it.
(defun create-client (port)
(let ((socket (usocket:socket-connect "127.0.0.1" port :element-type 'character)))
(unwind-protect
(progn
(usocket:wait-for-input socket)
(format t "~A~%" (read-line (usocket:socket-stream socket))))
(usocket:socket-close socket))))
; So, how do you run this? You need two REPLs - one for the server
; and one for the client. Load this file in both REPLs. Create the
; server in the first REPL.
; (create-server 12321)
; Now you are ready to run the client on the second REPL
; (create-client 12321)
; Voila! You should see "Hello World" on the second REPL.
; ; Also see
; 1. Short Guide on UDP/IP
; - https://gist.github.com/shortsightedsid/a760e0d83a9557aaffcc
@hanshuebner
Copy link

In code that you'd want someone else to read, you should use usocket:with-client-socket and usocket:with-server-socket. usocket:with-client-socket has the additional benefit that it binds a stream variable for you, so you do not need to use the stream accessors.

@shortsightedsid
Copy link
Author

Thanks @hanshuebner. I'll whip that one up as well.

@t-cool
Copy link

t-cool commented Dec 31, 2017

This tutorial helped me a lot!
Could I translate this post into Japanese and share it?

@traut
Copy link

traut commented Sep 12, 2018

small self-contained TCP echo server, inspired by this snippet -- https://gist.github.com/traut/6bf71d0da54493e6f22eb3d00671f2a9

@ArchI3Chris
Copy link

First of all, thanks for one of only few simple examples on the matter. That's quite helpful. So, thanks for that. That needs to be said.

However, this has some flaws.

First of all, it doesn't need the actually ql:quickload line every single time. This is for installation (Note: I'm using SBCL, but it should be the same), so you don't need to install it every single time you run the application and once is enough. What it needs is (require :usocket) and BOTH only works in case quicklisp is installed and loaded. This is a problem I had. SBCL didn't load the .sbclrc file and therefore didn't recognize, that quicklisp was installed. Therefore it gave me an error saying it doesn't know how to require usocket. So, loading the .sbclrc file it worked.

Secondly, have you actually tried the code? Yes, you SHOULD see Hello World. That's the theory. The reality in my case is, that I see HELLO and that's it. This one only sends the FIRST word and not the whole sentence. So, something doesn't work with this code.

Also, for serious two-way communication, did you ever try to send something from the client to the server and say output it on the server (console) or react based on that? Like telling the server what you need and it responds with different data, depending on the request? That would be interesting too.

@binghe
Copy link

binghe commented Apr 28, 2023

Hi, I'm a maintainer of usocket, can I incorporate your material as a section of the official user manual (in progress)? Of course your name will appear as the author.

@ArchI3Chris
Copy link

Hi, I'm a maintainer of usocket, can I incorporate your material as a section of the official user manual (in progress)? Of course your name will appear as the author.

@binghe I received the email notification. You are probably referring to the gist on top. But given I too offer an example and so does @traut in this threat just before my comment, which are the last two here, I just want to make sure. So, which of the three examples are you referring to and is it that you want to use?

@binghe
Copy link

binghe commented Apr 28, 2023

Sorry, to be clear, I was asking @shortsightedsid for his cl-tcpip.lisp and cl-udpip.lisp, because I found they also have good inline documents.

@ArchI3Chris
Copy link

Sorry, to be clear, I was asking @shortsightedsid for his cl-tcpip.lisp and cl-udpip.lisp, because I found they also have good inline documents.

No big deal. I thought so. Just wanted to make sure ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment