289 lines
11 KiB
HTML
289 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta name="generator" content=
|
||
"HTML Tidy for HTML5 for Linux version 5.2.0">
|
||
<title>TCP/UDP programming with sockets</title>
|
||
<meta charset="utf-8">
|
||
<meta name="description" content="A collection of examples of using Common Lisp">
|
||
<meta name="viewport" content=
|
||
"width=device-width, initial-scale=1">
|
||
<link rel="icon" href=
|
||
"assets/cl-logo-blue.png"/>
|
||
<link rel="stylesheet" href=
|
||
"assets/style.css">
|
||
<script type="text/javascript" src=
|
||
"assets/highlight-lisp.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery-3.2.1.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery.toc/jquery.toc.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/toggle-toc.js">
|
||
</script>
|
||
|
||
<link rel="stylesheet" href=
|
||
"assets/github.css">
|
||
|
||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||
</head>
|
||
<body>
|
||
<h1 id="title-xs"><a href="index.html">The Common Lisp Cookbook</a> – TCP/UDP programming with sockets</h1>
|
||
<div id="logo-container">
|
||
<a href="index.html">
|
||
<img id="logo" src="assets/cl-logo-blue.png"/>
|
||
</a>
|
||
|
||
<div id="searchform-container">
|
||
<form onsubmit="duckSearch()" action="javascript:void(0)">
|
||
<input id="searchField" type="text" value="" placeholder="Search...">
|
||
</form>
|
||
</div>
|
||
|
||
<div id="toc-container" class="toc-close">
|
||
<div id="toc-title">Table of Contents</div>
|
||
<ul id="toc" class="list-unstyled"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="content-container">
|
||
<h1 id="title-non-xs"><a href="index.html">The Common Lisp Cookbook</a> – TCP/UDP programming with sockets</h1>
|
||
|
||
<!-- Announcement we can keep for 1 month or more. I remove it and re-add it from time to time. -->
|
||
<!-- <p class="announce"> -->
|
||
<!-- 📢 🤶 ⭐ -->
|
||
<!-- <a style="font-size: 120%" href="https://www.udemy.com/course/common-lisp-programming/?couponCode=LISPY-XMAS2023" title="This course is under a paywall on the Udemy platform. Several videos are freely available so you can judge before diving in. vindarel is (I am) the main contributor to this Cookbook."> Discover our contributor's Lisp course with this Christmas coupon.</a> -->
|
||
<!-- <strong> -->
|
||
<!-- Recently added: 18 videos on MACROS. -->
|
||
<!-- </strong> -->
|
||
<!-- <a style="font-size: 90%" href="https://github.com/vindarel/common-lisp-course-in-videos/">Learn more</a>. -->
|
||
<!-- </p> -->
|
||
|
||
<p class="announce">
|
||
📢 New videos: <a href="https://www.youtube.com/watch?v=h_noB1sI_e8">web dev demo part 1</a>, <a href="https://www.youtube.com/watch?v=xnwc7irnc8k">dynamic page with HTMX</a>, <a href="https://www.youtube.com/watch?v=Zpn86AQRVN8">Weblocks demo</a>
|
||
</p>
|
||
|
||
<p class="announce-neutral">
|
||
📕 <a href="index.html#download-in-epub">Get the EPUB and PDF</a>
|
||
</p>
|
||
|
||
|
||
<div id="content"
|
||
<p>This is a short guide to TCP/IP and UDP/IP client/server programming in Common
|
||
Lisp using <a href="https://github.com/usocket/usocket">usockets</a>.</p>
|
||
|
||
<h2 id="tcpip">TCP/IP</h2>
|
||
|
||
<p>As usual, we will use quicklisp to load usocket.</p>
|
||
|
||
<pre><code>(ql:quickload "usocket")
|
||
</code></pre>
|
||
|
||
<p>Now we need to create a server. There are 2 primary functions that we need
|
||
to call. <code>usocket:socket-listen</code> and <code>usocket:socket-accept</code>.</p>
|
||
|
||
<p><code>usocket:socket-listen</code> 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 <code>usocket:socket-accept</code> 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.</p>
|
||
|
||
<p>So, what were the problems I faced due to my mistakes?</p>
|
||
|
||
<p>Mistake 1 - My initial understanding was that <code>socket-accept</code> 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? <code>(describe connection)</code> is your friend!</p>
|
||
|
||
<p>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.</p>
|
||
|
||
<p>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!</p>
|
||
|
||
<pre><code class="language-lisp">(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)))))
|
||
</code></pre>
|
||
|
||
<p>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.</p>
|
||
|
||
<pre><code class="language-lisp">(defun create-client (port)
|
||
(usocket:with-client-socket (socket stream "127.0.0.1" port
|
||
:element-type 'character)
|
||
(unwind-protect
|
||
(progn
|
||
(usocket:wait-for-input socket)
|
||
(format t "Input is: ~a~%" (read-line stream)))
|
||
(usocket:socket-close socket))))
|
||
</code></pre>
|
||
|
||
<p>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.</p>
|
||
|
||
<pre><code>(create-server 12321)
|
||
</code></pre>
|
||
|
||
<p>Now you are ready to run the client on the second REPL</p>
|
||
|
||
<pre><code>(create-client 12321)
|
||
</code></pre>
|
||
|
||
<p>Voilà! You should see “Hello World” on the second REPL.</p>
|
||
|
||
<h2 id="udpip">UDP/IP</h2>
|
||
|
||
<p>As a protocol, UDP is connection-less, and therefore there is no
|
||
concept of binding and accepting a connection. Instead we only do a
|
||
<code>socket-connect</code> but pass a specific set of parameters to make sure that
|
||
we create an UDP socket that’s waiting for data on a particular port.</p>
|
||
|
||
<p>So, what were the problems I faced due to my mistakes?
|
||
Mistake 1 - Unlike TCP, you don’t pass host and port to <code>socket-connect</code>.
|
||
If you do that, then you are indicating that you want to send a packet.
|
||
Instead, you pass <code>nil</code> but you set <code>:local-host</code> and <code>:local-port</code> to the address
|
||
and port that you want to receive data on. This part took some time to
|
||
figure out, because the documentation didn’t cover it. Instead reading
|
||
a bit of code from
|
||
<a href="https://code.google.com/p/blackthorn-engine-3d/source/browse/src/examples/usocket/usocket.lisp">blackthorn-engine-3d</a> helped a lot.</p>
|
||
|
||
<p>Also, since UDP is connectionless, anyone can send data to it at any
|
||
time. So, we need to know which host/port did we get data from so
|
||
that we can respond on it. So we bind multiple values to <code>socket-receive</code>
|
||
and use those values to send back data to our peer “client”.</p>
|
||
|
||
<pre><code class="language-lisp">(defun create-server (port buffer)
|
||
(let* ((socket (usocket:socket-connect nil nil
|
||
:protocol :datagram
|
||
:element-type '(unsigned-byte 8)
|
||
:local-host "127.0.0.1"
|
||
:local-port port)))
|
||
(unwind-protect
|
||
(multiple-value-bind (buffer size client receive-port)
|
||
(usocket:socket-receive socket buffer 8)
|
||
(format t "~A~%" buffer)
|
||
(usocket:socket-send socket (reverse buffer) size
|
||
:port receive-port
|
||
:host client))
|
||
(usocket:socket-close socket))))
|
||
</code></pre>
|
||
|
||
<p>Now for the sender/receiver. This part is pretty easy. Create a socket,
|
||
send data on it and receive data back.</p>
|
||
|
||
<pre><code class="language-lisp">(defun create-client (port buffer)
|
||
(let ((socket (usocket:socket-connect "127.0.0.1" port
|
||
:protocol :datagram
|
||
:element-type '(unsigned-byte 8))))
|
||
(unwind-protect
|
||
(progn
|
||
(format t "Sending data~%")
|
||
(replace buffer #(1 2 3 4 5 6 7 8))
|
||
(format t "Receiving data~%")
|
||
(usocket:socket-send socket buffer 8)
|
||
(usocket:socket-receive socket buffer 8)
|
||
(format t "~A~%" buffer))
|
||
(usocket:socket-close socket))))
|
||
</code></pre>
|
||
|
||
<p>So, how do you run this? You need again two REPLs, one for the server
|
||
and one for the client. Load this file in both REPLs. Create the
|
||
server in the first REPL.</p>
|
||
|
||
<pre><code>(create-server 12321 (make-array 8 :element-type '(unsigned-byte 8)))
|
||
</code></pre>
|
||
|
||
<p>Now you are ready to run the client on the second REPL</p>
|
||
|
||
<pre><code>(create-client 12321 (make-array 8 :element-type '(unsigned-byte 8)))
|
||
</code></pre>
|
||
|
||
<p>Voilà! You should see a vector <code>#(1 2 3 4 5 6 7 8)</code> on the first REPL
|
||
and <code>#(8 7 6 5 4 3 2 1)</code> on the second one.</p>
|
||
|
||
<h2 id="credit">Credit</h2>
|
||
|
||
<p>This guide originally comes from <a href="https://gist.github.com/shortsightedsid/71cf34282dfae0dd2528">shortsightedsid</a></p>
|
||
|
||
|
||
<p class="page-source">
|
||
Page source: <a href="https://github.com/LispCookbook/cl-cookbook/blob/master/sockets.md">sockets.md</a>
|
||
</p>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
|
||
// Don't write the TOC on the index.
|
||
if (window.location.pathname != "/cl-cookbook/") {
|
||
$("#toc").toc({
|
||
content: "#content", // will ignore the first h1 with the site+page title.
|
||
headings: "h1,h2,h3,h4"});
|
||
}
|
||
|
||
$("#two-cols + ul").css({
|
||
"column-count": "2",
|
||
});
|
||
$("#contributors + ul").css({
|
||
"column-count": "4",
|
||
});
|
||
</script>
|
||
|
||
|
||
|
||
<div>
|
||
<footer class="footer">
|
||
<hr/>
|
||
© 2002–2023 the Common Lisp Cookbook Project
|
||
<div>
|
||
📹 Discover <a style="color: darkgrey; text-decoration: underline", href="https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358">our contributor's Common Lisp video course on Udemy</a>
|
||
</div>
|
||
</footer>
|
||
|
||
</div>
|
||
<div id="toc-btn">T<br>O<br>C</div>
|
||
</div>
|
||
|
||
<script text="javascript">
|
||
HighlightLisp.highlight_auto({className: null});
|
||
</script>
|
||
|
||
<script type="text/javascript">
|
||
function duckSearch() {
|
||
var searchField = document.getElementById("searchField");
|
||
if (searchField && searchField.value) {
|
||
var query = escape("site:lispcookbook.github.io/cl-cookbook/ " + searchField.value);
|
||
window.location.href = "https://duckduckgo.com/?kj=b2&kf=-1&ko=1&q=" + query;
|
||
// https://duckduckgo.com/params
|
||
// kj=b2: blue header in results page
|
||
// kf=-1: no favicons
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<script async defer data-domain="lispcookbook.github.io/cl-cookbook" src="https://plausible.io/js/plausible.js"></script>
|
||
|
||
</body>
|
||
</html>
|