package main
import (
"bufio"
//"path/filepath"
"fmt"
//"io"
"log"
"net"
"os"
"strconv"
"strings"
"sync"
"time"
)
const (
HOST = "localhost"
TYPE = "tcp"
MaxNumConns = 10
BUFFERSIZE = 1024
)
var (
linuxLogo = `
_nnnn_
dGGGGMMb
@p~qp~~qMb
M|@||@) M|
@,----.JM|
JS^\__/ qKL
dZP qKRb
dZP qKKb
fZP SMMb
HZM MMMM
FqM MMMM
__| ". |\dS"qML
| ` + "`" + `. | ` + "`" + `' \Zq
_) \.___.,| .'
\____ )MMMMMP| .'
` + "`" + `-' ` + "`" + `--'
`
clients = make(map[net.Conn]struct{})
clientsMux sync.Mutex
clientNames = make(map[net.Conn]string)
wg sync.WaitGroup
PORT int = 2522
messages []string
messagesMux sync.Mutex
)
func main() {
if len(os.Args) > 1 {
PORT, _ = strconv.Atoi(os.Args[1])
if len(os.Args) > 2 {
fmt.Println("[USAGE]: ./TCPChat $port || go run . $port")
return
}
}
fmt.Println("listening on port", PORT)
file, err := os.Create("logfile.txt")
if err != nil {
fmt.Println("ERROR CREATIN FILE")
return
}
defer file.Close()
originalStdout := os.Stdout
os.Stdout = file
fmt.Println("Port: ", PORT)
listen, err := net.Listen(TYPE, HOST+":"+strconv.Itoa(PORT))
if err != nil {
log.Fatal(err)
os.Exit(1)
}
fmt.Println("listening on port", PORT)
// close listener
defer listen.Close()
for {
if len(clients) >= MaxNumConns {
fmt.Println("Max number of connections reached. Rejecting new connections...")
conn, err := listen.Accept()
if err != nil {
log.Println(err)
continue
}
conn.Close()
continue
}
conn, err := listen.Accept()
if err != nil {
log.Fatal(err)
os.Exit(1)
}
wg.Add(1)
go handleIncomingRequest(conn)
}
os.Stdout = originalStdout
}
/* func sendFileToClient(connection net.Conn) {
reader := bufio.NewReader(connection)
// Receive file path from client
srcFilePath, err := reader.ReadString('\n')
if err != nil {
log.Println(err)
return
}
srcFilePath = strings.TrimSpace(srcFilePath)
if srcFilePath == "" {
connection.Write([]byte("Please specify filepath\n"))
return
}
connection.Write([]byte(fmt.Sprintf("File transmission incoming, the file name is: %s. Type accept to accept file transmission \n", filepath.Base(srcFilePath))))
reader = bufio.NewReader(connection)
input, err := reader.ReadString('\n')
if err != nil {
log.Println(err)
return
}
input = strings.TrimSpace(input)
if input != "accept" {
connection.Write([]byte("You have declined the file transfer "))
return
}
originalFile, err := os.Open(srcFilePath)
if err != nil {
fmt.Println(err)
return
}
var copyName string
defer originalFile.Close()
if len(os.Args) == 1 {
connection.Write([]byte("\n You haven't specified the name of the file"))
return
}
// Create a new file to store the copy
copyFile, err := os.Create(copyName)
if err != nil {
fmt.Println(err)
return
}
defer copyFile.Close()
fileInfo, err := originalFile.Stat()
if err != nil {
fmt.Println(err)
return
}
fileSize := fillString(strconv.FormatInt(fileInfo.Size(), 10), 10)
fileName := fillString(fileInfo.Name(), 64)
fmt.Println("Sending filename and filesize!")
connection.Write([]byte(fileSize))
connection.Write([]byte(fileName))
sendBuffer := make([]byte, BUFFERSIZE)
fmt.Println("Start sending file!")
for {
n, err := originalFile.Read(sendBuffer)
if err == io.EOF {
break
}
if n > 0 {
connection.Write(sendBuffer[:n])
copyFile.Write(sendBuffer[:n]) // Write the bytes directly to the copy file
}
}
fmt.Println("File has been sent and copied, closing connection!")
} */
func handleIncomingRequest(conn net.Conn) {
defer func() {
clientsMux.Lock()
delete(clients, conn)
disconnectedName := clientNames[conn]
delete(clientNames, conn)
clientsMux.Unlock()
// Notify other clients about the disconnection
heDisconnected := fmt.Sprintf("\n%s has left the chat\n", disconnectedName)
notifyClients(heDisconnected, conn, false)
conn.Write([]byte(fmt.Sprintf("You have disconnected, press enter to close connection and return.")))
conn.Close()
fmt.Println("Client disconnected:", conn.RemoteAddr())
wg.Done()
}()
fmt.Println("Client connected:", conn.RemoteAddr())
// Add the client to the clients map
clientsMux.Lock()
clients[conn] = struct{}{}
clientsMux.Unlock()
// Send the initial response
response := fmt.Sprintf("Welcome to TCP-Chat!\n%s\n", linuxLogo)
conn.Write([]byte(response))
reader := bufio.NewReader(conn)
timeStr := time.Now().Format("Monday, 02-Jan-06 15:04:05 MST")
// Process the name
var name string
var enteredName = false
for !enteredName {
conn.Write([]byte("[Enter your name]: "))
// Read the user's input
input, err := reader.ReadString('\n')
if err != nil {
log.Println(err)
break
}
name = strings.TrimSpace(input)
if name == "" {
conn.Write([]byte("Your name cannot be empty\n"))
continue
}
if nameExists(name) {
conn.Write([]byte("Name already exists. Please choose a different name.\n"))
continue
}
// Respond to the client
conn.Write([]byte(fmt.Sprintf("Hello, %s!\n", name)))
// conn.Write([]byte("Press enter to continue to the chatroom\n"))
break
}
fmt.Println("Name:", name)
clientNames[conn] = name
enteredName = true
// Respond to the client
conn.Write([]byte(fmt.Sprintf("Hello, %s! You are now in the chatroom.\n", name)))
// conn.Write([]byte("Press enter to continue.\n"))
//defaultEntry := fmt.Sprintf("[%s][%s]: ", timeStr, name)
userJoin := fmt.Sprintf("\n User '%s' joined the chatroom.\n", name)
messagesMux.Lock()
for _, message := range messages {
if enteredName {
clearLines(conn, 1)
}
conn.Write([]byte(message))
}
messagesMux.Unlock()
// Notify other clients about the new user joining
notifyClients(userJoin, conn, true)
for {
timeStr = time.Now().Format("Monday, 02-Jan-06 15:04:05 MST")
ownDetails := fmt.Sprintf("[%s][%s]: ", timeStr, name)
conn.Write([]byte(ownDetails))
message, err := reader.ReadString('\n')
if err != nil {
log.Println(err)
break
}
// Process the received message
message = strings.TrimSpace(message)
if message == "exit" {
break
} /* else if message == "send" {
for client := range clients {
if client != conn {
sendFileToClient(client)
}
}
} */
if strings.HasPrefix(message, "change name") {
newName := strings.TrimSpace(strings.TrimPrefix(message, "change name"))
clientsMux.Lock()
oldName := clientNames[conn]
clientsMux.Unlock()
clientsMux.Lock()
clientNames[conn] = newName
clientsMux.Unlock()
conn.Write([]byte(fmt.Sprintf("You have changed your name to %s\n", newName)))
nameChangeMessage := fmt.Sprintf("\n%s changed his name to %s\n", oldName, newName)
notifyClients(nameChangeMessage, conn, false)
name = newName
continue
}
// Update the receiver's details
receiverTimeStr := time.Now().Format("Monday, 02-Jan-06 15:04:05 MST")
receiverName := name
messageWithDetails := fmt.Sprintf("\n[%s][%s]: %s\n", receiverTimeStr, receiverName, message)
// Broadcast the message to other clients
if message != "" {
messagesMux.Lock()
messages = append(messages, messageWithDetails)
messagesMux.Unlock()
notifyClients(messageWithDetails, conn, false)
fmt.Println("[" + timeStr + "]" + "[" + name + "]: " + message)
}
}
}
func nameExists(name string) bool {
for _, existingName := range clientNames {
if existingName == name {
return true
}
}
return false
}
func notifyClients(message string, sender net.Conn, entry bool) {
clientsMux.Lock()
defer clientsMux.Unlock()
for client := range clients {
if client != sender {
if !entry {
clearLines(client, 1)
}
clientTimeStr := time.Now().Format("Monday, 02-Jan-06 15:04:05 MST")
receiverName := clientNames[client]
if receiverName == "" {
return
}
// Construct the default entry with the client's time
defaultEntry := fmt.Sprintf("[%s][%s]: ", clientTimeStr, receiverName)
// Send the message and default entry to the client
client.Write([]byte(message))
client.Write([]byte(defaultEntry))
}
}
}
func clearLines(client net.Conn, lines int) {
for i := 0; i < lines; i++ {
if clientNames[client] != "" {
client.Write([]byte("\033[2K\033[1A"))
}
}
}
func fillString(retunString string, toLength int) string {
for {
lengtString := len(retunString)
if lengtString < toLength {
retunString = retunString + ":"
continue
}
break
}
return retunString
}