// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using System.Runtime.Serialization.Formatters.Binary; namespace Spludlow.Tetris { /// /// Container for all Tetris network comminication /// public class TetrisMessage { public ushort MagicNumber; // Confirm the data is what we are expecting public byte BodyType; // What type of data public int BodyLength; // Data Size public byte[] Body; // the Data public enum BodyTypes : byte { Text, // UTF-8 Text sent from server to all clients Board, // TetrisBoard from server to clients, binary encoding see constructor Move, // One byte from client to server Name, // UTF-8 Text sent from client to server with required display name Sound, // UTF-8 Text of sound name to play Info, // TetrisInfo from server to clients, binary encoding see constructor }; public TetrisMessage() { } /// /// Create New /// public TetrisMessage(BodyTypes type, byte[] data) { this.MagicNumber = 0x7E75; // TETriS in 2 bytes this.BodyType = (byte)type; this.BodyLength = data.Length; this.Body = data; } /// /// Receive from socket, will block or return null if waitFinished gets set /// Calling code should deal with SocketException /// public static TetrisMessage Receive(Socket socket, System.Threading.EventWaitHandle waitFinished) { byte[] buffer = new byte[2]; // Start with async Receive, so it can block if nothing to do IAsyncResult aSyncResult = socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, null, null); // Wait for data OR stop signal int index = WaitHandle.WaitAny(new WaitHandle[] { waitFinished, aSyncResult.AsyncWaitHandle }); // Got Stop signal, abort if (index == 0) return null; int left; // End the async Receive try { left = buffer.Length - socket.EndReceive(aSyncResult); } catch (ObjectDisposedException) { // Socket Disposed elsewhere, abort Spludlow.Log.Warning("Tetris TetrisMessage, EndReceive"); return null; } // Using normal (not aync) socket Receive for rest of message // Finish first read (if neccerassy) ReadBuffer(buffer, socket, left); TetrisMessage message = new TetrisMessage(); message.MagicNumber = BitConverter.ToUInt16(buffer, 0); if (message.MagicNumber != 0x7E75) throw new ApplicationException("Tetris Message, Receive; Bad Magic Number"); buffer = new byte[1]; ReadBuffer(buffer, socket, buffer.Length); message.BodyType = buffer[0]; buffer = new byte[4]; ReadBuffer(buffer, socket, buffer.Length); message.BodyLength = BitConverter.ToInt32(buffer, 0); message.Body = new byte[message.BodyLength]; ReadBuffer(message.Body, socket, message.BodyLength); return message; } /// /// Because socket Receive is non blocking (they can return even if they havent got all the data yet) /// this method will keep calling recieve until the buffer has what is requested /// Normally you pass "remaining" as buffer.Length, with an async receive you may have already read some of the buffer /// private static void ReadBuffer(byte[] buffer, Socket socket, int remaining) { while (remaining > 0) { remaining -= socket.Receive(buffer, buffer.Length - remaining, remaining, SocketFlags.None); } } /// /// Send over the wire /// public bool SocketSend(Socket socket) { using (MemoryStream stream = new MemoryStream()) { using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write(this.MagicNumber); writer.Write(this.BodyType); writer.Write(this.BodyLength); writer.Write(this.Body, 0, this.BodyLength); } try { socket.Send(stream.ToArray()); return true; } catch (SocketException ee) { // 10053 An established connection was aborted by the software in your host machine // 10054 An existing connection was forcibly closed by the remote host // 10060 Timeout if (ee.ErrorCode == 10053 || ee.ErrorCode == 10054 || ee.ErrorCode == 10060) { return false; } throw ee; } } } //public static object Decode(byte[] data) //{ // using (MemoryStream stream = new MemoryStream()) // { // BinaryFormatter formatter = new BinaryFormatter(); // stream.Write(data, 0, data.Length); // stream.Seek(0, SeekOrigin.Begin); // return formatter.Deserialize(stream); // } //} //public static byte[] Encode(object obj) //{ // BinaryFormatter formatter = new BinaryFormatter(); // using (MemoryStream stream = new MemoryStream()) // { // formatter.Serialize(stream, obj); // return stream.ToArray(); // } //} } }