TCP echo server

This commit is contained in:
Alexander Heldt
2025-11-11 20:36:55 +01:00
parent 28b29d3dbf
commit a1971693ef
8 changed files with 191 additions and 50 deletions

View File

@@ -1,4 +1,6 @@
import gleam/bytes_tree
import gleam/bit_array
import gleam/erlang/atom
import gleam/result
import tcp/reason.{type Reason}
@@ -17,25 +19,82 @@ type ModeValue {
type TCPOption {
Active(Bool)
Mode(ModeValue)
Reuseaddr(Bool)
Ifaddr(Local)
ExitOnClose(Bool)
}
pub fn listen(socket_path: String) -> Result(Socket, Reason) {
let options = [
Mode(Binary),
Active(False),
Reuseaddr(True),
Ifaddr(Local(socket_path)),
ExitOnClose(False),
]
// port zero with `local` address
let port = 0
gen_tcp_listen(port, options)
}
pub fn accept(listen_socket: Socket) -> Result(Socket, Reason) {
gen_tcp_accept(listen_socket)
}
pub fn receive(socket: Socket, timeout: Int) -> Result(String, Reason) {
// Get all bytes
let length = 0
use bits <- result.try(gen_tcp_recv(socket, length, timeout))
case bits |> bit_array.to_string {
// TODO what error is best?
Error(_) -> Error(reason.Ebadmsg)
Ok(s) -> Ok(s)
}
}
pub fn connect(socket_path: String) -> Result(Socket, Reason) {
let options = [Mode(Binary), Active(False)]
// port zero with `local` address
let port = 0
// timeout in ms
let timeout = 1000
gen_tcp_connect(Local(socket_path), 0, options, timeout)
gen_tcp_connect(Local(socket_path), port, options, timeout)
}
pub fn send(socket: Socket, message: String) -> Result(Nil, Reason) {
gen_tcp_send(socket, bytes_tree.from_string(message))
gen_tcp_send(socket, bit_array.from_string(message))
}
pub fn close(socket: Socket) -> Nil {
gen_tcp_close(socket)
}
pub fn shutdown(socket: Socket) -> Result(Nil, Reason) {
let how = atom.create("write")
gen_tcp_shutdown(socket, how)
}
// https://www.erlang.org/doc/apps/kernel/gen_tcp.html#listen/2
@external(erlang, "gen_tcp", "listen")
fn gen_tcp_listen(port: Int, option: List(TCPOption)) -> Result(Socket, Reason)
// https://www.erlang.org/doc/apps/kernel/gen_tcp.html#accept/1
@external(erlang, "gen_tcp", "accept")
fn gen_tcp_accept(listen_socket: Socket) -> Result(Socket, Reason)
// https://www.erlang.org/doc/apps/kernel/gen_tcp.html#recv/3
@external(erlang, "gen_tcp", "recv")
fn gen_tcp_recv(
socket: Socket,
length: Int,
timeout: Int,
) -> Result(BitArray, Reason)
// https://www.erlang.org/doc/apps/kernel/gen_tcp.html#connect/4
@external(erlang, "gen_tcp", "connect")
fn gen_tcp_connect(
@@ -47,11 +106,12 @@ fn gen_tcp_connect(
// https://www.erlang.org/doc/apps/kernel/gen_tcp.html#send/2
@external(erlang, "tcp_ffi", "send")
fn gen_tcp_send(
socket: Socket,
packet: bytes_tree.BytesTree,
) -> Result(Nil, Reason)
fn gen_tcp_send(socket: Socket, packet: BitArray) -> Result(Nil, Reason)
// https://www.erlang.org/doc/apps/kernel/gen_tcp.html#close/1
@external(erlang, "gen_tcp", "close")
fn gen_tcp_close(socket: Socket) -> Nil
// https://www.erlang.org/doc/apps/kernel/gen_tcp.html#shutdown/2
@external(erlang, "tcp_ffi", "shutdown")
fn gen_tcp_shutdown(socket: Socket, how: atom.Atom) -> Result(Nil, Reason)