CSE224 - Go I/O

Read and Write binary files

Go follows the Reader/Writer interface model:

  1. io.Reader: Can read data (Read([]byte) (int, error))
  2. io.Writer: Can write data (Write([]byte) (int, error))

Most I/O types (files, TCP connections, buffers) implement these interfaces.

I/O types

1
2
3
4
file, _ := os.Open("hello.txt") // *os.File, used when read/write to files.
conn, _ := net.Dial("tcp", "example.com:80") // net.Conn,
r := strings.NewReader("hello world") // *strings.Reader
r := bufio.NewReader(strings.NewReader("hello\nworld")) // bufio.Reader, wraps any io.Reader to add buffering and helper methods like ReadLine(), ReadString('\n'), etc.

All above implements both io.Reader and io.Writer.

Read a binary file

Open a file:

1
2
3
4
5
file, err := os.Open("file.bin")
if err != nil {
log.Fatal(err)
}
defer file.Close()

Read specific number of bytes

1
2
3
4
5
buf := make([]byte, 8) // read 8 bytes
_, err := file.Read(buf)
if err != nil {
log.Fatal(err)
}
  • n, err := file.Read(buf): Read to a slice of bytes, returns actual number of bytes read, may read less than you ask for, even though there’s more data available later. It blocks until at least some bytes are ready to be read.
  • n, err := io.ReadFull(reader, buf): Reads exactly len(buf) bytes, blocking until buffer is full or EOF.
  • data, err := io.ReadAll(reader): Reads entire stream until EOF into memory (until I receive EOF).
  • data, err := os.ReadFile(filePath): os.ReadFile is a convenience function that: Opens a file, Reads all of its contents into memory, Closes the file, Returns the file contents as a []byte.

Read and convert

1
2
3
4
5
6
var num int32
err := binary.Read(file, binary.LittleEndian, &num)
if err != nil {
log.Fatal(err)
}
fmt.Println("Read int32:", num)
  • err := binary.Read(): A high-level function from encoding/binary, read and then convert

Write a binary file

Create a binary file

1
2
3
4
5
file, err := os.Create("data.bin")
if err != nil {
log.Fatal(err)
}
defer file.Close()
1
2
3
4
5
data := []byte("Hello, binary world!\n")
n, err := file.Write(data)
if err != nil {
log.Fatal(err)
}
  • n, err := writer.Write(data): Writes len(data) bytes, returns how many were actually written (should be all, or an error).
  • error := os.WriteFile(name string, data []byte, perm fs.FileMode): writes data to a file in one shot
1
2
3
4
5
var number uint32 = 123456
err = binary.Write(file, binary.LittleEndian, number)
if err != nil {
log.Fatal(err)
}

Communicate via network

When you call conn.Write(data) in Go (e.g., using a TCP connection), you’re not directly writing data onto the network wire. Instead, your data is copied into the OS’s send buffer. The OS then takes care of delivering that data over the network asynchronously.

When you call conn.Read(buffer), you’re trying to read data from the OS’s receive buffer. If no data is available yet, Read() will block (wait) until some data is available.

Client and Server

Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"fmt"
"net"
)

func main() {
// Start listening on TCP port 9000
listener, err := net.Listen("tcp", "127.0.0.1:9000")
if err != nil {
fmt.Println("Error starting server:", err)
return
}
defer listener.Close()

fmt.Println("Server is listening on 127.0.0.1:9000")

for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Failed to accept connection:", err)
continue
}
go handleConnection(conn) // Handle each connection concurrently
}
}

func handleConnection(conn net.Conn) {
defer conn.Close()

buffer := make([]byte, 1024) // Create a buffer to hold incoming data
n, err := conn.Read(buffer)
if err != nil {
fmt.Println("Failed to read data:", err)
return
}

message := string(buffer[:n])
fmt.Printf("Received message: %s\n", message)

// Send a response back
response := "Hello Client! I got your message: " + message
conn.Write([]byte(response))
}

Client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"
"net"
)

func main() {
// Connect to the server
conn, err := net.Dial("tcp", "127.0.0.1:9000")
if err != nil {
fmt.Println("Error connecting to server:", err)
return
}
defer conn.Close()

// Send a message
message := "Hello Server!"
_, err = conn.Write([]byte(message))
if err != nil {
fmt.Println("Failed to send message:", err)
return
}

// Read the response
buffer := make([]byte, 1024)
n, err := conn.Read(buffer)
if err != nil {
fmt.Println("Failed to read response:", err)
return
}

response := string(buffer[:n])
fmt.Printf("Server replied: %s\n", response)
}

CSE224 - Go I/O
https://thiefcat.github.io/2025/04/29/CSE224/go-io/
Author
小贼猫
Posted on
April 29, 2025
Licensed under