-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
150 lines (121 loc) · 3.37 KB
/
main.go
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package main
import (
"encoding/hex"
"fmt"
"log"
"math/rand"
"net"
)
type Message struct {
from string
payload []byte
}
type Server struct {
listenAddr string
listener net.Listener
quitChannel chan struct{}
messageChannel chan Message
peersMap map[string]string
}
func NewServer(listenAddr string) *Server {
return &Server{
listenAddr: listenAddr,
quitChannel: make(chan struct{}),
messageChannel: make(chan Message, 10),
peersMap: make(map[string]string),
}
}
func (server *Server) Start() error {
// Listen for incoming connections
listener, err := net.Listen("tcp", server.listenAddr)
if err != nil {
printError("listenAddr", err)
return err
}
// Cleanup after function return
defer listener.Close()
server.listener = listener
// Start accepting connections
go server.acceptLoop()
fmt.Println("Server is listening at: http://localhost:3000")
// Cleanup (after quit channel is closed)
// Why? Because if we stop server, other people still can read from it,
// now we close channel and notify users so they can also gracefuly handle the exit
<-server.quitChannel
return nil
}
func (server *Server) acceptLoop() {
for {
// Accept incoming connections if someone dials our server
conn, err := server.listener.Accept()
if err != nil {
printError("acceptLoop", err)
// We want to continue even after an error
continue
}
fmt.Println("New connection:", conn.RemoteAddr())
// Add new connection to our peers map
peerID := generateUniqueID()
server.peersMap[peerID] = conn.RemoteAddr().String()
// Each time we have a connection, we spin up a goroutine,
// so we can have millions of connection without any issues
go server.readLoop(conn)
}
}
func (server *Server) readLoop(conn net.Conn) {
// Cleanup after function return
defer conn.Close()
// Create a buffer to read client data
buffer := make([]byte, 2048)
// Spin up a loop to read bytes from new connections
for {
n, err := conn.Read(buffer)
if err != nil {
printError("readLoop", err)
continue
}
// Message is going to be amount we read from buffer, then we
// convert byte slice into string each time we receieve the message.
// We gonna write it into the channel and print it from there,
// instead of writing plain bytes to this channel
server.messageChannel <- Message{
from: conn.RemoteAddr().String(),
payload: buffer[:n],
}
// Implementation of 'ping-pong' (writing back to peer)
conn.Write([]byte("Thank you for your message!\n"))
// Display a map of all connected peers
server.showAllPeers()
}
}
func (server *Server) showAllPeers() {
fmt.Println("Peers connected:")
for peerID, conn := range server.peersMap {
fmt.Printf("ID: %s, Address: %s\n", peerID, conn)
}
}
func main() {
// Start the server
server := NewServer(":3000")
// A new goroutine to loop in the messageChannel and print messages
go func() {
for msg := range server.messageChannel {
fmt.Printf("Message from (%s):%s\n", msg.from, string(msg.payload))
}
}()
log.Fatal(server.Start())
}
// Helpers
func printError(variant string, err error) {
fmt.Printf("Error in %s: %v\n", variant, err)
}
func generateUniqueID() string {
randomBytes := make([]byte, 8)
_, err := rand.Read(randomBytes)
if err != nil {
printError("generateUniqueID", err)
return ""
}
uniqueID := hex.EncodeToString(randomBytes)
return uniqueID
}