Tidy remote_client_connection.go and resolve lint warnings (#522)

* Added automap.go stub

* Fixed bad error handling

* Handle header error from io.Copy()

* d2client.ClientConnection renamed to ServerConnection

Having two interfaces named ClientConnection in adjacent packages was a point of confusion for me. Renamed the client ClientConnection to ServerConnection since clients connect to servers and servers to clients.

* Fix lint warnings in remote_client_connection.go

* Tidying and lint warnings in remote_client_connection.go

* Switch statement in remote_client_connection.go now has matching blocks.

* RemoteClientConnection.serverListener refactor.

The switch statement is still repetitive but now it's separate. I think we would need generics to make it smaller.

* Receiver name change from l to r.

* Comments and other adjustments.

* Old commits

* Fixed bad error handling

* Cleaned up remote_client_connection.go

* SendPacketToClient removed.
This commit is contained in:
danhale-git 2020-07-02 21:29:28 +01:00 committed by GitHub
parent 9c2b1dccaf
commit 6367929688
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 184 additions and 143 deletions

View File

@ -2,8 +2,6 @@
package d2localclient
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
@ -52,8 +50,10 @@ func Create(openNetworkServer bool) *LocalClientConnection {
func (l *LocalClientConnection) Open(_ string, saveFilePath string) error {
l.SetPlayerState(d2player.LoadPlayerState(saveFilePath))
d2server.Create(l.openNetworkServer)
go d2server.Run()
d2server.OnClientConnected(l)
return nil
}
@ -61,10 +61,12 @@ func (l *LocalClientConnection) Open(_ string, saveFilePath string) error {
func (l *LocalClientConnection) Close() error {
err := l.SendPacketToServer(d2netpacket.CreateServerClosedPacket())
if err != nil {
log.Printf("LocalClientConnection: error sending ServerClosedPacket to server: %s", err)
return err
}
d2server.OnClientDisconnected(l)
d2server.Destroy()
return nil
}

View File

@ -5,17 +5,16 @@ import (
"bytes"
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
@ -26,32 +25,16 @@ import (
// for a remote client.
type RemoteClientConnection struct {
clientListener d2networking.ClientListener // The GameClient
uniqueId string // Unique ID generated on construction
uniqueID string // Unique ID generated on construction
udpConnection *net.UDPConn // UDP connection to the server
active bool // The connection is currently open
}
// GetUniqueId returns RemoteClientConnection.uniqueId.
func (l RemoteClientConnection) GetUniqueId() string {
return l.uniqueId
}
// GetConnectionType returns an enum representing the connection type.
// See: d2clientconnectiontype
func (l RemoteClientConnection) GetConnectionType() d2clientconnectiontype.ClientConnectionType {
return d2clientconnectiontype.LANClient
}
// SendPacketToClient passes a packet to the game client for processing.
func (l *RemoteClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) error {
return l.clientListener.OnPacketReceived(packet)
}
// Create constructs a new RemoteClientConnection
// and returns a pointer to it.
func Create() *RemoteClientConnection {
result := &RemoteClientConnection{
uniqueId: uuid.NewV4().String(),
uniqueID: uuid.NewV4().String(),
}
return result
@ -59,7 +42,7 @@ func Create() *RemoteClientConnection {
// Open runs serverListener() in a goroutine to continuously read UDP packets.
// It also sends a PlayerConnectionRequestPacket packet to the server (see d2netpacket).
func (l *RemoteClientConnection) Open(connectionString string, saveFilePath string) error {
func (r *RemoteClientConnection) Open(connectionString, saveFilePath string) error {
if !strings.Contains(connectionString, ":") {
connectionString += ":6669"
}
@ -72,18 +55,20 @@ func (l *RemoteClientConnection) Open(connectionString string, saveFilePath stri
return err
}
l.udpConnection, err = net.DialUDP("udp", nil, udpAddress)
r.udpConnection, err = net.DialUDP("udp", nil, udpAddress)
// TODO: Show connection error screen if connection fails
if err != nil {
return err
}
l.active = true
go l.serverListener()
r.active = true
go r.serverListener()
log.Printf("Connected to server at %s", r.udpConnection.RemoteAddr().String())
log.Printf("Connected to server at %s", l.udpConnection.RemoteAddr().String())
gameState := d2player.LoadPlayerState(saveFilePath)
err = l.SendPacketToServer(d2netpacket.CreatePlayerConnectionRequestPacket(l.GetUniqueId(), gameState))
err = r.SendPacketToServer(d2netpacket.CreatePlayerConnectionRequestPacket(r.GetUniqueID(), gameState))
if err != nil {
log.Print("RemoteClientConnection: error sending PlayerConnectionRequestPacket to server.")
return err
@ -94,9 +79,10 @@ func (l *RemoteClientConnection) Open(connectionString string, saveFilePath stri
// Close informs the server that this client has disconnected and sets
// RemoteClientConnection.active to false.
func (l *RemoteClientConnection) Close() error {
l.active = false
err := l.SendPacketToServer(d2netpacket.CreatePlayerDisconnectRequestPacket(l.GetUniqueId()))
func (r *RemoteClientConnection) Close() error {
r.active = false
err := r.SendPacketToServer(d2netpacket.CreatePlayerDisconnectRequestPacket(r.GetUniqueID()))
if err != nil {
return err
}
@ -104,139 +90,186 @@ func (l *RemoteClientConnection) Close() error {
return nil
}
// GetUniqueID returns RemoteClientConnection.uniqueID.
func (r RemoteClientConnection) GetUniqueID() string {
return r.uniqueID
}
// GetConnectionType returns an enum representing the connection type.
// See: d2clientconnectiontype
func (r RemoteClientConnection) GetConnectionType() d2clientconnectiontype.ClientConnectionType {
return d2clientconnectiontype.LANClient
}
// SetClientListener sets RemoteClientConnection.clientListener to the given value.
func (r *RemoteClientConnection) SetClientListener(listener d2networking.ClientListener) {
r.clientListener = listener
}
// SendPacketToServer compresses the JSON encoding of a NetPacket and
// sends it to the server.
func (l *RemoteClientConnection) SendPacketToServer(packet d2netpacket.NetPacket) error {
func (r *RemoteClientConnection) SendPacketToServer(packet d2netpacket.NetPacket) error {
data, err := json.Marshal(packet.PacketData)
if err != nil {
return err
}
var buff bytes.Buffer
buff.WriteByte(byte(packet.PacketType))
writer, _ := gzip.NewWriterLevel(&buff, gzip.BestCompression)
if written, err := writer.Write(data); err != nil {
var written int
if written, err = writer.Write(data); err != nil {
return err
} else if written == 0 {
return errors.New(fmt.Sprintf("RemoteClientConnection: attempted to send empty %v packet body.", packet.PacketType))
return fmt.Errorf("remoteClientConnection: attempted to send empty %v packet body", packet.PacketType)
}
if err = writer.Close(); err != nil {
return err
}
if _, err = l.udpConnection.Write(buff.Bytes()); err != nil {
return err
}
return nil
}
// SetClientListener sets RemoteClientConnection.clientListener to the given value.
func (l *RemoteClientConnection) SetClientListener(listener d2networking.ClientListener) {
l.clientListener = listener
if err := writer.Close(); err != nil {
return err
}
if _, err = r.udpConnection.Write(buff.Bytes()); err != nil {
return err
}
return nil
}
// serverListener runs a while loop, reading from the GameServer's UDP
// connection.
func (l *RemoteClientConnection) serverListener() {
func (r *RemoteClientConnection) serverListener() {
buffer := make([]byte, 4096)
for l.active {
n, _, err := l.udpConnection.ReadFromUDP(buffer)
for r.active {
n, _, err := r.udpConnection.ReadFromUDP(buffer)
if err != nil {
fmt.Printf("Socket error: %s\n", err)
continue
}
if n <= 0 {
continue
}
buff := bytes.NewBuffer(buffer)
packetTypeId, err := buff.ReadByte()
packetType := d2netpackettype.NetPacketType(packetTypeId)
reader, err := gzip.NewReader(buff)
sb := new(strings.Builder)
written, err := io.Copy(sb, reader)
data, packetType, err := r.bytesToJSON(buffer)
if err != nil {
log.Printf("RemoteClientConnection: error copying bytes from %v packet: %s", packetType, err)
// TODO: All packets coming from the client seem to be throwing an error
//continue
}
if written == 0 {
log.Printf("RemoteClientConnection: empty packet %v packet received", packetType)
continue
log.Println(packetType, err)
}
stringData := sb.String()
switch packetType {
case d2netpackettype.GenerateMap:
var packet d2netpacket.GenerateMapPacket
err := json.Unmarshal([]byte(stringData), &packet)
if err != nil {
log.Printf("GameServer: error unmarshalling %T: %s", packet, err)
continue
}
err = l.SendPacketToClient(d2netpacket.NetPacket{
PacketType: packetType,
PacketData: packet,
})
if err != nil {
log.Printf("RemoteClientConnection: error processing packet %v: %s", packetType, err)
}
case d2netpackettype.MovePlayer:
var packet d2netpacket.MovePlayerPacket
err := json.Unmarshal([]byte(stringData), &packet)
if err != nil {
log.Printf("GameServer: error unmarshalling %T: %s", packet, err)
continue
}
err = l.SendPacketToClient(d2netpacket.NetPacket{
PacketType: packetType,
PacketData: packet,
})
if err != nil {
log.Printf("RemoteClientConnection: error processing packet %v: %s", packetType, err)
}
case d2netpackettype.UpdateServerInfo:
var packet d2netpacket.UpdateServerInfoPacket
err := json.Unmarshal([]byte(stringData), &packet)
if err != nil {
log.Printf("GameServer: error unmarshalling %T: %s", packet, err)
continue
}
err = l.SendPacketToClient(d2netpacket.NetPacket{
PacketType: packetType,
PacketData: packet,
})
if err != nil {
log.Printf("RemoteClientConnection: error processing packet %v: %s", packetType, err)
}
case d2netpackettype.AddPlayer:
var packet d2netpacket.AddPlayerPacket
err := json.Unmarshal([]byte(stringData), &packet)
if err != nil {
log.Printf("GameServer: error unmarshalling %T: %s", packet, err)
continue
}
err = l.SendPacketToClient(d2netpacket.NetPacket{
PacketType: packetType,
PacketData: packet,
})
if err != nil {
log.Printf("RemoteClientConnection: error processing packet %v: %s", packetType, err)
}
case d2netpackettype.Ping:
err := l.SendPacketToServer(d2netpacket.CreatePongPacket(l.uniqueId))
if err != nil {
log.Printf("RemoteClientConnection: error responding to server ping: %s", err)
}
case d2netpackettype.PlayerDisconnectionNotification:
var packet d2netpacket.PlayerDisconnectRequestPacket
err := json.Unmarshal([]byte(stringData), &packet)
if err != nil {
log.Printf("GameServer: error unmarshalling %T: %s", packet, err)
continue
}
log.Printf("Received disconnect: %s", packet.Id)
default:
fmt.Printf("Unknown packet type %d\n", packetType)
packet, err := r.decodeToPacket(packetType, data)
if err != nil {
log.Println(packetType, err)
}
err = r.clientListener.OnPacketReceived(packet)
if err != nil {
log.Println(packetType, err)
}
}
}
// bytesToJSON reads the packet type, decompresses the packet and returns a JSON string.
func (r *RemoteClientConnection) bytesToJSON(buffer []byte) (string, d2netpackettype.NetPacketType, error) {
buff := bytes.NewBuffer(buffer)
packetTypeID, err := buff.ReadByte()
if err != nil {
// The packet type here will be UpdateServerInfo. That shouldn't matter
// but perhaps we should have a 'None' packet type anyway.
return "", d2netpackettype.NetPacketType(0), fmt.Errorf("error reading packet type: %s", err)
}
packetType := d2netpackettype.NetPacketType(packetTypeID)
reader, err := gzip.NewReader(buff)
if err != nil {
return "", packetType, fmt.Errorf("error creating reader for %v packet: %s", packetType, err)
}
sb := new(strings.Builder)
// This will throw errors where packets are not compressed. This doesn't
// break anything, so the gzip.ErrHeader error is currently ignored to
// avoid noisy logging.
written, err := io.Copy(sb, reader)
if err != nil && err != gzip.ErrHeader {
return "", packetType, fmt.Errorf("error copying bytes from %v packet: %s", packetType, err)
}
if written == 0 {
return "", packetType, fmt.Errorf("empty %v packet received", packetType)
}
return sb.String(), packetType, nil
}
// decodeToPacket unmarshals the JSON string into the correct struct
// and returns a NetPacket declaring that struct.
func (r *RemoteClientConnection) decodeToPacket(t d2netpackettype.NetPacketType, data string) (d2netpacket.NetPacket, error) {
var np = d2netpacket.NetPacket{}
var err error
switch t {
case d2netpackettype.GenerateMap:
var p d2netpacket.GenerateMapPacket
if err = json.Unmarshal([]byte(data), &p); err != nil {
break
}
np = d2netpacket.NetPacket{PacketType: t, PacketData: p}
case d2netpackettype.MovePlayer:
var p d2netpacket.MovePlayerPacket
if err = json.Unmarshal([]byte(data), &p); err != nil {
break
}
np = d2netpacket.NetPacket{PacketType: t, PacketData: p}
case d2netpackettype.UpdateServerInfo:
var p d2netpacket.UpdateServerInfoPacket
if err = json.Unmarshal([]byte(data), &p); err != nil {
break
}
np = d2netpacket.NetPacket{PacketType: t, PacketData: p}
case d2netpackettype.AddPlayer:
var p d2netpacket.AddPlayerPacket
if err = json.Unmarshal([]byte(data), &p); err != nil {
break
}
np = d2netpacket.NetPacket{PacketType: t, PacketData: p}
case d2netpackettype.Ping:
var p d2netpacket.PingPacket
if err = json.Unmarshal([]byte(data), &p); err != nil {
break
}
np = d2netpacket.NetPacket{PacketType: t, PacketData: p}
case d2netpackettype.PlayerDisconnectionNotification:
var p d2netpacket.PlayerDisconnectRequestPacket
if err = json.Unmarshal([]byte(data), &p); err != nil {
break
}
np = d2netpacket.NetPacket{PacketType: t, PacketData: p}
default:
err = fmt.Errorf("RemoteClientConnection: unrecognized packet type: %v", t)
}
if err != nil {
return np, err
}
return np, nil
}

View File

@ -1,7 +1,7 @@
// Package d2client provides client side connection, map and entity
// management.
/*
GameClient declares the ClientConnection interface to interact with
GameClient declares the ServerConnection interface to interact with
local and remote servers. LocalClientConnection is the host and
creates the game server (see d2server).*/
package d2client

View File

@ -25,7 +25,7 @@ import (
// GameClient manages a connection to d2server.GameServer
// and keeps a synchronised copy of the map and entities.
type GameClient struct {
clientConnection ClientConnection // Abstract local/remote connection
clientConnection ServerConnection // Abstract local/remote connection
connectionType d2clientconnectiontype.ClientConnectionType // Type of connection (local or remote)
GameState *d2player.PlayerState // local player state
MapEngine *d2mapengine.MapEngine // Map and entities
@ -152,7 +152,9 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
if err != nil {
log.Printf("GameClient: error responding to server ping: %s", err)
}
case d2netpackettype.PlayerDisconnectionNotification:
// Not implemented
log.Printf("RemoteClientConnection: received disconnect: %s", packet.PacketData)
case d2netpackettype.ServerClosed:
// TODO: Need to be tied into a character save and exit
log.Print("Server has been closed")

View File

@ -5,9 +5,9 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
)
// ClientConnection is an interface for abstracting local and
// ServerConnection is an interface for abstracting local and
// remote server connections.
type ClientConnection interface {
type ServerConnection interface {
Open(connectionString string, saveFilePath string) error
Close() error
SendPacketToServer(packet d2netpacket.NetPacket) error

View File

@ -110,10 +110,14 @@ func runNetworkServer() {
packetType := d2netpackettype.NetPacketType(packetTypeId)
reader, err := gzip.NewReader(buff)
sb := new(strings.Builder)
// This will throw errors where packets are not compressed. This doesn't
// break anything, so the gzip.ErrHeader error, is currently ignored to
// avoid noisy logging.
written, err := io.Copy(sb, reader)
if err != nil {
if err != nil && err != gzip.ErrHeader {
log.Printf("GameServer: error copying bytes from %v packet: %s", packetType, err)
continue
}
if written == 0 {
log.Printf("GameServer: empty packet %v packet received", packetType)