sdsandbox-rl-scripts/Scripts/tcp/TcpServer.cs

219 lines
6.5 KiB
C#
Executable File

using UnityEngine;
using System.Collections;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Collections.Generic;
namespace tk
{
public class TcpServer : MonoBehaviour
{
// register for OnClientConnected to handle the game specific creation of TcpClients with a MonoBehavior
public delegate TcpClient OnClientConnected();
public OnClientConnected onClientConntedCB;
// register for OnClientDisconnected to have an opportunity to handle dropped clients
public delegate void OnClientDisconnected(TcpClient client);
public OnClientDisconnected onClientDisconntedCB;
// Server listener socket
Socket listener = null;
// Accept thread
Thread thread = null;
// Thread signal.
public ManualResetEvent allDone = new ManualResetEvent(false);
// All connected clients
List<TcpClient> clients = new List<TcpClient>();
// All new clients that need a onClientConntedCB callback
List<Socket> new_clients = new List<Socket>();
// Lock object to protect access to new_clients
readonly object _locker = new object();
// Verbose messages
public bool debug = false;
// Call the Run method to start the server. The ip address is typically 127.0.0.1 to accept only local connections.
// Or 0.0.0.0 to bind to all incoming connections for this NIC.
public void Run(string ip, int port)
{
Bind(ip, port);
// Poll for new connections in the ListenLoop
thread = new Thread(ListenLoop);
thread.Start();
}
// Stop the server. Will disconnect all clients and shutdown networking.
public void Stop()
{
foreach( TcpClient client in clients)
{
client.ReleaseServer();
client.Disconnect();
}
clients.Clear();
if (thread != null)
{
thread.Abort();
thread = null;
}
if(listener != null)
{
listener.Close();
listener = null;
Debug.Log("Server stopped.");
}
}
// When GameObject is deleted..
void OnDestroy()
{
Stop();
}
// SendData will broadcast send to all peers
public void SendData(byte[] data, TcpClient skip = null)
{
foreach (TcpClient client in clients)
{
if (client == skip)
continue;
client.SendData(data);
if(debug)
{
Debug.Log("sent: " + System.Text.Encoding.Default.GetString(data));
}
}
}
// Remove reference to TcpClient
public void RemoveClient(TcpClient client)
{
clients.Remove(client);
}
public List<TcpClient> GetClients()
{
return clients;
}
public void Update()
{
lock (_locker)
{
// Because we might be creating GameObjects we need this callback to happen in the main
// thread context. So we queue new sockets and then create their TcpClients from here.
if (new_clients.Count > 0)
{
if (onClientConntedCB != null)
{
foreach (Socket handler in new_clients)
{
TcpClient client = onClientConntedCB.Invoke();
if (client != null)
{
if(client.OnServerAccept(handler, this))
{
clients.Add(client);
client.SetDebug(debug);
client.ClientFinishedConnect();
}
}
}
}
new_clients.Clear();
}
}
//Poll for dropped connection.
foreach(TcpClient client in clients)
{
if(client.IsDropped())
{
onClientDisconntedCB.Invoke(client);
}
}
}
// Start listening for connections
private void Bind(string ip, int port)
{
IPAddress ipAddress = IPAddress.Parse(ip);
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);
// Create a TCP/IP socket.
listener = new Socket(ipAddress.AddressFamily,
SocketType.Stream, ProtocolType.Tcp);
//Bind to address
listener.Bind(localEndPoint);
listener.Listen(100);
Debug.Log("Server Listening on: " + ip + ":" + port.ToString());
}
// Thread loop to wait for new connections
private void ListenLoop()
{
while(true)
{
// Set the event to non-signaled state.
allDone.Reset();
// Start an asynchronous socket to listen for connections.
Debug.Log("Waiting for a connection...");
listener.BeginAccept(
new AsyncCallback(AcceptCallback),
listener);
// Wait until a connection is made before continuing.
allDone.WaitOne();
}
}
// Callback to handle new connections
private void AcceptCallback(IAsyncResult ar)
{
// Signal the main thread to continue.
allDone.Set();
// Get the socket that handles the client request.
Socket listener = (Socket)ar.AsyncState;
try
{
Socket handler = listener.EndAccept(ar);
Debug.Log("client connected.");
lock (_locker)
{
// Add clients to this new_clients list.
// They will get a onClientConntedCB later on in the Update method.
new_clients.Add(handler);
}
}
catch(SocketException e)
{
Debug.LogError(e.ToString());
}
}
}
}