-
Notifications
You must be signed in to change notification settings - Fork 41
/
node.go
272 lines (235 loc) · 6.88 KB
/
node.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package main
/**
This project will implement a Peer-to-Peer command-line chat in Go language.
@March 2013
@by Morteza Shahriari Nia @mshahriarinia
Reading arbitrary strings from command-line was a bit trickey as I couldn't get a straight-forward example
on telling how to do it. But after visiting tens of pages and blogs it was fixed. Buffers, buffered reader,
streams, ... The diffference between when you learn womething and when you actually do it.
Multi-threading communciation via channels is very useful but not in our context. We need Mutex
for handling our clients which is not straightforward or natural in channels and message streams.
*/
import (
"bufio"
"fmt"
"math/rand"
"net"
"os"
"runtime"
"strconv"
"strings"
// "strings"
"container/list"
"sync"
"time"
)
var (
port string
SERVER_IP = "localhost" //TODO fix server ip
SERVER_PORT string = "5555" //default port as the main p2p server
stop = false
mutexClientList sync.Mutex
CONTROL_MESSAGE_PREAMBLE = "\u001B" + ":!q " //char code used in VIM to exit the program
)
func main() {
t := time.Now()
fmt.Println(t.Format("StampMilli"))
//initialize values
reader := bufio.NewReader(os.Stdin) //read line from standard input
connList := list.New() //list of p2p chat users.
fmt.Println("\n\n Welcome to Peer-to-Peer (P2P) Command-Line Chat in Go language.\n\n")
fmt.Print("Run this node as main server? (y/n) ")
str, err := reader.ReadString('\n') //ignore the error by sending it to nil
if err != nil {
fmt.Println("Can not read from command line.")
os.Exit(1)
}
if []byte(str)[0] == 'y' {
fmt.Println("Node is the main p2p server.")
port = SERVER_PORT
} else if []byte(str)[0] == 'n' {
fmt.Println("Node is a normal p2p node.")
port = generatePortNo()
} else {
fmt.Println("Wrong argument type.")
os.Exit(1)
}
fmt.Println("Server Socket: " + SERVER_IP + ":" + SERVER_PORT)
localIp := getLocalIP()
fmt.Println("Local Socket: " + localIp[0] + ":" + port)
fmt.Println("---------------------------------------------------------")
go acceptClients(port, connList)
go chatSay(connList)
if []byte(str)[0] == 'n' {
connectToNode(SERVER_IP+":"+SERVER_PORT, connList)
}
runtime.Gosched() //let the new thread to start, otherwuse it will not execute.
//it's good to not include accepting new clients from main just in case the user
//wants to quit by typing some keywords, the main thread is not stuck at
// net.listen.accept forever
for !stop {
time.Sleep(1000 * time.Millisecond)
} //keep main thread alive
}
/**
ask for a connection from a node
*/
func connectToNode(ipport string, connList *list.List) {
mutexClientList.Lock()
conn, err := net.Dial("tcp", ipport)
if err != nil {
fmt.Println("Error connecting to:", ipport, err.Error())
return
}
connList.PushBack(conn)
mutexClientList.Unlock()
printlist(connList)
go handleClient(conn, connList)
runtime.Gosched()
}
//TODO maintain list of all nodes and send to everybody
//read access to channel list
//close the connection
func chatSay(connList *list.List) {
reader := bufio.NewReader(os.Stdin) //get teh reader to read lines from standard input
//conn, err := net.Dial("tcp", serverIP+":"+SERVER_PORT)
for !stop { //keep reading inputs forever
fmt.Print("user@Home[\\ ")
str, _ := reader.ReadString('\n')
mutexClientList.Lock()
for e := connList.Front(); e != nil; e = e.Next() {
conn := e.Value.(*net.TCPConn)
_, err := conn.Write([]byte(str)) //transmit string as byte array
if err != nil {
fmt.Println("Error sending reply:", err.Error())
}
}
mutexClientList.Unlock()
}
}
//TODO close connections
//TODO forward new ip:port to other nodes
//TODO at first get list of clients. be ready to get a new client any time
/**
Accept new clients.
*/
func acceptClients(port string, connList *list.List) {
//fmt.Println("Listenning to port", port)
ln, err := net.Listen("tcp", ":"+port)
if err != nil {
fmt.Println("Error listenning to port ", port)
stop = true
}
for !stop {
conn, err := ln.Accept()
if err != nil {
fmt.Println("Error in accepting connection.")
stop = true
continue
}
go handleClient(conn, connList)
runtime.Gosched()
}
}
/**
Receive message from client.
Listen and wait for content from client. the write to
client will be performed when the current user enters an input
*/
func handleClient(conn net.Conn, connList *list.List) {
fmt.Println("New node: ", conn.RemoteAddr())
stopConn := false
mutexClientList.Lock()
connList.PushBack(conn)
mutexClientList.Unlock()
printlist(connList)
//send current node list (when acting as connection server)
str := connListToStr(connList)
_, err := conn.Write([]byte(CONTROL_MESSAGE_PREAMBLE + str)) //transmit string as byte array
if err != nil {
fmt.Println("Error sending reply:", err.Error())
}
//
buffer := make([]byte, 1024)
for !stopConn {
bytesRead, err := conn.Read(buffer)
if err != nil {
stopConn = true
fmt.Println("Error in reading from connection", conn.RemoteAddr())
mutexClientList.Lock()
el := getListElement(conn, connList)
if el != nil {
connList.Remove(el)
}
mutexClientList.Unlock()
} else {
input := string(buffer[0:bytesRead])
fmt.Println(conn.RemoteAddr(), " says: ", input)
if strings.Contains(input, CONTROL_MESSAGE_PREAMBLE) {
strArr := strings.Split(input, " ")
for _, ipport := range strArr {
if !strings.Contains(ipport,
strings.Trim(CONTROL_MESSAGE_PREAMBLE, " ")) ||
strings.Contains(ipport, conn.LocalAddr().String()) { //skip preamble
connectToNode(ipport, connList)
}
}
}
}
}
fmt.Println("Closing ", conn.RemoteAddr())
conn.Close()
}
func getListElement(conn net.Conn, l *list.List) *list.Element {
for e := l.Front(); e != nil; e = e.Next() {
temp := e.Value.(*net.TCPConn)
if conn.RemoteAddr() == temp.RemoteAddr() {
//fmt.Println("found connection.")
return e
}
}
return nil
}
/**
Generate a port number
*/
func generatePortNo() string {
rand.Seed(time.Now().Unix())
return strconv.Itoa(rand.Intn(5000) + 5000) //generate a valid port
}
/**
Determine the local IP addresses
*/
func getLocalIP() []string {
name, err := os.Hostname()
if err != nil {
fmt.Printf("Oops: %v\n", err)
return []string{}
}
fmt.Print("Local Hostname: " + name)
addrs, err := net.LookupHost(name)
if err != nil {
fmt.Printf("Oops: %v\n", err)
return []string{}
}
fmt.Print("\t\tLocal IP Addresses: ", addrs)
return addrs
}
func connListToStr(l *list.List) string {
if l == nil {
return ""
}
s := ""
mutexClientList.Lock()
for e := l.Front(); e != nil; e = e.Next() {
conn := e.Value.(*net.TCPConn)
s = s + conn.RemoteAddr().String() + " "
}
mutexClientList.Unlock()
return strings.Trim(s, " ")
}
func printlist(l *list.List) {
fmt.Print("\nConnection List: [")
fmt.Print(connListToStr(l))
fmt.Println("]")
}