# A Server With Nobody to Talk To

Canonical: <https://journeytocode.io/a-server-with-nobody-to-talk-to/>

Open the First TCP Connection in Rust

A multiplayer server has one awkward requirement before it can become multiplayer.

Someone has to connect.

We are going to start with two small Rust programs. The server waits on `127.0.0.1:4000`. The client connects, reads one welcome message and closes. By the end, two terminals will have spoken to each other through a real TCP connection.

No rooms. No players. Barely a conversation. But it crosses the wall.

This is a sequel in skills, not source code. We are starting a separate project instead of adding a server to Rustbound Idle. If you want to compare your result after building it, the [`article_1` branch](https://github.com/journey-to-code-org/one-world-many-players/tree/article_1) holds this lesson's tested checkpoint.

## Create the project

Create one Cargo package with two binaries:

```bash
cargo new one-world-many-players
cd one-world-many-players
mkdir src/bin
```


Remove `src/main.rs`, then create this shape:

```text
one-world-many-players/
├── Cargo.toml
└── src/
    └── bin/
        ├── client.rs
        └── server.rs
```


Cargo treats every file in `src/bin/` as a separate executable. We can now run the server and client without creating two projects.

## Listen for one connection

Put this in `src/bin/server.rs`:

**src/bin/server.rs**

```rust
use std::io::{self, Write};
use std::net::TcpListener;

fn main() -> io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:4000")?;
    println!("Server listening on 127.0.0.1:4000");

    let (mut stream, address) = listener.accept()?;
    println!("Connected: {address}");
    writeln!(stream, "Welcome to One World")?;

    Ok(())
}
```


`TcpListener::bind()` asks the operating system to reserve that address for our process. It can fail if another program already owns the port, so it returns a `Result`.

The `?` after the call means:

> If this worked, give us the listener. If it failed, return the error from `main()`.

That works because `main()` returns `io::Result<()>`. We are not hiding the failure. We are letting the function that can report it do exactly that.

`accept()` waits until a connection arrives. When one does, it gives us a `TcpStream` and the remote address. The stream is the connection. We write one newline-terminated message into it with `writeln!`.

## Connect from the other terminal

Put this in `src/bin/client.rs`:

**src/bin/client.rs**

```rust
use std::io::{self, BufRead, BufReader};
use std::net::TcpStream;

fn main() -> io::Result<()> {
    let stream = TcpStream::connect("127.0.0.1:4000")?;
    let mut reader = BufReader::new(stream);
    let mut message = String::new();

    reader.read_line(&mut message)?;
    print!("{message}");

    Ok(())
}
```


`TcpStream::connect()` reaches the address the server reserved. TCP gives us bytes, not messages, so the client wraps the stream in a `BufReader` and reads until the newline the server sent.

Start the server in one terminal:

```bash
cargo run --bin server
```


Leave it waiting. Open a second terminal in the same folder and run:

```bash
cargo run --bin client
```


The client prints the welcome line. The server prints the connection address, then both programs end.

**Question:** Why can we use `?` after `TcpListener::bind()` in this `main()` function?

1. `bind()` cannot fail on localhost
2. `main()` returns an I/O `Result`, so it can return the error
3. `?` turns every error into an empty listener

**Answer:** 2. `main()` returns an I/O `Result`, so it can return the error

> **Explanation:** The question-mark operator returns the error from the current function, so that function needs a compatible `Result` return type.


## What we learned

- one Cargo package can contain several binaries
- `TcpListener` waits for incoming connections
- `TcpStream` represents one TCP connection
- `BufReader` can read a newline-terminated message from a byte stream
- `?` passes an error to a function that already knows how to return it

Our connection works once.

That is also exactly how many messages it can carry.

**One connection. Shared world.**
[Join the discussion on the canonical article](https://journeytocode.io/a-server-with-nobody-to-talk-to/#ob-discussion)
