240 lines
5.2 KiB
Go
240 lines
5.2 KiB
Go
package client
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
)
|
|
|
|
const PORT = ":5518"
|
|
|
|
type PacketID int
|
|
|
|
const (
|
|
CONNECT_REQUEST PacketID = iota
|
|
CONNECT_RESPONSE
|
|
JOIN_REQUEST
|
|
JOIN_RESPONSE
|
|
CREATE_REQUEST
|
|
CREATE_RESPONSE
|
|
READ_DATA
|
|
SEND_DATA_REQUEST
|
|
SEND_DATA_RESPONSE
|
|
LEAVE_ROOM
|
|
)
|
|
|
|
// Client represents a client connection to the server.
|
|
type Client struct {
|
|
Conn net.Conn
|
|
ClientID string
|
|
RoomCode string
|
|
DataChannel chan string
|
|
}
|
|
|
|
// NewClient creates a new client and connects to the server.
|
|
func NewClient(serverAddr, clientID string) (*Client, error) {
|
|
conn, err := net.Dial("tcp", serverAddr+PORT)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to connect to server: %w", err)
|
|
}
|
|
return &Client{
|
|
Conn: conn,
|
|
ClientID: clientID,
|
|
DataChannel: make(chan string),
|
|
}, nil
|
|
}
|
|
|
|
// CreateRoom creates a new room on the server.
|
|
func (c *Client) CreateRoom(password string) (string, error) {
|
|
request := CreateRoomRequestPacket{
|
|
UserID: c.ClientID,
|
|
Password: password,
|
|
}
|
|
|
|
if err := c.sendPacket(CREATE_REQUEST, request); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var response CreateRoomResponsePacket
|
|
if err := c.receivePacket(CREATE_RESPONSE, &response); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if !response.Success {
|
|
return "", errors.New(response.Reason)
|
|
}
|
|
|
|
c.RoomCode = response.RoomCode
|
|
return response.RoomCode, nil
|
|
}
|
|
|
|
// JoinRoom joins an existing room on the server.
|
|
func (c *Client) JoinRoom(roomCode, password string) error {
|
|
request := JoinRequestPacket{
|
|
UserID: c.ClientID,
|
|
RoomCode: roomCode,
|
|
Password: password,
|
|
}
|
|
|
|
if err := c.sendPacket(JOIN_REQUEST, request); err != nil {
|
|
return err
|
|
}
|
|
|
|
var response JoinRequestResponsePacket
|
|
if err := c.receivePacket(JOIN_RESPONSE, &response); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !response.Success {
|
|
return errors.New(response.Reason)
|
|
}
|
|
|
|
c.RoomCode = roomCode
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) ListenForData() {
|
|
go func() {
|
|
for {
|
|
var dataPacket SendDataRequestPacket
|
|
err := c.receivePacket(READ_DATA, &dataPacket)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
fmt.Println("Connection closed by the server")
|
|
close(c.DataChannel)
|
|
return
|
|
}
|
|
fmt.Printf("Error receiving data: %v\n", err)
|
|
close(c.DataChannel)
|
|
return
|
|
}
|
|
|
|
// Send the received data to the channel
|
|
c.DataChannel <- dataPacket.Data
|
|
}
|
|
}()
|
|
}
|
|
|
|
// SendData sends a message to all other clients in the room.
|
|
func (c *Client) SendData(data string) error {
|
|
request := SendDataRequestPacket{
|
|
UserID: c.ClientID,
|
|
Data: data,
|
|
}
|
|
|
|
if err := c.sendPacket(SEND_DATA_REQUEST, request); err != nil {
|
|
return fmt.Errorf("failed to send data packet: %w", err)
|
|
}
|
|
|
|
var response SendDataResponsePacket
|
|
if err := c.receivePacket(SEND_DATA_RESPONSE, &response); err != nil {
|
|
return fmt.Errorf("failed to receive response for sent data: %w", err)
|
|
}
|
|
|
|
if !response.Success {
|
|
return errors.New("server failed to process the data request: " + response.Reason)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LeaveRoom disconnects the client from the current room.
|
|
func (c *Client) LeaveRoom() error {
|
|
return c.sendPacket(LEAVE_ROOM, nil)
|
|
}
|
|
|
|
// Close closes the connection to the server.
|
|
func (c *Client) Close() {
|
|
c.Conn.Close()
|
|
}
|
|
|
|
// sendPacket sends a packet with a given ID and data to the server.
|
|
func (c *Client) sendPacket(packetID PacketID, data interface{}) error {
|
|
packetData, err := json.Marshal(data)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encode packet: %w", err)
|
|
}
|
|
return writePacket(c.Conn, byte(packetID), packetData)
|
|
}
|
|
|
|
// receivePacket reads a response from the server for a given packet ID.
|
|
func (c *Client) receivePacket(expected PacketID, v interface{}) error {
|
|
packetID, data, err := readPacket(c.Conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if packetID != expected {
|
|
return fmt.Errorf("unexpected packet ID: got %v, want %v", packetID, expected)
|
|
}
|
|
return json.Unmarshal(data, v)
|
|
}
|
|
|
|
// readPacket reads a packet from the connection.
|
|
func readPacket(conn net.Conn) (PacketID, []byte, error) {
|
|
var length uint32
|
|
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
var messageType byte
|
|
if err := binary.Read(conn, binary.BigEndian, &messageType); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
data := make([]byte, length-5)
|
|
if _, err := io.ReadFull(conn, data); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
return PacketID(messageType), data, nil
|
|
}
|
|
|
|
// writePacket writes a packet to the connection.
|
|
func writePacket(conn net.Conn, messageType byte, data []byte) error {
|
|
length := uint32(5 + len(data))
|
|
if err := binary.Write(conn, binary.BigEndian, length); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(conn, binary.BigEndian, messageType); err != nil {
|
|
return err
|
|
}
|
|
_, err := conn.Write(data)
|
|
return err
|
|
}
|
|
|
|
type CreateRoomRequestPacket struct {
|
|
UserID string
|
|
Password string
|
|
}
|
|
|
|
type CreateRoomResponsePacket struct {
|
|
Success bool
|
|
Reason string
|
|
RoomCode string
|
|
}
|
|
|
|
type JoinRequestPacket struct {
|
|
UserID string
|
|
RoomCode string
|
|
Password string
|
|
}
|
|
|
|
type JoinRequestResponsePacket struct {
|
|
Success bool
|
|
Reason string
|
|
CurrentData string
|
|
}
|
|
|
|
type SendDataRequestPacket struct {
|
|
UserID string
|
|
Data string
|
|
}
|
|
|
|
type SendDataResponsePacket struct {
|
|
Success bool
|
|
Reason string
|
|
}
|