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 }