Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions src/cat.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,62 @@
package main

import (
"bufio"
"io"
"log"
"os"
)

// ctrlDReader wraps a reader and detects Ctrl+D (ASCII 4) as EOT/EOF
type ctrlDReader struct {
r io.Reader
}

func (c *ctrlDReader) Read(p []byte) (n int, err error) {
n, err = c.r.Read(p)

// Check for Ctrl+D (ASCII 4)
for i := 0; i < n; i++ {
if p[i] == 4 {
// If we found Ctrl+D, return data up to that point and signal EOF
return i, io.EOF
}
}

return n, err
}

func main() {
stdout := os.Stdout

if len(os.Args) == 1 {
_, err := io.Copy(os.Stdout, os.Stdin)
if err != nil {
// Create a reader that detects Ctrl+D as EOF
stdin := &ctrlDReader{r: os.Stdin}
reader := bufio.NewReader(stdin)

// Copy stdin contents to stdout
_, err := io.Copy(stdout, reader)
if err != nil && err != io.EOF {
log.Fatal(err)
}
} else {
for _, fname := range os.Args[1:] {
// Open file in binary mode
fh, err := os.Open(fname)
if err != nil {
log.Fatal(err)
}

_, err = io.Copy(os.Stdout, fh)
if err != nil {
// Use buffered I/O for better performance
reader := bufio.NewReader(fh)

// Copy file contents preserving all bytes
_, err = io.Copy(stdout, reader)
if err != nil && err != io.EOF {
log.Fatal(err)
}

fh.Close()
}
}
}
79 changes: 79 additions & 0 deletions src/tcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

import (
"bufio"
"fmt"
"log"
"net"
"strings"
)

type Server struct {
host string
port string
}

type Client struct {
conn net.Conn
}

type Config struct {
Host string
Port string
}

func New(config *Config) *Server {
return &Server{
host: config.Host,
port: config.Port,
}
}

func (server *Server) Run() {
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", server.host, server.port))
if err != nil {
log.Fatal(err)
}
defer listener.Close()

for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}

client := &Client{
conn: conn,
}
go client.handleRequest()
}
}

func (client *Client) handleRequest() {
reader := bufio.NewReader(client.conn)
for {
message, err := reader.ReadString('\n')
if err != nil {
client.conn.Close()
return
}
message = strings.Trim(message, "\n\r")
fmt.Sprintf("Message incoming: %s\n", message)
client.conn.Write([]byte(fmt.Sprintf("Message received: %s\n", message)))
if message == "exit" {
fmt.Println("Exiting connection...")
client.conn.Close()
return
}
}
}

func main() {
server := New(&Config{
Host: "localhost",
Port: "8888",
})
fmt.Println("run...")
server.Run()
fmt.Println("...done")
}
28 changes: 28 additions & 0 deletions terminal_input_explanation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Why Enter is Required After Ctrl+D

The requirement to press Enter after Ctrl+D is due to how terminal input is handled at the operating system level, not in our Go code.

## Terminal Input Modes

In Unix-like systems (including Alpine Linux), terminals operate in "canonical mode" (cooked mode) by default where:
- Input is line-buffered by the terminal driver at the OS level
- Input isn't sent to the application until a line delimiter (Enter/CR/LF) is received
- Special characters like Ctrl+D are interpreted by the terminal driver

## The Actual Issue

When you press Ctrl+D in a terminal:
1. The terminal driver in canonical mode doesn't immediately send this to our application
2. It holds the input in its buffer until Enter is pressed
3. Only after Enter is pressed does our application receive the input, including the Ctrl+D character
4. Our ctrlDReader then detects the Ctrl+D and signals EOF

## Why bufio.Reader Doesn't Help

The bufio.Reader in our code provides buffering for data that has already been delivered to our application by the OS. It doesn't affect how or when the OS delivers that data to our application in the first place.

## Potential Solution

To make Ctrl+D work without requiring Enter, you would need to put the terminal in raw mode using terminal control libraries like `golang.org/x/term`. This would bypass the OS-level line buffering so every keystroke would be immediately sent to your application.

However, raw mode has other implications - it disables all terminal processing, including echo, so you'd need to handle that yourself if needed.