169 lines
3.5 KiB
Go
169 lines
3.5 KiB
Go
/*
|
|
* ============================================================================
|
|
* Projekt.....: rs2322tcp
|
|
* Datei.......: data_connection.go
|
|
* Copyright (C) 2026 Dieter Lang
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* Beschreibung:
|
|
* Bidirektionale Datenverbindung zwischen einer TCP-Verbindung und
|
|
* einem seriellen Gerätekanal.
|
|
* ============================================================================
|
|
*/
|
|
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
)
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// DataConnection
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// DataConnection connects one TCP connection with one serial device.
|
|
//
|
|
// Data is transferred bidirectionally:
|
|
//
|
|
// TCP -> Serial
|
|
// TCP <- Serial
|
|
//
|
|
// The serial side is represented by an io.ReadWriteCloser so that this
|
|
// server component does not depend directly on the concrete serial
|
|
// implementation.
|
|
type DataConnection struct {
|
|
tcp net.Conn
|
|
serial io.ReadWriteCloser
|
|
|
|
closeOnce sync.Once
|
|
closeErr error
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Constructor
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// NewDataConnection creates a new bidirectional data connection.
|
|
func NewDataConnection(
|
|
tcp net.Conn,
|
|
serial io.ReadWriteCloser,
|
|
) (*DataConnection, error) {
|
|
if tcp == nil {
|
|
return nil, fmt.Errorf("TCP connection is nil")
|
|
}
|
|
|
|
if serial == nil {
|
|
return nil, fmt.Errorf("serial connection is nil")
|
|
}
|
|
|
|
return &DataConnection{
|
|
tcp: tcp,
|
|
serial: serial,
|
|
}, nil
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Properties
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// TCPConn returns the TCP connection.
|
|
func (c *DataConnection) TCPConn() net.Conn {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
return c.tcp
|
|
}
|
|
|
|
// SerialConn returns the serial connection.
|
|
func (c *DataConnection) SerialConn() io.ReadWriteCloser {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
return c.serial
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Run
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Run transfers data in both directions.
|
|
//
|
|
// Run blocks until one of the two transfer directions terminates.
|
|
// The other direction is then stopped and both connections are closed.
|
|
//
|
|
// The first non-EOF transfer error is returned.
|
|
func (c *DataConnection) Run() error {
|
|
if c == nil {
|
|
return fmt.Errorf("data connection is nil")
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
errCh := make(chan error, 2)
|
|
|
|
wg.Add(2)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
_, err := io.Copy(c.serial, c.tcp)
|
|
errCh <- err
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
_, err := io.Copy(c.tcp, c.serial)
|
|
errCh <- err
|
|
}()
|
|
|
|
err := <-errCh
|
|
|
|
_ = c.Close()
|
|
|
|
wg.Wait()
|
|
|
|
if err == nil || err == io.EOF {
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Lifecycle
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Close closes both sides of the data connection.
|
|
//
|
|
// Close is safe to call multiple times.
|
|
func (c *DataConnection) Close() error {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
c.closeOnce.Do(func() {
|
|
var firstErr error
|
|
|
|
if c.tcp != nil {
|
|
if err := c.tcp.Close(); err != nil {
|
|
firstErr = err
|
|
}
|
|
}
|
|
|
|
if c.serial != nil {
|
|
if err := c.serial.Close(); err != nil && firstErr == nil {
|
|
firstErr = err
|
|
}
|
|
}
|
|
|
|
c.closeErr = firstErr
|
|
})
|
|
|
|
return c.closeErr
|
|
}
|