306 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			306 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System.Collections.Generic;
 | |
| using System.Collections.Concurrent;
 | |
| using System.Net;
 | |
| using System.Net.Sockets;
 | |
| using System.IO;
 | |
| using System.Threading.Tasks;
 | |
| 
 | |
| namespace Passer.Control.Core {
 | |
| 
 | |
|     public class Participant {
 | |
|         //public ConnectionMethod connection;
 | |
|         public IPEndPoint? endPoint = null;
 | |
|         public UdpClient? udpClient = null;
 | |
|         public string ipAddress = "0.0.0.0";
 | |
|         public string broadcastIpAddress = "255.255.255.255";
 | |
|         public int port;
 | |
|         public Stream dataStream;
 | |
| 
 | |
|         public byte[] buffer = new byte[256];
 | |
| 
 | |
|         public byte networkId = 0;
 | |
| 
 | |
|         public readonly ConcurrentQueue<IMessage> messageQueue = new();
 | |
| 
 | |
|         public Participant? GetClient(string ipAddress, int port) {
 | |
|             foreach (Participant c in others) {
 | |
|                 if (c.ipAddress == ipAddress && c.port == port)
 | |
|                     return c;
 | |
|             }
 | |
|             return null;
 | |
|         }
 | |
|         public List<Participant> others = new List<Participant>();
 | |
| 
 | |
|         #region Init
 | |
| 
 | |
|         public Participant() {
 | |
|             this.dataStream = new EchoStream();
 | |
|             others.Add(this);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Create a new participant
 | |
|         /// </summary>
 | |
|         /// <param name="ipAddress">The ip address of the site server</param>
 | |
|         /// <param name="port">The port number of the site server</param>
 | |
|         public Participant(string ipAddress = "0.0.0.0", int port = 7681) : this() {
 | |
|             this.ipAddress = ipAddress;
 | |
|             this.port = port;
 | |
|             this.endPoint = new IPEndPoint(IPAddress.Parse(ipAddress), port); // for sending
 | |
|             this.udpClient = new UdpClient(); // for receiving
 | |
|             this.udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, 0));
 | |
|             this.udpClient.BeginReceive(new AsyncCallback(result => ReceiveUDP(result)), null);
 | |
|         }
 | |
| 
 | |
|         public Participant(UdpClient udpClient, int port) : this() {
 | |
|             this.udpClient = udpClient;
 | |
|             this.ipAddress = null;
 | |
|             this.port = port;
 | |
|             //this.dataStream = new EchoStream();
 | |
|             //clients.Add(this);
 | |
|         }
 | |
| 
 | |
|         #endregion Init
 | |
| 
 | |
|         #region Update
 | |
| 
 | |
|         protected void ReceiveUDP(IAsyncResult result) {
 | |
|             if (udpClient == null || this.endPoint == null)
 | |
|                 return;
 | |
| 
 | |
|             try {
 | |
|                 byte[] data = udpClient.EndReceive(result, ref this.endPoint);
 | |
|                 this.dataStream.WriteByte((byte)data.Length);
 | |
|                 this.dataStream.Write(data, 0, data.Length);
 | |
|                 Task task = Task.Run(() => ReceiveData());
 | |
|             }
 | |
|             catch (Exception _) {
 | |
|                 Console.WriteLine("connection error");
 | |
|             }
 | |
|             udpClient.BeginReceive(new AsyncCallback(result => ReceiveUDP(result)), null);
 | |
|         }
 | |
| 
 | |
|         private static readonly long publishInterval = 3000;
 | |
|         private long nextPublishMe = 0;
 | |
| 
 | |
| 
 | |
|         public virtual void Update(long currentTimeMs) {
 | |
|             if (currentTimeMs > this.nextPublishMe) {
 | |
|                 this.Publish(new ClientMsg(this.networkId));
 | |
|                 Console.WriteLine($"Sent ClientMsg {this.networkId}");
 | |
|                 this.nextPublishMe = currentTimeMs + Participant.publishInterval;
 | |
|             }
 | |
| 
 | |
|             for (int ix = 0; ix < this.others.Count; ix++) {
 | |
|                 Participant client = this.others[ix];
 | |
|                 if (client == null)
 | |
|                     continue;
 | |
| 
 | |
|                 client.ProcessMessages();
 | |
|             }
 | |
|             Thing.UpdateAll(currentTimeMs);
 | |
|         }
 | |
| 
 | |
|         #endregion Update
 | |
| 
 | |
|         #region Send
 | |
| 
 | |
|         public void SendThingInfo(Thing thing) {
 | |
|             this.Send(new ThingMsg(this.networkId, thing));
 | |
|             this.Send(new NameMsg(this.networkId, thing));
 | |
|             this.Send(new ModelUrlMsg(this.networkId, thing));
 | |
|         }
 | |
| 
 | |
|         public bool Send(IMessage msg) {
 | |
|             int bufferSize = msg.Serialize(ref this.buffer);
 | |
|             if (bufferSize <= 0)
 | |
|                 return true;
 | |
| 
 | |
|             Console.WriteLine($"msg to {endPoint.Address.ToString()} {endPoint.Port}");
 | |
|             this.udpClient?.Send(this.buffer, bufferSize, this.endPoint);
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         public bool Publish(IMessage msg) {
 | |
|             int bufferSize = msg.Serialize(ref this.buffer);
 | |
|             if (bufferSize <= 0)
 | |
|                 return true;
 | |
| 
 | |
|             //Console.WriteLine($"publish to {broadcastIpAddress.ToString()} {this.port}");
 | |
|             this.udpClient?.Send(this.buffer, bufferSize, this.broadcastIpAddress, this.port);
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         public bool SendBuffer(int bufferSize) {
 | |
|             //if (this.ipAddress == null)
 | |
|             //    return false;
 | |
| 
 | |
|             // UnityEngine.Debug.Log($"Send msg {buffer[0]} to {ipAddress}");
 | |
|             //this.udpClient.Send(this.buffer, bufferSize, this.ipAddress, this.port);
 | |
|             this.udpClient?.Send(this.buffer, bufferSize, this.endPoint);
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         public bool PublishBuffer(int bufferSize) {
 | |
|             if (this.broadcastIpAddress == null)
 | |
|                 return false;
 | |
| 
 | |
|             this.udpClient?.Send(this.buffer, bufferSize, this.broadcastIpAddress, this.port);
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|         #region Receive
 | |
| 
 | |
|         public async Task ReceiveData() {
 | |
|             while (true) {
 | |
|                 byte packetSize = (byte)this.dataStream.ReadByte();
 | |
|                 if (packetSize != 0xFF)
 | |
|                     await ReceiveData(this.dataStream, this, packetSize);
 | |
|                 // else timeout
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public static async Task ReceiveData(Stream dataStream, Participant client, byte packetSize) {
 | |
|             byte msgId = (byte)dataStream.ReadByte();
 | |
|             if (msgId == 0xFF) {
 | |
|                 // Timeout
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             //UnityEngine.Debug.Log($"R {msgId} from {client.ipAddress}");
 | |
|             bool result = false;
 | |
|             switch (msgId) {
 | |
|                 case ClientMsg.Id: // 0xA0 / 160
 | |
|                     result = await ClientMsg.Receive(dataStream, client, packetSize);
 | |
|                     break;
 | |
|                 case NetworkIdMsg.Id: // 0xA1 / 161
 | |
|                     result = await NetworkIdMsg.Receive(dataStream, client, packetSize);
 | |
|                     break;
 | |
|                 case InvestigateMsg.Id: // 0x81
 | |
|                     result = await InvestigateMsg.Receive(dataStream, client, packetSize);
 | |
|                     break;
 | |
|                 case ThingMsg.id: // 0x80 / 128
 | |
|                     result = await ThingMsg.Receive(dataStream, client, packetSize);
 | |
|                     break;
 | |
|                 case NameMsg.Id: // 0x91 / 145
 | |
|                     result = await NameMsg.Receive(dataStream, client, packetSize);
 | |
|                     break;
 | |
|                 case ModelUrlMsg.Id: // 0x90 / 144
 | |
|                     result = await ModelUrlMsg.Receive(dataStream, client, packetSize);
 | |
|                     break;
 | |
|                 case PoseMsg.Id: // 0x10 / 16
 | |
|                     result = await PoseMsg.Receive(dataStream, client, packetSize);
 | |
|                     break;
 | |
|                 case CustomMsg.Id: // 0xB1 / 177
 | |
|                     result = await CustomMsg.Receive(dataStream, client, packetSize);
 | |
|                     break;
 | |
|                 case TextMsg.Id: // 0xB0 / 176
 | |
|                     result = await TextMsg.Receive(dataStream, client, packetSize);
 | |
|                     break;
 | |
|                 case DestroyMsg.Id: // 0x20 / 32
 | |
|                     result = await DestroyMsg.Receive(dataStream, client, packetSize);
 | |
|                     break;
 | |
|                 default:
 | |
|                     break;
 | |
|             }
 | |
|             if (result == false) {
 | |
|                 packetSize = msgId; // skip 1 byte, msgId is possibly a packet size byte
 | |
|                 await ReceiveData(dataStream, client, packetSize);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|         #region Process
 | |
| 
 | |
|         public virtual void ProcessMessages() {
 | |
|             while (this.messageQueue.TryDequeue(out IMessage msg))
 | |
|                 ProcessMessage(msg);
 | |
|         }
 | |
| 
 | |
|         public void ProcessMessage(IMessage msg) {
 | |
|             switch (msg) {
 | |
|                 case ClientMsg clientMsg:
 | |
|                     ProcessClient(clientMsg);
 | |
|                     break;
 | |
|                 case NetworkIdMsg networkId:
 | |
|                     ProcessNetworkId(networkId);
 | |
|                     break;
 | |
|                 case InvestigateMsg investigate:
 | |
|                     ProcessInvestigate(investigate);
 | |
|                     break;
 | |
|                 case ThingMsg thing:
 | |
|                     ProcessThing(thing);
 | |
|                     break;
 | |
|                 case NameMsg name:
 | |
|                     //UnityEngine.Debug.Log($"Name [{name.networkId}/{name.thingId}] {name.name}");
 | |
|                     ProcessName(name);
 | |
|                     break;
 | |
|                 case ModelUrlMsg modelUrl:
 | |
|                     ProcessModelUrl(modelUrl);
 | |
|                     break;
 | |
|                 case PoseMsg pose:
 | |
|                     ProcessPose(pose);
 | |
|                     break;
 | |
|                 case CustomMsg custom:
 | |
|                     ProcessCustom(custom);
 | |
|                     break;
 | |
|                 case TextMsg text:
 | |
|                     ProcessText(text);
 | |
|                     break;
 | |
|                 case DestroyMsg destroy:
 | |
|                     ProcessDestroy(destroy);
 | |
|                     break;
 | |
|                 default:
 | |
|                     return;
 | |
|             }
 | |
|             ForwardMessage(msg);
 | |
|         }
 | |
| 
 | |
|         protected virtual void ProcessClient(ClientMsg msg) { }
 | |
| 
 | |
|         protected virtual void ProcessNetworkId(NetworkIdMsg msg) {
 | |
|             if (this.networkId != msg.networkId) {
 | |
|                 this.networkId = msg.networkId;
 | |
|                 Console.WriteLine($"receive network id {msg.networkId}");
 | |
|                 foreach (Thing thing in Thing.GetAllThings())
 | |
|                     this.SendThingInfo(thing);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected virtual void ProcessInvestigate(InvestigateMsg msg) { }
 | |
| 
 | |
|         protected virtual void ProcessThing(ThingMsg msg) {
 | |
|             Console.WriteLine($"received thing {msg.thingId}");
 | |
|         }
 | |
| 
 | |
|         protected virtual void ProcessName(NameMsg msg) {
 | |
|             Console.WriteLine($"received name {msg.name}");
 | |
|         }
 | |
| 
 | |
|         protected virtual void ProcessModelUrl(ModelUrlMsg msg) {
 | |
|             Console.WriteLine($"received name {msg.url}");
 | |
|         }
 | |
| 
 | |
|         protected virtual void ProcessPose(PoseMsg msg) { }
 | |
| 
 | |
|         protected virtual void ProcessCustom(CustomMsg msg) { }
 | |
| 
 | |
|         protected virtual void ProcessText(TextMsg temsgxt) { }
 | |
| 
 | |
|         protected virtual void ProcessDestroy(DestroyMsg msg) { }
 | |
| 
 | |
|         private void ForwardMessage(IMessage msg) {
 | |
|             foreach (Participant client in others) {
 | |
|                 if (client == this)
 | |
|                     continue;
 | |
|                 //UnityEngine.Debug.Log($"---> {client.ipAddress}");
 | |
|                 IMessage.SendMsg(client, msg);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
|     }
 | |
| } | 
