Last active
August 21, 2020 09:02
-
-
Save VisualMelon/9e1e8425b0e44012c79d932c2f1ca92b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Text; | |
using System.Threading.Tasks; | |
using System.Net.Sockets; | |
namespace NetTest | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var s = RunServer(); | |
RunClient(); | |
RunClient(); | |
Console.WriteLine("Press any key to quit..."); | |
Console.ReadKey(true); | |
} | |
static async Task RunServer() | |
{ | |
Server server = new Server(5325); | |
await server.Run(); // ensure it is started before returning control to caller | |
} | |
static void RunClient() | |
{ | |
var tcpClient = new TcpClient("localhost", 5325); | |
using Client c = new Client(tcpClient); | |
for (int i = 1; i < 5; i++) | |
{ | |
c.SendMessage("Hello! " + i); | |
Console.WriteLine("Client: " + c.ReadMessage()); | |
} | |
c.Close(); | |
} | |
private class Server | |
{ | |
private TcpListener Listener { get; } | |
public Server(int port) | |
{ | |
Listener = new TcpListener(port); | |
} | |
public async Task Run() | |
{ | |
Listener.Start(); | |
while (true) | |
{ | |
var client = await Listener.AcceptTcpClientAsync(); | |
ProcessClient(client); | |
} | |
} | |
private void ProcessClient(TcpClient tcpClient) | |
{ | |
using Client client = new Client(tcpClient); | |
try | |
{ | |
while (true) | |
{ | |
Console.WriteLine("Server: " + client.ReadMessage()); | |
client.SendMessage("Goodbye!"); | |
} | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine("Exception: " + ex.Message); | |
} | |
} | |
} | |
private class Client : IDisposable | |
{ | |
private TcpClient TcpClient { get; } | |
public Client(TcpClient tcpClient) | |
{ | |
TcpClient = tcpClient; | |
} | |
public void Close() | |
{ | |
TcpClient.Close(); | |
} | |
/// <summary> Sends a length-prepended (Pascal) string over the network </summary> | |
public void SendMessage(string message) | |
{ | |
NetworkStream networkStream = TcpClient.GetStream(); | |
// we won't use a binary writer, because the endianness is unhelpful | |
// turn the string message into a byte[] (encode) | |
byte[] messageBytes = Encoding.ASCII.GetBytes(message); // a UTF-8 encoder would be 'better', as this is the standard for network communications | |
// determine length of message | |
int length = messageBytes.Length; | |
// convert the length into bytes using BitConverter (encode) | |
byte[] lengthBytes = System.BitConverter.GetBytes(length); | |
// flip the bytes if we are a little-endian system: reverse the bytes in lengthBytes to do so | |
if (System.BitConverter.IsLittleEndian) | |
{ | |
Array.Reverse(lengthBytes); | |
} | |
// send length | |
networkStream.Write(lengthBytes, 0, lengthBytes.Length); | |
// send message | |
networkStream.Write(messageBytes, 0, length); | |
} | |
/// <summary> Reads a number of bytes from the stream </summary> | |
private byte[] ReadBytes(int count) | |
{ | |
NetworkStream networkStream = TcpClient.GetStream(); | |
byte[] bytes = new byte[count]; // buffer to fill (and later return) | |
int readCount = 0; // bytes is empty at the start | |
// while the buffer is not full | |
while (readCount < count) | |
{ | |
// ask for no-more than the number of bytes left to fill our byte[] | |
int left = count - readCount; // we will ask for `left` bytes | |
int r = networkStream.Read(bytes, readCount, left); // but we are given `r` bytes (`r` <= `left`) | |
if (r == 0) | |
{ // I lied, in the default configuration, a read of 0 can be taken to indicate a lost connection | |
throw new Exception("Lost Connection during read"); | |
} | |
readCount += r; // advance by however many bytes we read | |
} | |
return bytes; | |
} | |
/// <summary> Reads the next message from the stream </summary> | |
public string ReadMessage() | |
{ | |
// read length bytes, and flip if necessary | |
byte[] lengthBytes = ReadBytes(sizeof(int)); // int is 4 bytes | |
if (System.BitConverter.IsLittleEndian) | |
{ | |
Array.Reverse(lengthBytes); | |
} | |
// decode length | |
int length = System.BitConverter.ToInt32(lengthBytes, 0); | |
// read message bytes | |
byte[] messageBytes = ReadBytes(length); | |
// decode the message | |
string message = System.Text.Encoding.ASCII.GetString(messageBytes); | |
return message; | |
} | |
private bool disposed = false; | |
public void Dispose() | |
{ | |
Dispose(true); | |
} | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (disposing && !disposed) | |
{ | |
TcpClient.Dispose(); | |
} | |
disposed = true; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment