I have a similar issue described in SocketAsyncEventArgs buffer is full of zeroes in which my implementation of a SocketAsyncEventArgs UDP server receives packets which have:
SocketAsyncEventArgs.BytesTransferred > 0
No data in SocketAsyncEventArgs.Buffer
This only happens under load occassionally and can be reproduced on 3 separate machines. I guess the cause of this is also what is causing a problem in my other outstanding SO question (FatalExecutionEngineError Detected when unhooking event).
Here is the implementation of the UDP Server so far:
/// <summary>
/// Provides a basic implementation of a UDPSocket based on ISocket
/// </summary>
public class UDPSocket : ISocket
{
#region "Private Variables"
private Socket _socket;
private IBufferManagerProvider _bufferManager;
#endregion
#region "Public Properties"
public Int32 Port { get; private set; }
public Int32 MessagePrefixLength { get; set; }
public IPAddress ListeningAddress { get; private set; }
public ILogProvider LogProvider { get; private set; }
public bool AllowAddressReuse { get; private set; }
#endregion
#region "Constructors"
private UDPSocket() { }
public UDPSocket(String listeningAddress) : this(listeningAddress, 4444, null, null) { }
public UDPSocket(String listeningAddress, Int32 port) : this(listeningAddress, port, null, null) { }
public UDPSocket(Int32 port) : this("0.0.0.0", port, null, null) { }
public UDPSocket(String listeningAddress, Int32 port, IBufferManagerProvider manager, ILogProvider logger)
{
// Setup the port
if (port <= 0)
{
throw new ArgumentException("Port number cannot be less than 0.");
}
else
{
this.Port = port;
}
// check the ip address
if (String.IsNullOrEmpty(listeningAddress))
{
throw new Exception("The listening address supplied is not valid.");
}
this.ListeningAddress = IPAddress.Parse(listeningAddress);
// check the interfaces
this.LogProvider = (logger == null) ? new DefaultLogProvider(LogLevel.None) : logger;
_bufferManager = (manager == null) ? new DefaultBufferManager(100, 2048, null, null) : manager;
// use a default message prefix
this.MessagePrefixLength = 4;
}
#endregion
#region "Event Handlers"
#endregion
#region "Internal handler methods"
private void Receive()
{
SocketAsyncEventArgs args = _bufferManager.TakeNextSocketAsyncEventArgs();
byte[] buff = _bufferManager.TakeNextBuffer();
args.SetBuffer(buff, 0, buff.Length);
args.Completed += PacketReceived;
args.RemoteEndPoint = new IPEndPoint(IPAddress.Any, this.Port);
try
{
if (!_socket.ReceiveMessageFromAsync(args))
{
OnPacketReceived(args);
}
}
catch
{
// we should only jump here when we disconnect all the clients.
}
}
private void PacketReceived(object sender, SocketAsyncEventArgs e)
{
OnPacketReceived(e);
}
private void OnPacketReceived(SocketAsyncEventArgs e)
{
// Start a new Receive operation straight away
Receive();
// Now process the packet that we have already
if (e.BytesTransferred <= MessagePrefixLength)
{
// Error condition, empty packet
this.LogProvider.Log(String.Format("Empty packet received from {0}. Discarding packet.", e.ReceiveMessageFromPacketInfo.Address.ToString()), "UDPSocket.OnPacketReceived", LogLevel.Minimal);
ReleaseArgs(e);
return;
}
//
// The buffer can occassionally be zero'd at this point in time
//
// Get the message length from the beginning of the packet.
byte[] arrPrefix = new byte[MessagePrefixLength];
Buffer.BlockCopy(e.Buffer, 0, arrPrefix, 0, MessagePrefixLength);
Int32 messageLength = BitConverter.ToInt32(arrPrefix, 0);
// the number of bytes remaining to store
Int32 bytesToProcess = e.BytesTransferred - MessagePrefixLength;
if (bytesToProcess < messageLength)
{
this.LogProvider.Log(String.Format("Missing data from {0}. Discarding packet.", e.ReceiveMessageFromPacketInfo.Address.ToString()), "UDPSocket.OnPacketReceived", LogLevel.Minimal);
ReleaseArgs(e);
return;
}
// Create a data buffer
byte[] data = new byte[messageLength];
// Copy the remaining data to the data buffer on the user token
Buffer.BlockCopy(e.Buffer, MessagePrefixLength, data, 0, messageLength);
// Data is safely stored, so unhook the event and return the SocketAsyncEventArgs back to the pool
ReleaseArgs(e);
// Thread safe way of triggering the event
var evnt = OnDataReceived;
if (evnt != null)
{
evnt(e, new SocketDataEventArgs(data));
}
}
private void ReleaseArgs(SocketAsyncEventArgs e)
{
e.Completed -= PacketReceived;
_bufferManager.InsertSocketAsyncEventArgs(e);
_bufferManager.InsertBuffer(e.Buffer);
}
#endregion
#region "ISocket implicit implementation"
public void Start()
{
this.LogProvider.Log("Starting. Creating socket", "UDPSocket.Start", LogLevel.Verbose);
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true);
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, this.AllowAddressReuse);
_socket.Bind(new IPEndPoint(this.ListeningAddress, this.Port));
// use a default message prefix
this.MessagePrefixLength = 4;
// begin receiving packets
Receive();
this.LogProvider.Log("Socket created. Listening for packets", "UDPSocket.Start", LogLevel.Verbose);
}
public void Stop()
{
// do a shutdown before you close the socket
try
{
_socket.Shutdown(SocketShutdown.Both);
this.LogProvider.Log("Clean socket shutdown", "TCPSocket.CloseSocket", LogLevel.Verbose);
}
// throws if socket was already closed
catch (Exception ex)
{
this.LogProvider.Log("Error closing socket - " + ex.Message, "TCPSocket.CloseSocket", LogLevel.Minimal);
}
// Close the socket, which calls Dispose internally
_socket.Close();
this.LogProvider.Log("Socket closed", "TCPSocket.CloseSocket", LogLevel.Verbose);
}
public event EventHandler<SocketDataEventArgs> OnDataReceived;
#endregion
}
I can produce a full server/client demonstration if required as well as posting the other classes such as the buffer manager. This is my first time using SocketAsyncEventArgs so my implementation might not be 100% correct
I think that I found the problem. Instead of using a Stack<T> to manage the buffers, I have moved to a Queue<T> which dramatically reduces the problem. For those interested, here is my implementation of the buffer queue:
/// <summary>
/// Creates a managed Queue that makes a program wait when the resources are depleated
/// </summary>
/// <typeparam name="T">The type of Queue to create</typeparam>
public sealed class ManagedQueue<T>
{
#region "Constructors"
public ManagedQueue(Int32 capacity = 400, bool fillQueue = false, Queue<T> queue = null)
{
Capacity = capacity;
_queue = queue ?? new Queue<T>(capacity);
_restrictor = new SemaphoreSlim((fillQueue) ? Capacity : 0, Capacity);
if (fillQueue)
{
// Setup the queue with default values
for (Int32 i = 0; i < Capacity; i++)
{
Insert(default(T));
}
}
}
#endregion
/// <summary>
/// Gets the defined over all queue Capacity
/// </summary>
public Int32 Capacity { get; private set; }
// The queue to hold the items
private readonly Queue<T> _queue;
// The SemaphoreSlim to restrict access to the queue
private readonly SemaphoreSlim _restrictor;
/// <summary>
/// Take the next resource available from the queue. This is a blocking operation if capacity is reached.
/// </summary>
/// <returns>The next resource available</returns>
public T TakeNext()
{
// Sanity Check
if (_queue == null)
{
throw new InvalidOperationException("The queue cannot be null");
}
// make us wait if necessary
_restrictor.Wait();
lock (_queue)
{
if (_queue.Count > 0)
{
return _queue.Dequeue();
}
throw new Exception("There has been a Semaphore/queue offset");
}
}
/// <summary>
/// Adds an item to the queue. This will release other threads if they are blocked
/// </summary>
/// <param name="item"></param>
public void Insert(T item)
{
// Sanity Check
if (_queue == null)
{
throw new InvalidOperationException("The queue cannot be null");
}
// Sanity Check
if (item == null)
{
throw new ArgumentException("The item cannot be null");
}
lock (_queue)
{
_queue.Enqueue(item);
_restrictor.Release();
}
}
}
Related
i have 3 days working with handling a disconnected client that when i disconnect a client it keeps sending nothing and when i connect it again it have another id . `
public class SocketServer : System.Windows.Forms.Form
{
internal const int MAX_CLIENTS = 10;
public AsyncCallback pfnWorkerCallBack;
private Socket m_mainSocket;
private Socket[] m_workerSocket = new Socket[30];// Hard limit of 30 TOTAL CONNECTIONS!!
private int m_clientCount = 0;
public SocketServer()
{
InitializeComponent();
// Display the local IP address on the GUI
textBoxIP.Text = GetIP();
}
[STAThread]
public static void Main(string[] args)
{
Application.Run(new SocketServer());
}
}`
internal void ButtonStartListenClick(object sender, System.EventArgs e)
{
try
{
// Check the port value
if (textBoxPort.Text == "")
{
MessageBox.Show("Please enter a Port Number");
return;
}
string portStr = textBoxPort.Text;
int port = System.Convert.ToInt32(portStr);
// Create the listening socket...
m_mainSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port);
// Bind to local IP Address...
m_mainSocket.Bind(ipLocal);
// Start listening...
m_mainSocket.Listen(20);
// Create the call back for any client connections...
m_mainSocket.BeginAccept(new AsyncCallback(OnClientConnect), null);
UpdateControls(true);
}
catch (SocketException se)
{
MessageBox.Show(se.Message);
}
}
/// <summary>
/// The UpdateControls
/// </summary>
/// <param name="listening">The listening<see cref="bool"/></param>
private void UpdateControls(bool listening)
{
buttonStartListen.Enabled = !listening;
buttonStopListen.Enabled = listening;
}
/// <summary>
/// The OnClientConnect
/// </summary>
/// <param name="asyn">The asyn<see cref="IAsyncResult"/></param>
public void OnClientConnect(IAsyncResult asyn)
{
try
{
// Here we complete/end the BeginAccept() asynchronous call
// by calling EndAccept() - which returns the reference to
// a new Socket object
m_workerSocket[m_clientCount] = m_mainSocket.EndAccept(asyn);
// Display this client connection as a status message on the GUI
String str = String.Format("Client # {0} connected", m_clientCount);
SetFormStatus(str);
LogIncomingMessageToForm(str);
if (broadcastIncomingMessages)
{
SendMsgToAll(str);
}
// Send the client their ID (First thing the server sends!)
//byte[] byData = System.Text.Encoding.ASCII.GetBytes(m_clientCount.ToString());
//Console.WriteLine(byData);
//m_workerSocket[m_clientCount].Send(byData);
Invoke(new Action(() => checkedListBox1.Items.Add("client " + (m_clientCount).ToString() + " => " + m_workerSocket[m_clientCount].RemoteEndPoint.ToString(), CheckState.Unchecked)));
// Let the worker Socket do the further processing for the
// just connected client
WaitForData(m_clientCount);
// Now increment the client count
++m_clientCount;
// Since the main Socket is now free, it can go back and wait for
// other clients who are attempting to connect
m_mainSocket.BeginAccept(new AsyncCallback(OnClientConnect), null);
}
catch (ObjectDisposedException)
{
System.Diagnostics.Debugger.Log(0, "1", "\n OnClientConnection: Socket has been closed\n");
}
catch (SocketException se)
{
MessageBox.Show(se.Message);
}
}
/// <summary>
/// Defines the <see cref="SocketPacket" />
/// </summary>
public class SocketPacket
{
/// <summary>
/// Defines the m_currentSocket
/// </summary>
public System.Net.Sockets.Socket m_currentSocket;
/// <summary>
/// Defines the dataBuffer
/// </summary>
public byte[] dataBuffer = new byte[255];
/// <summary>
/// Defines the id
/// </summary>
public int id;
}
/// <summary>
/// The WaitForData
/// </summary>
/// <param name="id">The id<see cref="int"/></param>
public void WaitForData(int id)
{
Socket soc = m_workerSocket[id];
try
{
if (pfnWorkerCallBack == null)
{
// Specify the call back function which is to be
// invoked when there is any write activity by the
// connected client
pfnWorkerCallBack = new AsyncCallback(OnDataReceived);
}
SocketPacket theSocPkt = new SocketPacket();
theSocPkt.m_currentSocket = soc;
theSocPkt.id = id;
// Start receiving any data written by the connected client
// asynchronously
soc.BeginReceive(theSocPkt.dataBuffer, 0,
theSocPkt.dataBuffer.Length,
SocketFlags.None,
pfnWorkerCallBack,
theSocPkt);
}
catch (SocketException se)
{
MessageBox.Show(se.Message);
}
}
/// <summary>
/// The OnDataReceived
/// </summary>
/// <param name="asyn">The asyn<see cref="IAsyncResult"/></param>
public void OnDataReceived(IAsyncResult asyn)
{
try
{
SocketPacket socketData = (SocketPacket)asyn.AsyncState;
int iRx = 0;
// Complete the BeginReceive() asynchronous call by EndReceive() method
// which will return the number of characters written to the stream
// by the client
iRx = socketData.m_currentSocket.EndReceive(asyn);
char[] chars = new char[iRx + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(socketData.dataBuffer,
0, iRx, chars, 0);
System.String szData = new System.String(chars);
LogIncomingMessageToForm(socketData.id.ToString(), szData);
if (broadcastIncomingMessages)
{
SendMsgToAll(socketData.id.ToString(), szData);
}
// Continue the waiting for data on the Socket
WaitForData(socketData.id);
}
catch (ObjectDisposedException)
{
System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
}
catch (SocketException se)
{
MessageBox.Show(se.Message);
}
}
then here i need to handle the disconnected client not to do that
Here is my many-years many times modified code for TCP asynchronous client.
It is able to detect connection loss (thanks to using keep alive values).
But now, I need to reconnect it to server automatically after any connection loss detection (any communication error or Disconnect() call).
It works fine with some simple SW TCP servers, when I stop them listening or disconnect my client from them. But, when I connect to some real devices and start to simulate possible errors, problems start.
For example, I disconnect client PC from network, then after some time of "reconnecting", application goes to connection state loop and hangs, specifically method OnDataReceived throws SocketException: ConnectionReset on iRx = stateObject.Socket.EndReceive(asyn); call.
As I am not adept at async code, I believe I do something very bad
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace SharpLib.Tcp
{
public enum ConnectionState
{
Connected, Disconnected
};
public delegate void ConnectionStateChangedEventHandler(object sender, ConnectionStateEventArgs args);
public class ConnectionStateEventArgs : EventArgs
{
public ConnectionState ConnectionState { get; set; }
public ConnectionStateEventArgs(ConnectionState state)
{
this.ConnectionState = state;
}
}
/// <summary>
/// Structure of received data
/// </summary>
public class StateObject
{
public byte[] DataBuffer { get; set; }
public Socket Socket { get; set; }
public StateObject(Socket socket)
{
this.DataBuffer = new byte[128];
this.Socket = socket;
}
}
/// <summary>
/// TCP client with asynchronous connecting and data receiving
/// </summary>
public class TcpAsyncClient
{
protected string address;
protected int port;
private Socket socket;
private int keepAliveTime;
private int keepAliveInterval;
private int connectTimeout;
private bool autoReconnect;
private AsyncCallback callback;
private static ManualResetEvent connectDone = new ManualResetEvent(false);
public event MessageEventHandler DataReceived = delegate { };
public event ExceptionEventHandler ExceptionCaught = delegate { };
public event ConnectionStateChangedEventHandler ConnectionStateChanged = delegate { };
public bool Connected
{
get
{
if (socket == null)
return false;
return socket.Connected;
}
}
public TcpAsyncClient(string address, int port, int keepAliveTime = 1000, int keepAliveInterval = 1000, int connectTimeout = 1000, bool autoReconnect = false)
{
this.address = address;
this.port = port;
this.keepAliveInterval = keepAliveInterval;
this.keepAliveTime = keepAliveTime;
this.connectTimeout = connectTimeout;
this.autoReconnect = autoReconnect;
ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
}
/// <summary>
/// Connect to tcp server - async
/// </summary>
public void Connect()
{
try
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipEnd = FindIpEndPoint(address, port);
socket.BeginConnect(ipEnd, new AsyncCallback(ConnectCallback), new StateObject(socket));
connectDone.WaitOne(500);
}
catch (SocketException ex)
{
OnError(ex);
}
}
/// <summary>
/// Connect done callback
/// </summary>
/// <param name="ar"></param>
private void ConnectCallback(IAsyncResult ar)
{
try
{
// Complete the connection.
((StateObject)ar.AsyncState).Socket.EndConnect(ar);
// Signal that the connection has been made.
connectDone.Set();
WaitForData();
SetKeepAlive(true, Convert.ToUInt32(keepAliveTime), Convert.ToUInt32(keepAliveInterval));
ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Connected));
}
catch (SocketException ex)
{
OnError(ex);
}
}
/// <summary>
/// Disconnect from tcp server
/// </summary>
public void Disconnect()
{
try
{
// MSDN recommends to Shutdown() before Disconnect()
socket.Shutdown(SocketShutdown.Both);
socket.Disconnect(true);
}
catch { }
ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
if (autoReconnect)
{
Connect();
}
}
/// <summary>
/// Send string message to tcp server
/// </summary>
/// <param name="message"></param>
public void Send(string message)
{
// because of this, we can Send from client imidiately after Connect() call
DateTime start = DateTime.Now;
if (!Connected)
{
ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
return;
}
// make return on the end of line
message += "\r";
int sent = 0; // how many bytes is already sent
do
{
try
{
sent += socket.Send(System.Text.Encoding.UTF8.GetBytes(message), sent, message.Length - sent, SocketFlags.None);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.WouldBlock ||
ex.SocketErrorCode == SocketError.IOPending ||
ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
{
// socket buffer is probably full, wait and try again
Thread.Sleep(30);
}
else
{
OnError(ex);
break;
}
}
}
while (sent < message.Length);
}
/// <summary>
/// Start receiving data from tcp server
/// </summary>
public void WaitForData()
{
try
{
StateObject stateObject = new StateObject(socket);
IAsyncResult result = socket.BeginReceive(stateObject.DataBuffer, 0, 128, SocketFlags.None, new AsyncCallback(OnDataReceived), stateObject);
}
catch (SocketException ex)
{
OnError(ex);
}
}
/// <summary>
/// Data received callback
/// </summary>
/// <param name="asyn"></param>
public void OnDataReceived(IAsyncResult asyn)
{
try
{
StateObject stateObject = (StateObject)asyn.AsyncState;
if (!stateObject.Socket.Connected)
return;
int iRx = stateObject.Socket.EndReceive(asyn);
// Server probably stopped listening
if (iRx == 0)
{
Disconnect();
ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
return;
}
char[] chars = new char[iRx];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(stateObject.DataBuffer, 0, iRx, chars, 0);
string szData = new string(chars);
DataReceived(this, new MessageEventArgs(szData));
WaitForData();
}
catch (SocketException ex)
{
OnError(ex);
}
}
/// <summary>
/// Socket exception during connecting or communication with server
/// </summary>
/// <param name="ex"></param>
private void OnError(Exception ex)
{
ExceptionCaught(this, new ExceptionEventArgs(ex));
Disconnect();
}
/// <summary>
/// Set KeepAlive timer for socket
/// </summary>
/// <param name="on"></param>
/// <param name="time"></param>
/// <param name="interval"></param>
private void SetKeepAlive(bool on, uint time, uint interval)
{
int size = Marshal.SizeOf(new uint());
var inOptionValues = new byte[size * 3];
BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)time).CopyTo(inOptionValues, size);
BitConverter.GetBytes((uint)interval).CopyTo(inOptionValues, size * 2);
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
}
/// <summary>
/// Create ip address from known host name
/// </summary>
/// <param name="hostName"></param>
/// <param name="port"></param>
/// <returns></returns>
private IPEndPoint FindIpEndPoint(string hostName, int port)
{
var addresses = System.Net.Dns.GetHostAddresses(hostName);
if (addresses.Length == 0)
{
throw new ArgumentException(
"Unable to retrieve address from specified host name.",
"hostName"
);
}
return new IPEndPoint(addresses[0], port);
}
}
}
I have a C# project working with input audio Stream from Kinect 1, Kinect 2, Microphone or anything else.
waveIn.DataAvailable += (object sender, WaveInEventArgs e) => {
lock(buffer){
var pos = buffer.Position;
buffer.Write(e.Buffer, 0, e.BytesRecorded);
buffer.Position = pos;
}
};
The buffer variable is a Stream from component A that will be processed by a SpeechRecognition component B working on Streams.
I will add new components C, D, E, working on Streams to compute pitch, detect sound, do finger printing, or anything else ...
How can I duplicate that Stream for components C, D, E ?
Component A send an Event "I have a Stream do what you want" I don't want to reverse the logic by an Event "Give me your streams"
I'm looking for a "MultiStream" that could give me a Stream instance and will handle the job
Component A
var MultiStream buffer = new MultiStream()
...
SendMyEventWith(buffer)
Component B, C, D, E
public void HandleMyEvent(MultiStream buffer){
var stream = buffer.GetNewStream();
var engine = new EngineComponentB()
engine.SetStream(stream);
}
The MultiStream must be a Stream to wrap Write() method (because Stream do not have data available mechanics) ?
If a Stream is Dispose() by Component B the MultiStream should remove it from it's array ?
The MultiStream must throw an exception on Read() to require use of GetNewStream()
EDIT: Kinect 1 provide a Stream itself ... :-( should I use a Thread to pumpit into the MultiStream ?
Did anybody have that kind of MultiStream Class ?
Thanks
I'm not sure if this is the best way to do it or that it's better than the previous answer, and I'm not guaranteeing that this code is perfect, but I coded something that is literally what you asked for because it was fun - a MultiStream class.
You can find the code for the class here: http://pastie.org/10289142
Usage Example:
MultiStream ms = new MultiStream();
Stream copy1 = ms.CloneStream();
ms.Read( ... );
Stream copy2 = ms.CloneStream();
ms.Read( ... );
copy1 and copy2 will contain identical data after the example is ran, and they will continue to get updated as the MultiStream is written to. You can read, update position, and dispose of the cloned streams individually. If disposed the cloned streams will get removed from MultiStream, and disposing of Multistream will close all related and cloned streams (you can change this if it's not the behavior you want). Trying to write to the cloned streams will throw a not supported exception.
Somehow I don't think streams really fit what you're trying to do. You're setting up a situation where a long run of the program is going to continually expand the data requirements for no apparent reason.
I'd suggest a pub/sub model that publishes the received audio data to subscribers, preferably using a multi-threaded approach to minimize the impact of a bad subscriber. Some ideas can be found here.
I've done this before with a processor class that implements IObserver<byte[]> and uses a Queue<byte[]> to store the sample blocks until the process thread is ready for them. Here's are the base classes:
public abstract class BufferedObserver<T> : IObserver<T>, IDisposable
{
private object _lck = new object();
private IDisposable _subscription = null;
public bool Subscribed { get { return _subscription != null; } }
private bool _completed = false;
public bool Completed { get { return _completed; } }
protected readonly Queue<T> _queue = new Queue<T>();
protected bool DataAvailable { get { lock(_lck) { return _queue.Any(); } } }
protected int AvailableCount { get { lock (_lck) { return _queue.Count; } } }
protected BufferedObserver()
{
}
protected BufferedObserver(IObservable<T> observable)
{
SubscribeTo(observable);
}
public virtual void Dispose()
{
if (_subscription != null)
{
_subscription.Dispose();
_subscription = null;
}
}
public void SubscribeTo(IObservable<T> observable)
{
if (_subscription != null)
_subscription.Dispose();
_subscription = observable.Subscribe(this);
_completed = false;
}
public virtual void OnCompleted()
{
_completed = true;
}
public virtual void OnError(Exception error)
{ }
public virtual void OnNext(T value)
{
lock (_lck)
_queue.Enqueue(value);
}
protected bool GetNext(ref T buffer)
{
lock (_lck)
{
if (!_queue.Any())
return false;
buffer = _queue.Dequeue();
return true;
}
}
protected T NextOrDefault()
{
T buffer = default(T);
GetNext(ref buffer);
return buffer;
}
}
public abstract class Processor<T> : BufferedObserver<T>
{
private object _lck = new object();
private Thread _thread = null;
private object _cancel_lck = new object();
private bool _cancel_requested = false;
private bool CancelRequested
{
get { lock(_cancel_lck) return _cancel_requested; }
set { lock(_cancel_lck) _cancel_requested = value; }
}
public bool Running { get { return _thread == null ? false : _thread.IsAlive; } }
public bool Finished { get { return _thread == null ? false : !_thread.IsAlive; } }
protected Processor(IObservable<T> observable)
: base(observable)
{ }
public override void Dispose()
{
if (_thread != null && _thread.IsAlive)
{
//CancelRequested = true;
_thread.Join(5000);
}
base.Dispose();
}
public bool Start()
{
if (_thread != null)
return false;
_thread = new Thread(threadfunc);
_thread.Start();
return true;
}
private void threadfunc()
{
while (!CancelRequested && (!Completed || _queue.Any()))
{
if (DataAvailable)
{
T data = NextOrDefault();
if (data != null && !data.Equals(default(T)))
ProcessData(data);
}
else
Thread.Sleep(10);
}
}
// implement this in a sub-class to process the blocks
protected abstract void ProcessData(T data);
}
This way you're only keeping the data as long as you need it, and you can attach as many process threads as you need to the same observable data source.
And for the sake of completeness, here's a generic class that implements IObservable<T> so you can see how it all fits together. This one even has comments:
/// <summary>Generic IObservable implementation</summary>
/// <typeparam name="T">Type of messages being observed</typeparam>
public class Observable<T> : IObservable<T>
{
/// <summary>Subscription class to manage unsubscription of observers.</summary>
private class Subscription : IDisposable
{
/// <summary>Observer list that this subscription relates to</summary>
public readonly ConcurrentBag<IObserver<T>> _observers;
/// <summary>Observer to manage</summary>
public readonly IObserver<T> _observer;
/// <summary>Initialize subscription</summary>
/// <param name="observers">List of subscribed observers to unsubscribe from</param>
/// <param name="observer">Observer to manage</param>
public Subscription(ConcurrentBag<IObserver<T>> observers, IObserver<T> observer)
{
_observers = observers;
_observer = observer;
}
/// <summary>On disposal remove the subscriber from the subscription list</summary>
public void Dispose()
{
IObserver<T> observer;
if (_observers != null && _observers.Contains(_observer))
_observers.TryTake(out observer);
}
}
// list of subscribed observers
private readonly ConcurrentBag<IObserver<T>> _observers = new ConcurrentBag<IObserver<T>>();
/// <summary>Subscribe an observer to this observable</summary>
/// <param name="observer">Observer instance to subscribe</param>
/// <returns>A subscription object that unsubscribes on destruction</returns>
/// <remarks>Always returns a subscription. Ensure that previous subscriptions are disposed
/// before re-subscribing.</remarks>
public IDisposable Subscribe(IObserver<T> observer)
{
// only add observer if it doesn't already exist:
if (!_observers.Contains(observer))
_observers.Add(observer);
// ...but always return a new subscription.
return new Subscription(_observers, observer);
}
// delegate type for threaded invocation of IObserver.OnNext method
private delegate void delNext(T value);
/// <summary>Send <paramref name="data"/> to the OnNext methods of each subscriber</summary>
/// <param name="data">Data object to send to subscribers</param>
/// <remarks>Uses delegate.BeginInvoke to send out notifications asynchronously.</remarks>
public void Notify(T data)
{
foreach (var observer in _observers)
{
delNext handler = observer.OnNext;
handler.BeginInvoke(data, null, null);
}
}
// delegate type for asynchronous invocation of IObserver.OnComplete method
private delegate void delComplete();
/// <summary>Notify all subscribers that the observable has completed</summary>
/// <remarks>Uses delegate.BeginInvoke to send out notifications asynchronously.</remarks>
public void NotifyComplete()
{
foreach (var observer in _observers)
{
delComplete handler = observer.OnCompleted;
handler.BeginInvoke(null, null);
}
}
}
Now you can create an Observable<byte[]> to use as your transmitter for Process<byte[]> instances that are interested. Pull data blocks out of the input stream, audio reader, etc. and pass them to the Notify method. Just make sure that you clone the arrays beforehand...
I'm writing a chat program using sockets in C# Winform.
It works well with single client, but when I turn on another client, previous client's (worked well) Send function never works, but it keeps receiving data.
Here's my whole source code (because idk what's wrong with it, and I got some advices that I gave them poor details)
Server:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace Player__Server_
{
public partial class Form1 : Form
{
Socket Serv;
List<Socket> ClnSocket = new List<Socket>();
List<string> MusList = new List<string>();
List<string> Nickname = new List<string>();
int ClnCounter = 0;
int MusCounter = 0;
int BufferingCount = 0;
Socket socket;
Thread run;
private delegate void sDelegate(string sData, int socketIndex);
public Form1()
{
InitializeComponent();
}
new public string Left(string Text, int TextLength)
{
if (Text.Length < TextLength)
{
TextLength = Text.Length;
}
return Text.Substring(0, TextLength);
}
new public string Right(string Text, int TextLength)
{
if (Text.Length < TextLength)
{
TextLength = Text.Length;
}
return Text.Substring(Text.Length - TextLength, TextLength);
}
new public string Mid(string sString, int nStart, int nLength)
{
string sReturn;
--nStart;
if (nStart <= sString.Length)
{
if ((nStart + nLength) <= sString.Length)
{
sReturn = sString.Substring(nStart, nLength);
}
else
{
sReturn = sString.Substring(nStart);
}
}
else
{
sReturn = string.Empty;
}
return sReturn;
}
private void Form1_Load(object sender, EventArgs e)
{
// prevent exception
Stream fe = new FileStream(Environment.CurrentDirectory + "/Music.server", FileMode.OpenOrCreate);
fe.Close();
// Read "Music.server" file and add to MusList (List)
string line;
StreamReader fr = new StreamReader(Environment.CurrentDirectory + "/Music.server");
while ((line = fr.ReadLine()) != null)
{
MusList.Add(line);
MusCounter++;
}
fr.Close();
// prevent exception
Stream fa = new FileStream(Environment.CurrentDirectory + "/Account.server", FileMode.OpenOrCreate);
fa.Close();
Serv = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Serv.Bind(new IPEndPoint(IPAddress.Any, 9180));
Serv.Listen(100);
Thread accept = new Thread(this.Accept);
accept.Start();
}
private void runChatting(object s)
{
byte[] str = new byte[2048];
socket = s as Socket;
while (true)
{
try
{
str = new byte[2048];
socket.Receive(str);
sDelegate sdelegate = new sDelegate(this.Receive);
this.Invoke(sdelegate, Encoding.Default.GetString(str), ClnSocket.IndexOf(socket));
}
catch
{
try
{
ClnSocket.Remove(socket);
ClnCounter--;
}
catch { }
return;
}
}
}
private string GetMusic(string MusName)
{
// Function :: return original information of music- search by name
int i;
for (i = 0; i < MusCounter; i++)
{
try
{
if (MusList[i].IndexOf(MusName) > 0)
{
return MusList[i];
}
}
catch { }
}
return null;
}
private void Receive(string sData, int socketIndex)
{
TextBox.AppendText("GET : " + sData);
if (sData.IndexOf(Environment.NewLine) > 0) { ; } else TextBox.AppendText(Environment.NewLine);
sData = sData.Replace("\0", "");
if (Left(sData,10) == "#musicadd#")
{
string TempData = Mid(sData, 11, sData.Length);
string[] SpliteData = TempData.Split('#');
if (GetMusic(SpliteData[1]) == null)
{
Stream fs = new FileStream(Environment.CurrentDirectory + "/Music.server", FileMode.Append);
StreamWriter ws = new StreamWriter(fs);
ws.WriteLine(SpliteData[0] + "#" + SpliteData[1] + "#" + SpliteData[2] + "#sc");
ws.Close();
fs.Close();
MusList.Add(SpliteData[0] + "#" + SpliteData[1] + "#" + SpliteData[2] + "#sc");
MusCounter++;
}
SendTo("#musicadd#" + SpliteData[1], socketIndex);
}
else if (Left(sData, 7) == "#login#")
{
SendAll(Mid(sData, 8, sData.Length) + " Connected." + Environment.NewLine);
}
else if (Left(sData, 14) == "#requestmusic#")
{
string requestValue = GetMusic(Mid(sData, 15, sData.Length));
SendAll("#buffermusic#" + requestValue);
BufferingCount = 0;
}
else if (Left(sData, 12) == "#bufferdone#")
{
BufferingCount++;
if (BufferingCount == ClnCounter)
{
SendAll("#musicplay#");
}
}
else
{
SendAll(sData);
}
}
private void SendAll(string sData)
{
int i;
for (i = 0; i < ClnSocket.Count; i++)
{
try
{
ClnSocket[i].Send(Encoding.Default.GetBytes(sData));
}
catch { }
}
TextBox.AppendText("POST : " + sData);
if (sData.IndexOf(Environment.NewLine) > 0) { ; } else TextBox.AppendText(Environment.NewLine);
}
private void SendTo(string sData, int socketIndex)
{
try
{
ClnSocket[socketIndex].Send(Encoding.Default.GetBytes(sData));
TextBox.AppendText("POST TO (" + socketIndex.ToString() + ") : " + sData);
if (sData.IndexOf(Environment.NewLine) > 0) { ; } else TextBox.AppendText(Environment.NewLine);
}
catch { }
}
private void Accept()
{
while (true)
{
ClnSocket.Add(Serv.Accept());
ClnCounter++;
run = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(this.runChatting));
run.Start(ClnSocket[ClnCounter - 1]);
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
this.Serv.Close();
this.Serv = null;
for (int i = 0; i < this.ClnSocket.Count; i++)
{
this.ClnSocket[i].Close();
}
this.ClnSocket.Clear();
System.Environment.Exit(0);
}
}
}
Client:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Media;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.IO;
namespace MineSky_Player
{
public partial class Form1 : Form
{
WMPLib.WindowsMediaPlayer Player = new WMPLib.WindowsMediaPlayer();
public Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Thread run;
string BufferingInformation;
int current_min = 0, current_sec = 0;
int duration_min = 0, duration_sec = 0;
int MusCounter = 0;
public string mynick { get; set; }
string MyNick;
List<string> MusList = new List<string>();
public delegate void sDelegate(string sData);
public Form1()
{
try
{
InitializeComponent();
socket.Connect("localhost", 9180);
run = new Thread(new ParameterizedThreadStart(Run));
run.Start();
}
catch (Exception ex){
MessageBox.Show(ex.ToString());
System.Environment.Exit(0);
}
}
private void Form1_Load(object sender, EventArgs e)
{
// prevent exception
Stream fe = new FileStream(Environment.CurrentDirectory + "/Music.client", FileMode.OpenOrCreate);
fe.Close();
// Read "Music.client" file and add to MusList (List)
string line;
StreamReader fr = new StreamReader(Environment.CurrentDirectory + "/Music.client");
while ((line = fr.ReadLine()) != null)
{
MusList.Add(line);
MusCounter++;
MusicList.Items.Add(line);
}
fr.Close();
MyNick = mynick;
}
new public string Left(string Text, int TextLength)
{
if (Text.Length < TextLength)
{
TextLength = Text.Length;
}
return Text.Substring(0, TextLength);
}
new public string Right(string Text, int TextLength)
{
if (Text.Length < TextLength)
{
TextLength = Text.Length;
}
return Text.Substring(Text.Length - TextLength, TextLength);
}
new public string Mid(string sString, int nStart, int nLength)
{
string sReturn;
--nStart;
if (nStart <= sString.Length)
{
if ((nStart + nLength) <= sString.Length)
{
sReturn = sString.Substring(nStart, nLength);
}
else
{
sReturn = sString.Substring(nStart);
}
}
else
{
sReturn = string.Empty;
}
return sReturn;
}
private void BufferTick_Tick(object sender, EventArgs e)
{
if (Player.playState.ToString() == "wmppsPlaying")
{
Player.controls.stop();
ToSocket("#bufferdone#");
BufferTick.Enabled = false;
}
}
private void button1_Click(object sender, EventArgs e)
{
Player.controls.play();
}
private void Run(object s)
{
byte[] str = new byte[2048];
try
{
while (true)
{
str = new byte[2048];
socket.Receive(str);
sDelegate sdelegate = new sDelegate(this.Receive);
IntPtr x;
if (!this.IsHandleCreated) x = this.Handle;
this.Invoke(sdelegate, Encoding.Default.GetString(str));
}
}
catch (Exception e)
{
MessageBox.Show("Connection Lost." + Environment.NewLine + e.ToString());
Application.Exit();
}
}
public void Receive(string sData)
{
//MessageBox.Show("GET : " + sData);
sData = sData.Replace("\0", "");
if (Left(sData, 10) == "#musicadd#")
{
if (MusicList.Items.Contains(Mid(sData, 11, sData.Length)))
{
MessageBox.Show("Already in the list!", "MSPlayer", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
else
{
Stream fs = new FileStream(Environment.CurrentDirectory + "/Music.client", FileMode.Append);
StreamWriter ws = new StreamWriter(fs);
ws.WriteLine(Mid(sData, 11, sData.Length));
ws.Close();
fs.Close();
MusList.Add(Mid(sData, 11, sData.Length));
MusicList.Items.Add(Mid(sData, 11, sData.Length));
MusCounter++;
}
}
else if (Left(sData, 13) == "#buffermusic#")
{
PlayProgressBar.Value = 0;
current_min = 0;
current_sec = 0;
Player.URL = "";
BufferingInformation = Mid(sData, 14, sData.Length);
playingLabel.Text = BufferingInformation.Split('#')[1];
playLabel.Text = "Buffering...";
Player.URL = "https://player.soundcloud.com/player.swf?url=https%3A//api.soundcloud.com/tracks/" + BufferingInformation.Split('#')[0] + ";color=ff5500&show_comments=false&auto_play=true& color=a2eeff";
BufferTick.Enabled = true;
}
else if (Left(sData, 11) == "#musicplay#")
{
duration_min = Int32.Parse(BufferingInformation.Split('#')[2]) / 60;
duration_sec = Int32.Parse(BufferingInformation.Split('#')[2]) % 60;
playLabel.Text = "0:00 / " + duration_min.ToString() + ":" + duration_sec.ToString();
PlayProgressBar.Maximum = Int32.Parse(BufferingInformation.Split('#')[2]);
Player.controls.play();
PlayTick.Enabled = true;
}
else
{
Text_Board.AppendText(sData.Replace("\#\", "#"));
}
}
public void Send(string sData)
{
sData = sData.Replace("#", "\#\");
Send_Supplied(sData);
}
public void Send_Supplied(string sData)
{
if (Left(sData, 2) == "//")
{
sData = sData.ToLower();
if (Left(sData, 6) == "//exit") Application.Exit();
}
else
{
ToSocket(sData);
}
}
private void ToSocket(string sData)
{
socket.Send(Encoding.Default.GetBytes(sData));
}
private void Text_Chat_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
string str = this.Text_Chat.Text.Replace(Environment.NewLine, "");
this.Text_Chat.Text = "";
Send(MyNick + " : " + str + Environment.NewLine);
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
socket.Close();
System.Environment.Exit(0);
}
private void Button_Exit_Click(object sender, EventArgs e)
{
socket.Close();
System.Environment.Exit(0);
}
private void MusicList_DoubleClick(object sender, EventArgs e)
{
ToSocket("#requestmusic#" + MusicList.GetItemText(MusicList.SelectedItem));
}
private void Button_MusicAdd_Click(object sender, EventArgs e)
{
AddForm addform = new AddForm();
addform.socket = socket;
addform.ShowDialog();
}
private void MusicList_SelectedIndexChanged(object sender, EventArgs e)
{
}
private void PlayTick_Tick(object sender, EventArgs e)
{
if (Player.playState.ToString() == "wmppsPlaying")
{
current_sec++;
PlayProgressBar.Value++;
if (current_sec == 60)
{
current_min++;
current_sec = 0;
}
playLabel.Text = current_min.ToString() + ":" + current_sec.ToString() + " / " + duration_min.ToString() + ":" + duration_sec.ToString();
if (PlayProgressBar.Value == PlayProgressBar.Maximum)
{
Player.controls.stop();
playingLabel.Text = "Not Playing";
playLabel.Text = "0:00 / 0:00";
PlayProgressBar.Value = 0;
PlayTick.Enabled = false;
}
}
}
}
}
Addform and Enterform are not the problem, I think. I've tested.
I wasn't able to get your code to compile when I copied it in to a new project, so I'm not sure. One thing is for sure though, you have to many while loops taking place here. I would recommend replacing those with the async pattern that the Socket class has available. It would remove all of the while loops. A little bit of extra abstraction would help as well.
I have broken the server up in to its own class, then provided each Socket connection a wrapper class that the server will hold a reference to.
Note that I am using Visual Studio 2015, so my string formatting is using the new C# 6.0 style. If you can not use C# 6, it is easy enough to convert back to string.Format("{0}", foo);
Server Status
We need to have a way of checking what the current server status is. This can be as simple as a small enum.
/// <summary>
/// Provides status values for the server to use, indicating its current state.
/// </summary>
public enum ServerStatus
{
/// <summary>
/// The server has stopped.
/// </summary>
Stopped,
/// <summary>
/// Server is in the process of starting.
/// </summary>
Starting,
/// <summary>
/// Server is up and running.
/// </summary>
Running
}
ConnectedArgs
Next, the Server class needs to raise some events so that the Form code-behind can be told when a client connects, and when the client has sent the server a message. We will provide a class called ConnectedArgs as our event handler argument. This class will hold a reference to our actual client, using the wrapper class we will create next.
public class ConnectedArgs : EventArgs
{
/// <summary>
/// Instances a new ConnectedArgs class.
/// </summary>
/// <param name="state">The state of a client connection.</param>
public ConnectedArgs(ConnectionState state)
{
this.ConnectedClient = state;
}
/// <summary>
/// Gets the client currently connected.
/// </summary>
public ConnectionState ConnectedClient { get; private set; }
}
ConnectionState
This class is responsible for holding the Socket associated with the connected client, and handle receiving the clients message data asynchronously. This uses the BeginInvoke and EndInvoke async pattern included with the Socket.
This class will have an event that will be used to notify the Form that a new message was received. Note that this was pulled from one of my existing projects, so the data parsing basically checks the buffer and if the buffer does not include a \r\n, it is considered incomplete. It caches it and waits for the next chunk of data from the client to process and try and complete. You will want to replace the ProcessReceivedData method with your custom method that handles processing the received data. When you are done, just push the results in to the OnDataReceived method so your Form can be given it.
public sealed class ConnectionState
{
/// <summary>
/// The size of the buffer that will hold data sent from the client
/// </summary>
private readonly int bufferSize;
/// <summary>
/// A temporary collection of incomplete messages sent from the client. These must be put together and processed.
/// </summary>
private readonly List<string> currentData = new List<string>();
/// <summary>
/// What the last chunk of data sent from the client contained.
/// </summary>
private string lastChunk = string.Empty;
/// <summary>
/// Instances a new PlayerConnectionState.
/// </summary>
/// <param name="player">An instance of a Player type that will be performing network communication</param>
/// <param name="currentSocket">The Socket used to communicate with the client.</param>
/// <param name="bufferSize">The storage size of the data buffer</param>
public ConnectionState(Socket currentSocket, int bufferSize)
{
this.CurrentSocket = currentSocket;
this.bufferSize = bufferSize;
this.Buffer = new byte[bufferSize];
}
/// <summary>
/// This event is raised when the server has received new, valid, data from the client.
/// </summary>
public event EventHandler<string> DataReceived;
/// <summary>
/// Gets the Socket for the player associated with this state.
/// </summary>
public Socket CurrentSocket { get; private set; }
/// <summary>
/// Gets the data currently in the network buffer
/// </summary>
public byte[] Buffer { get; private set; }
/// <summary>
/// Gets if the current network connection is in a valid state.
/// </summary>
public bool IsConnectionValid
{
get
{
return this.CurrentSocket != null && this.CurrentSocket.Connected;
}
}
/// <summary>
/// Starts listening for network communication sent from the client to the server
/// </summary>
public void StartListeningForData()
{
this.Buffer = new byte[bufferSize];
this.CurrentSocket.BeginReceive(this.Buffer, 0, bufferSize, 0, new AsyncCallback(this.ReceiveData), null);
}
/// <summary>
/// Receives the input data from the user.
/// </summary>
/// <param name="result">The result.</param>
private void ReceiveData(IAsyncResult result)
{
// If we are no longer in a valid state, dispose of the connection.
if (!this.IsConnectionValid)
{
this.CurrentSocket?.Dispose();
return;
}
int bytesRead = this.CurrentSocket.EndReceive(result);
if (bytesRead == 0 || !this.Buffer.Any())
{
this.StartListeningForData();
return;
}
ProcessReceivedData(bytesRead);
this.StartListeningForData();
}
/// <summary>
/// Process the data we received from the client.
/// </summary>
/// <param name="bytesRead"></param>
private void ProcessReceivedData(int bytesRead)
{
// Encode our input string sent from the client
this.lastChunk = Encoding.ASCII.GetString(this.Buffer, 0, bytesRead);
// If the previous chunk did not have a new line feed, then we add this message to the collection of currentData.
// This lets us build a full message before processing it.
if (!lastChunk.Contains("\r\n"))
{
// Add this to our incomplete data stash and read again.
this.currentData.Add(lastChunk);
return;
}
// This message contained at least 1 new line, so we split it and process per line.
List<string> messages = lastChunk.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();
foreach (string line in this.PruneReceivedMessages(messages))
{
this.OnDataReceived(line);
}
}
/// <summary>
/// Runs through the messages collection and prepends data from a previous, incomplete, message
/// and updates the internal message tracking state.
/// </summary>
/// <param name="messages"></param>
private List<string> PruneReceivedMessages(List<string> messages)
{
// Append the first line to the incomplete line given to us during the last pass if one exists.
if (this.currentData.Any() && messages.Any())
{
messages[0] = string.Format("{0} {1}", string.Join(" ", this.currentData), messages[0]);
this.currentData.Clear();
}
// If we have more than 1 line and the last line in the collection does not end with a line feed
// then we add it to our current data so it may be completed during the next pass.
// We then remove it from the lines collection because it can be infered that the remainder will have
// a new line due to being split on \n.
if (messages.Count > 1 && !messages.Last().EndsWith("\r\n"))
{
this.currentData.Add(messages.Last());
messages.Remove(messages.Last());
}
return messages;
}
private void OnDataReceived(string data)
{
var handler = this.DataReceived;
if (handler == null)
{
return;
}
handler(this, data);
}
}
Server
Now that we have the client async receiving and processing of data, we need to write the server component to actually accept incoming Socket connections asynchronously.
This class will have two events that the Form will subscribe to. One for when a client connects, and another for when the client disconnects. Each client that is connected, is assigned a ConnectionState and cached in a collection of List<ConnectionState>. This removes the need for you to keep a fragile counter of the number of clients connected.
You could optionally wire up a timer that periodically prunes the List<ConnectionState> collection. You can check if each instance in the collection has its IsConnectionValid set to true. If it returns false, remove it from the collection.
/// <summary>
/// The Default Desktop game Server
/// </summary>
public sealed class Server
{
/// <summary>
/// The user connection buffer size
/// </summary>
private const int UserConnectionBufferSize = 1024;
/// <summary>
/// The server socket
/// </summary>
private Socket serverSocket;
/// <summary>
/// The player connections
/// </summary>
private List<ConnectionState> connectedClients;
/// <summary>
/// Used for making access to the connectedClients collection thread-safe
/// </summary>
private object lockObject = new object();
/// <summary>
/// Initializes a new instance of the <see cref="Server"/> class.
/// </summary>
public Server()
{
this.Status = ServerStatus.Stopped;
this.connectedClients = new List<ConnectionState>();
}
/// <summary>
/// Occurs when a client connects to the server.
/// </summary>
public event EventHandler<ConnectedArgs> ClientConnected;
/// <summary>
/// Occurs when a client is disconnected from the server.
/// </summary>
public event EventHandler<ConnectedArgs> ClientDisconnected;
/// <summary>
/// Gets or sets the port that the server is running on.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Gets or sets the maximum queued connections.
/// </summary>
public int MaxQueuedConnections { get; set; }
/// <summary>
/// Gets the current server status.
/// </summary>
public ServerStatus Status { get; private set; }
public void Start()
{
if (this.Status != ServerStatus.Stopped)
{
throw new InvalidOperationException("The server is either starting or already running. You must stop the server before starting it again.");
}
else if (this.Port == 0)
{
throw new InvalidOperationException("You can not start the server on Port 0.");
}
this.Status = ServerStatus.Starting;
// Get our server address information
IPHostEntry serverHost = Dns.GetHostEntry(Dns.GetHostName());
var serverEndPoint = new IPEndPoint(IPAddress.Any, this.Port);
// Instance the server socket, bind it to a port.
this.serverSocket = new Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
this.serverSocket.Bind(serverEndPoint);
this.serverSocket.Listen(this.MaxQueuedConnections);
// Begin listening for connections.
this.serverSocket.BeginAccept(new AsyncCallback(this.ConnectClient), this.serverSocket);
this.Status = ServerStatus.Running;
}
/// <summary>
/// Stops the server.
/// </summary>
public void Stop()
{
this.DisconnectAll();
// We test to ensure the server socket is still connected and active.
this.serverSocket.Blocking = false;
try
{
this.serverSocket.Send(new byte[1], 0, 0);
// Message was received meaning it's still receiving, so we can safely shut it down.
this.serverSocket.Shutdown(SocketShutdown.Both);
}
catch (SocketException e)
{
// Error code 10035 indicates it works, but will block the socket.
// This means it is still receiving and we can safely shut it down.
// Otherwise, it's not receiving anything and we don't need to shut down.
if (e.NativeErrorCode.Equals(10035))
{
this.serverSocket.Shutdown(SocketShutdown.Both);
}
}
finally
{
this.Status = ServerStatus.Stopped;
}
}
/// <summary>
/// Disconnects the specified IServerPlayer object.
/// </summary>
/// <param name="connection">The client to disconnect.</param>
public void Disconnect(ConnectionState connection)
{
if (connection != null && connection.IsConnectionValid)
{
connection.CurrentSocket.Shutdown(SocketShutdown.Both);
this.connectedClients.Remove(connection);
this.OnClientDisconnected(connection);
}
}
/// <summary>
/// Disconnects everyone from the server.
/// </summary>
public void DisconnectAll()
{
// Loop through each connection and disconnect them.
foreach (ConnectionState state in this.connectedClients)
{
Socket connection = state.CurrentSocket;
if (connection != null && connection.Connected)
{
connection.Shutdown(SocketShutdown.Both);
this.OnClientDisconnected(state);
}
}
this.connectedClients.Clear();
}
/// <summary>
/// Called when a client connects.
/// </summary>
private void OnClientConnected(ConnectionState connection)
{
EventHandler<ConnectedArgs> handler = this.ClientConnected;
if (handler == null)
{
return;
}
handler(this, new ConnectedArgs(connection));
}
/// <summary>
/// Called when a client disconnects.
/// </summary>
private void OnClientDisconnected(ConnectionState connection)
{
EventHandler<ConnectedArgs> handler = this.ClientDisconnected;
if (handler == null)
{
return;
}
handler(this, new ConnectedArgs(connection));
}
/// <summary>
/// Connects the client to the server and then passes the connection responsibilities to the client object.
/// </summary>
/// <param name="result">The async result.</param>
private void ConnectClient(IAsyncResult result)
{
// Connect and register for network related events.
Socket connection = this.serverSocket.EndAccept(result);
// Send our greeting
byte[] buffer = Encoding.ASCII.GetBytes("Welcome to the Music App Server!");
connection.BeginSend(buffer, 0, buffer.Length, 0, new AsyncCallback(asyncResult => connection.EndReceive(asyncResult)), null);
// Fetch the next incoming connection.
this.serverSocket.BeginAccept(new AsyncCallback(this.ConnectClient), this.serverSocket);
this.CompleteClientSetup(new ConnectionState(connection, UserConnectionBufferSize));
}
/// <summary>
/// Caches the ConnectionState and has the state begin listening to client data.
/// </summary>
/// <param name="connectionState"></param>
private void CompleteClientSetup(ConnectionState connectionState)
{
lock (this.lockObject)
{
this.connectedClients.Add(connectionState);
}
// Start receiving data from the client.
connectionState.StartListeningForData();
this.OnClientConnected(connectionState);
}
}
Form
Now that we have our server code put together, the Form can be constructed. I just did a simple form, with a single button for connecting and a single multi-line textbox for viewing the data.
I added an event handler for the Form's Loading event and for the Button's Clicked event.
public partial class Form1 : Form
{
private Server server;
public Form1()
{
InitializeComponent();
server = new Server { Port = 9180, MaxQueuedConnections = 100 };
}
private void Form1_Load(object sender, EventArgs e)
{
server.ClientConnected += OnClientConnected;
server.ClientDisconnected += OnClientDisconnected;
}
private void OnClientDisconnected(object sender, ConnectedArgs e)
{
this.Invoke(new Action(() => this.textBox1.AppendText("A Client disconnected.\n")));
}
private void OnClientConnected(object sender, ConnectedArgs e)
{
this.Invoke(new Action(() =>
{
this.textBox1.AppendText("New Client Connected.\n");
e.ConnectedClient.DataReceived += OnClientSentDataToServer;
}));
}
private void OnClientSentDataToServer(object sender, string e)
{
this.Invoke(new Action(() => this.textBox1.AppendText($"{e}\n")));
}
private void button1_Click(object sender, EventArgs e)
{
this.textBox1.AppendText("Server starting.\n");
server.Start();
this.textBox1.AppendText("Server running.\n");
}
}
Due to the server running as a background process, you have to marshall the event handlers back to the UI thread when accessing the TextBox. You could abstract that piece out a bit as well, so that the Server and ConnectionState objects always push to the dispatcher you provide them. This reduces the number of Invoke(Action) invocations you have to do.
This works well with multiple connections. I started the server up, and then spun up two different connections across a couple different computers without any issues. You will need to customize the data processing and pushing messages to the client. This should at least solve your multiple connection issue, and be less stress on the CPU (no more while loops!).
Hope this helps.
I have an application which simply pulls items from a queue and then attempts to send them asynchronously via a network socket.
I am experiencing some issues when things go wrong or the client aborts the host socket.
Here is some of my code: I think it may explain better than my words:
Here is my SocketState.cs class which holds the socket and related info:
public class SocketState
{
public const int BufferSize = 256;
public Socket WorkSocket { get; set; }
public byte[] Buffer { get; set; }
/// <summary>
/// Constructor receiving a socket
/// </summary>
/// <param name="socket"></param>
public SocketState(Socket socket)
{
WorkSocket = socket;
Buffer = new byte[BufferSize];
}
}
Here is my SocketHandler.cs class which controls most of the socket operations:
public class SocketHandler : IObserver
{
# region Class Variables
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private SocketState _state;
private OutgoingConnectionManager _parentConnectionManager;
private int _recieverId;
private readonly ManualResetEvent _sendDone = new ManualResetEvent(false);
public volatile bool NameIsSet = false;
private ManualResetEvent _receiveDone = new ManualResetEvent(false);
public String Name;
public readonly Guid HandlerId;
# endregion Class Variables
/// <summary>
/// Constructor
/// </summary>
public SocketHandler(SocketState state)
{
HandlerId = Guid.NewGuid();
_state = state;
_state.WorkSocket.BeginReceive(_state.Buffer, 0, SocketState.BufferSize, 0, new AsyncCallback(ReceiveCallback), this._state);
}
/// <summary>
/// Set the receiver id for this socket.
/// </summary>
public void SetReceiverId(int receiverId)
{
_recieverId = receiverId;
}
/// <summary>
/// Stops / closes a connection
/// </summary>
public void Stop()
{
CloseConnection();
}
/// <summary>
/// Used to set this connections parent connection handler
/// </summary>
/// <param name="conMan"></param>
public void SetConnectionManager(OutgoingConnectionManager conMan)
{
_parentConnectionManager = conMan;
}
/// <summary>
/// Returns if the socket is connected or not
/// </summary>
/// <returns></returns>
public bool IsConnected()
{
return _state.WorkSocket.Connected;
}
/// <summary>
/// Public Class that implements the observer interface , this function provides a portal to receive new messages which it must send
/// </summary>
/// <param name="e"> Event to execute</param>
public void OnMessageRecieveEvent(ObserverEvent e)
{
SendSignalAsync(e.Message.payload);
}
# region main working space
# region CallBack Functions
/// <summary>
/// CallBack Function that is called when a connection Receives Some Data
/// </summary>
/// <param name="ar"></param>
private void ReceiveCallback(IAsyncResult ar)
{
try
{
String content = String.Empty;
if (ar != null)
{
SocketState state = (SocketState)ar.AsyncState;
if (state != null)
{
Socket handler = state.WorkSocket;
if (handler != null)
{
int bytesRead = handler.EndReceive(ar);
if (bytesRead > 0)
{
StringBuilder Sb = new StringBuilder();
Sb.Append(Encoding.Default.GetString(state.Buffer, 0, bytesRead));
if (Sb.Length > 1)
{
content = Sb.ToString();
foreach (var s in content.Split('Ÿ'))
{
if (string.Compare(s, 0, "ID", 0, 2) == 0)
{
Name = s.Substring(2);
NameIsSet = true;
}
if (string.Compare(s, 0, "TG", 0, 2) == 0)
{
LinkReplyToTag(s.Substring(2), this.Name);
}
}
_state.WorkSocket.BeginReceive(_state.Buffer, 0, SocketState.BufferSize, 0,
new AsyncCallback(ReceiveCallback), _state);
}
}
}
}
}
}
catch
{
CloseConnection();
}
}
/// <summary>
/// Call Back Function called when data is send though this connection
/// </summary>
/// <param name="asyncResult"></param>
private void SendCallback(IAsyncResult asyncResult)
{
try
{
if (asyncResult != null)
{
Socket handler = (Socket)asyncResult.AsyncState;
if (handler != null)
{
int bytesSent = handler.EndSend(asyncResult);
// Signal that all bytes have been sent.
_sendDone.Set();
if (bytesSent > 0)
{
return;
}
}
}
}
catch (Exception e)
{
Log.Error("Transmit Failed On Send CallBack");
}
//Close socket as something went wrong
CloseConnection();
}
# endregion CallBack Functions
/// <summary>
/// Sends a signal out of the current connection
/// </summary>
/// <param name="signal"></param>
private void SendSignalAsync(Byte[] signal)
{
try
{
if (_state != null)
{
if (_state.WorkSocket != null)
{
if (_state.WorkSocket.Connected)
{
try
{
_sendDone.Reset();
_state.WorkSocket.BeginSend(signal, 0, signal.Length, 0, new AsyncCallback(SendCallback),
_state.WorkSocket);
_sendDone.WaitOne(200);
return;
}
catch (Exception e)
{
Log.Error("Transmission Failier for IP: " + ((IPEndPoint)_state.WorkSocket.RemoteEndPoint).Address, e);
}
}
}
}
//Close Connection as something went wrong
CloseConnection();
}
catch (Exception e)
{
Log.Error("An Exception has occurred in the SendSignalAsync function", e);
}
}
/// <summary>
/// Call this to Close the connection
/// </summary>
private void CloseConnection()
{
try
{
var ip = "NA";
try
{
if (_state != null)
{
ip = ((IPEndPoint)_state.WorkSocket.RemoteEndPoint).Address.ToString();
}
}
catch
{
//Cannot get the ip address
}
OutgoingListeningServer.UpdateRecieversHistory(_recieverId, ip, "Disconnected");
try
{
if (_state != null)
{
if (_state.WorkSocket != null)
{
_state.WorkSocket.Shutdown(SocketShutdown.Both);
_state.WorkSocket.Close();
//_state.WorkSocket.Dispose();
_state.WorkSocket = null;
_state = null;
}
}
}
catch (Exception e)
{
_state = null;
Log.Error("Error while trying to close socket for IP: " + ip, e);
}
if (_parentConnectionManager != null)
{
// Remove this connection from the connection list
_parentConnectionManager.ConnectionRemove(this);
}
}
catch (Exception e)
{
Log.Error("A major error occurred in the close connection function, outer try catch was hit", e);
}
}
# endregion main working space
}
And here is my thread which will then call the OnMessageRecieveEvent() function in the SocketHandler.cs class.
private void Main()
{
Log.Info("Receiver" + ReceiverDb.Id + " Thread Starting");
// Exponential back off
var eb = new ExponentialBackoff();
try
{
while (_run)
{
try
{
if (ReceiverOutgoingConnectionManager.HasConnectedClient())
{
//Fetch Latest Item
ILogItem logItem;
if (_receiverQueue.TryDequeue(out logItem))
{
//Handle the logItem
**calls the OnMessageRecieveEvent() for all my connections.
ReceiverOutgoingConnectionManager.SendItemToAllConnections(logItem);
//Reset the exponential back off counter
eb.reset();
}
else
{
//Exponential back off thread sleep
eb.sleep();
}
}
else
{
//Exponential back off thread sleep
eb.sleep();
}
}
catch (Exception e)
{
Log.Error("Error occurred in " + ReceiverDb.Id + "receiver mains thread ", e);
}
}
}
catch (Exception e)
{
Log.Error(" ** An error has occurred in a receiver holder main thread ** =====================================================================>", e);
}
Log.Info("Receiver" + ReceiverDb.Id + " Thread Exiting ** ===============================================================================>");
}
I apologize for so much code. But I fear that it might not be something obvious so I posted all related code.
Now to further explain my issue.
If an error happens on the socket. I get allot of Transmit Failed On Send CallBack. which to me means that i'm not closing the socket correctly, and there are still outstanding callback being executed.
Is there no way I can cancel all outstanding callback when i close the socket?
I am also sure there will be a few suggestions / issues with my code I posted. I would more than appreciate the feedback.
I'm going to assume this is for learning purposes, since writing your own networking code for other purposes is somewhat... hard.
Getting an exception in the send callback is fine. That's what exceptions are for. However, you need to identify the exception, rather than just catching the blanket Exception and pretty much ignoring all the information inside.
Handle the exceptions you can handle in the places where it's proper for them to be handled.
I'm not going to be very specific, because there's a lot of issues with your code. But one of the key things is ignoring proper socket handling - when you receive 0 bytes, it means you're supposed to close the socket. That's the signal from the other side saying "we're done". By ignoring this, you're working with a (perhaps partially) closed connection.
Please, use some networking library that can give you the guarantees (and simplicity) you need. WCF, Lindgren, whatever. TCP doesn't guarantee you'll get the message in one part, for example, so your message parsing code is unreliable. You need to use some message framing, you need to add proper error handling... Socket isn't a high-level construct, it doesn't work "automagically", you need to implement all this stuff on your own.
Even ignoring the network code itself, it's obvious you're just ignoring most of the complexities of asynchronous code. What's up with the SendDone? Either you want to use asynchronous code, and then get rid of the synchronicity, or you want synchronous code, and then why are you using asynchronous sockets?