emacs.d/clones/lispcookbook.github.io/cl-cookbook/sockets.html
2022-08-04 11:37:48 +02:00

272 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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> &ndash; 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> &ndash; 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 href="https://www.udemy.com/course/common-lisp-programming/?couponCode=6926D599AA-LISP4ALL">NEW! Learn Lisp in videos and support our contributors with this 40% discount.</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. Thats where <code>usocket:socket-accept</code> comes in. Its 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?
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, its 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 thats 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 dont 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 didnt cover it. Instead reading
a bit of code from
https://code.google.com/p/blackthorn-engine-3d/source/browse/src/examples/usocket/usocket.lisp 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 https://gist.github.com/shortsightedsid/71cf34282dfae0dd2528</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/>
&copy; 2002&ndash;2021 the Common Lisp Cookbook Project
</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>