I have a bunch of async methods that I want to expose via C# sockets. The general pattern in the MSDN documentaions Has the following form:
public static void StartListening()
{
...
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);
...
listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
...
}
public static void AcceptCallback(IAsyncResult ar)
{
...
handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}
public static void ReadCallback(IAsyncResult ar)
{
...
StateObject state = (StateObject) ar.AsyncState;
...
CalculateResult(state);
...
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
...
}
So writing all this in a nice and clean form without repetition of code has been a challenge. I am thinking along these lines but have not been able to connect the dots:
public static void StartListeningMaster()
{
string ipAddress = "localhost";
IPHostEntry ipHost = Dns.GetHostEntry(ipAddress);
IPAddress address = ipHost.AddressList[0];
StartListening(50000, address, AcceptCallback1);
StartListening(50001, address, AcceptCallback2);
StartListening(50002, address, AcceptCallback3);
...
}
public static void StartListening(int port, IPAddress ipAddress,
Action<IAsyncResult> acceptCallback) {...}
public static void AcceptCallback1(IAsyncResult ar)
{
...
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize,
0, new AsyncCallback1(ReadCallback1), state);
}
...
This works fine up to this point. But to refactor it properly I would like to have one AcceptCallback method that takes as its parameter a generic ReadCallback that takes as its parameter a CalculateResult method. This way I would not have any repetition of code. However, if I modify my AcceptCallback method to take any more parameters than IAsyncResult (for example something like:
public static void StartListening(int port, IPAddress ipAddress, Action<IAsyncResult, Action<IAsyncResult>> acceptCallback) {...}
public static void AcceptCallback(IAsyncResult ar, Action<IAsyncResult> readCallback) {}
I break the AsyncCallback delegate contract.
public delegate void AsyncCallback(IAsyncResult ar);
Then I looked into extending the existing interfaces to allow the functionality. I looked into extending
public interface IAsyncResult
But that does not seem to be the right approach either. So, how do I write this code so I do not copy and paste pretty much the same code all over the place?
So the way I tackle this is by moving the basic components in to their own abstract objects. Then build upon those objects. For example, the server only needs to accept/track connections. So I would make a server object that looks something like this:
namespace MultiServerExample.Base
{
public interface IAsyncServerBase
{
void StartListening();
bool IsListening { get; }
void StopListening();
void WriteDataToAllClients(byte[] data);
}
public abstract class AsyncServerBase<TClientBase> : IAsyncServerBase
where TClientBase : IAsyncClientBase, new()
{
// implement a TcpListener to gain access to Active property
private sealed class ActiveTcpListener : TcpListener
{
public ActiveTcpListener(IPAddress localaddr, int port)
: base(localaddr, port) { }
public bool IsActive => Active;
}
// our listener object
private ActiveTcpListener Listener { get; }
// our clients
private ConcurrentDictionary<string, TClientBase> Clients { get; }
// construct with a port
public AsyncServerBase(int port)
{
Clients = new ConcurrentDictionary<string, TClientBase>();
Listener = new ActiveTcpListener(IPAddress.Any, port);
}
// virtual methods for client action
public virtual void OnClientConnected(TClientBase client) { }
public virtual void OnClientDisconnected(TClientBase client, Exception ex) { }
// start the server
public void StartListening()
{
if(!IsListening)
{
Listener.Start();
Listener.BeginAcceptTcpClient(OnAcceptedTcpClient, this);
}
}
// check if the server is running
public bool IsListening =>
Listener.IsActive;
// stop the server
public void StopListening()
{
if (IsListening)
{
Listener.Stop();
Parallel.ForEach(Clients, x => x.Value.DetachClient(null));
Clients.Clear();
}
}
// async callback for when a client wants to connect
private static void OnAcceptedTcpClient(IAsyncResult res)
{
var me = (AsyncServerBase<TClientBase>)res.AsyncState;
if (!me.IsListening) { return; }
try
{
TcpClient client = null;
try
{
client = me.Listener.EndAcceptTcpClient(res);
}
catch(Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Warning: unable to accept client:\n{ex}");
}
if(client != null)
{
// create a new client
var t = new TClientBase();
// set up error callbacks
t.Error += me.OnClientBaseError;
// notify client we have attached
t.AttachClient(client);
// track the client
me.Clients[t.Id] = t;
// notify we have a new connection
me.OnClientConnected(t);
}
}
finally
{
// if we are still listening, wait for another connection
if(me.IsListening)
{
me.Listener.BeginAcceptSocket(OnAcceptedTcpClient, me);
}
}
}
// Event callback from a client that an error has occurred
private void OnClientBaseError(object sender, AsyncClientBaseErrorEventArgs e)
{
var client = (TClientBase)sender;
client.Error -= OnClientBaseError;
OnClientDisconnected(client, e.Exception);
client.DetachClient(e.Exception);
Clients.TryRemove(client.Id, out _);
}
// utility method to write data to all clients connected
public void WriteDataToAllClients(byte[] data)
{
Parallel.ForEach(Clients, x => x.Value.WriteData(data));
}
}
}
At this point all the basics of running a server have been accounted for. Now for the client that runs on the server:
namespace MultiServerExample.Base
{
public interface IAsyncClientBase
{
event EventHandler<AsyncClientBaseErrorEventArgs> Error;
void AttachClient(TcpClient client);
void WriteData(byte[] data);
void DetachClient(Exception ex);
string Id { get; }
}
public abstract class AsyncClientBase : IAsyncClientBase
{
protected virtual int ReceiveBufferSize { get; } = 1024;
private TcpClient Client { get; set; }
private byte[] ReceiveBuffer { get; set; }
public event EventHandler<AsyncClientBaseErrorEventArgs> Error;
public string Id { get; }
public AsyncClientBase()
{
Id = Guid.NewGuid().ToString();
}
public void AttachClient(TcpClient client)
{
if(ReceiveBuffer != null) { throw new InvalidOperationException(); }
ReceiveBuffer = new byte[ReceiveBufferSize];
Client = client;
try
{
Client.GetStream().
BeginRead(ReceiveBuffer, 0, ReceiveBufferSize, OnDataReceived, this);
OnAttachedToServer();
}
catch (Exception ex)
{
Error?.Invoke(this,
new AsyncClientBaseErrorEventArgs(ex, "BeginRead"));
}
}
public void DetachClient(Exception ex)
{
try
{
Client.Close();
OnDetachedFromServer(ex);
}
catch { /* intentionally swallow */ }
Client = null;
ReceiveBuffer = null;
}
public virtual void OnDataReceived(byte[] buffer) { }
public virtual void OnAttachedToServer() { }
public virtual void OnDetachedFromServer(Exception ex) { }
public void WriteData(byte[] data)
{
try
{
Client.GetStream().BeginWrite(data, 0, data.Length, OnDataWrote, this);
}
catch(Exception ex)
{
Error?.Invoke(this, new AsyncClientBaseErrorEventArgs(ex, "BeginWrite"));
}
}
private static void OnDataReceived(IAsyncResult iar)
{
var me = (AsyncClientBase)iar.AsyncState;
if(me.Client == null) { return; }
try
{
var bytesRead = me.Client.GetStream().EndRead(iar);
var buf = new byte[bytesRead];
Array.Copy(me.ReceiveBuffer, buf, bytesRead);
me.OnDataReceived(buf);
}
catch (Exception ex)
{
me.Error?.Invoke(me, new AsyncClientBaseErrorEventArgs(ex, "EndRead"));
}
}
private static void OnDataWrote(IAsyncResult iar)
{
var me = (AsyncClientBase)iar.AsyncState;
try
{
me.Client.GetStream().EndWrite(iar);
}
catch(Exception ex)
{
me.Error?.Invoke(me,
new AsyncClientBaseErrorEventArgs(ex, "EndWrite"));
}
}
}
}
Now all your base code is written. You don't need to change this in any way. You simply implement your own client and server to respond accordingly. For example, here is a basic server implementation:
public class MyServer : AsyncServerBase<MyClient>
{
public MyServer(int port) : base(port)
{
}
public override void OnClientConnected(MyClient client)
{
Console.WriteLine($"* MyClient connected with Id: {client.Id}");
base.OnClientConnected(client);
}
public override void OnClientDisconnected(MyClient client, Exception ex)
{
Console.WriteLine($"***** MyClient disconnected with Id: {client.Id} ({ex.Message})");
base.OnClientDisconnected(client, ex);
}
}
And here is a client that the server above uses for communication:
public class MyClient : AsyncClientBase
{
public override void OnAttachedToServer()
{
base.OnAttachedToServer();
Console.WriteLine($"{Id}: {GetType().Name} attached. Waiting for data...");
}
public override void OnDataReceived(byte[] buffer)
{
base.OnDataReceived(buffer);
Console.WriteLine($"{Id}: {GetType().Name} recieved {buffer.Length} bytes. Writing 5 bytes back.");
WriteData(new byte[] { 1, 2, 3, 4, 5 });
}
public override void OnDetachedFromServer(Exception ex)
{
base.OnDetachedFromServer(ex);
Console.WriteLine($"{Id}: {GetType().Name} detached.");
}
}
And to drive the point home, here is another client that simply would plug in to the same server implementation, but gives it different characteristics:
public class MyOtherClient : AsyncClientBase
{
public override void OnAttachedToServer()
{
base.OnAttachedToServer();
Console.WriteLine($"{Id}: {GetType().Name} attached. Writing 4 bytes back.");
WriteData(new byte[] { 1, 2, 3, 4 });
}
public override void OnDataReceived(byte[] buffer)
{
base.OnDataReceived(buffer);
Console.WriteLine($"{Id}: {GetType().Name} recieved {buffer.Length} bytes.");
}
public override void OnDetachedFromServer(Exception ex)
{
base.OnDetachedFromServer(ex);
Console.WriteLine($"{Id}: {GetType().Name} detached.");
}
}
As far as using this, here is a small test program that puts it through a stress-test:
class Program
{
static void Main(string[] args)
{
var servers = new IAsyncServerBase[]
{
new MyServer(50000),
new MyServer(50001),
new MyOtherServer(50002)
};
foreach (var s in servers)
{
s.StartListening();
}
RunTestUsingMyServer("1", 89, 50000);
RunTestUsingMyServer("2", 127, 50001);
RunTestUsingMyOtherServer("3", 88, 50002);
Console.Write("Press any key to exit... ");
Console.ReadKey(true);
foreach (var s in servers)
{
s.WriteDataToAllClients(new byte[] { 1, 2, 3, 4, 5 });
s.StopListening();
}
}
private static void RunTestUsingMyServer(string name, int clientCount, int port)
{
Parallel.For(0, clientCount, x =>
{
using (var t = new TcpClient())
{
t.Connect(IPAddress.Loopback, port);
t.GetStream().Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5);
t.GetStream().Read(new byte[512], 0, 512);
t.Close();
}
Console.WriteLine($"FINISHED PASS {name} #{x}");
});
}
private static void RunTestUsingMyOtherServer(string name, int clientCount, int port)
{
Parallel.For(0, clientCount, x =>
{
using (var t = new TcpClient())
{
t.Connect(IPAddress.Loopback, port);
t.GetStream().Read(new byte[512], 0, 512);
t.GetStream().Write(new byte[] { 1, 2, 3, 4, 5, 6 }, 0, 6);
t.Close();
}
Console.WriteLine($"FINISHED PASS {name} #{x}");
});
}
}
If interested, here is the full source code you can check out. Hopefully this gets you to where you want to be as it pertains to reusing code.
I don't know if this can help. You can define a state object with all the information related to every port:
public class StateObject
{
public string Name;
public Socket Listener;
public IPEndPoint LocalEndPoint;
//...
public StateObject(Socket listener, IPEndPoint endPoint, string name)
{
Listener = listener;
LocalEndPoint = endPoint;
Name = name;
}
}
Then, you can use it as you need:
public static void StartListeningMaster()
{
string ipAddress = "localhost";
IPHostEntry ipHost = Dns.GetHostEntry(ipAddress);
IPAddress address = ipHost.AddressList[0];
StartListening(50000, address, "Main port");
StartListening(50001, address, "Service port");
StartListening(50002, address, "Extra");
//...
}
public static void StartListening(int port, IPAddress ipAddress, string name = "")
{
//...
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);
//...
StateObject state = new StateObject(listener, localEndPoint);
listener.BeginAccept(AcceptCallback, state);
//...
}
public static void AcceptCallback(IAsyncResult ar)
{
StateObject state = (StateObject)ar.AsyncState;
//...
handler.BeginReceive(client.buffer, 0, StateObject.BufferSize,
0, new AsyncCallback(ReadCallback), state);
}
public static void ReadCallback(IAsyncResult ar)
{
StateObject state = (StateObject)ar.AsyncState;
// Always have the info related to every socket
Socket listener = state.Listener;
string address = state.LocalEndPoint.Address.ToString();
int port = state.LocalEndPoint.Port;
string name = state.Name;
//...
StateObject state = (StateObject)ar.AsyncState;
//...
CalculateResult(state);
//...
handler.BeginReceive(client.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
//...
}
The CalculateResult(state) method will have all the necessary info to do whatever. This way, you only have one StartListening(), one AcceptCallback() and one ReadCallback() for all the ports to manage.
I build a middle ware server.
I have 5(or 6) clients(different computer/IP address), and every client can have up to 6 connection -> max 36 connection to the server.
It is better to use only 1 port for all (e.g. 9000) or one different port for every client(e.g. 9000-9005) ?
I write me a simple test server(see code).
It worked like expected but maybe there is some room for optimization.
Thanks
My TCP server
public class TCPServerMulti : IDisposable
{
private static TcpListener _tcpListener;
private string _ipAdress = "";
private int _port = 9050;
private readonly Thread _listener;
private readonly bool _running;
private int cc = 0; //Test Counter
public string IPAdress
{
get { return _ipAdress; }
set { _ipAdress = value; }
}
public TCPServerMulti(int port)
{
if(port>0)
_port = port;
_running = true;
string sHostName = Dns.GetHostName();
IPAdress = "";
//Only use the first address -> need to change
IPAddress[] localAddress = Dns.GetHostAddresses(sHostName);
foreach (IPAddress ipAddress in localAddress)
{
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
_tcpListener = new TcpListener(ipAddress, _port);
IPAdress = ipAddress.ToString();
break;
}
}
if (IPAdress == "")
{
Console.Writeline("TCP Error");
return;
}
_tcpListener.Start();
_listener = new Thread(Listen) { IsBackground = true };
_listener.Start();
}
public TCPServerMulti():this(-1)
{}
//listening for clients
public void Listen()
{
try
{
while(_running)
{
cc++;
//create a own client object for every connection
TCPClientData cd = new TCPClientData(this, ""+cc, _tcpListener.AcceptTcpClient());
cd.StartClient();
if (ClientConnected != null) ClientConnected("Connected: " + cc);
}
_tcpListener.Stop();
}
catch (Exception)
{
}
}
public void Dispose()
{
_listener.Abort();
_tcpListener.Stop();
}
//Handle for Mainthread
internal void ReceiveClient(TCPClientData sender, string msg)
{
if (ReceiveMsg != null)
ReceiveMsg(sender, msg);
}
public delegate void ReceiveEventHandler(TCPClientData sender, string msg);
public event ReceiveEventHandler ReceiveMsg;
public delegate void ConnectedEventHandler(string msg);
public event ConnectedEventHandler ClientConnected;
}
Clientdata object
public class TCPClientData : IDisposable
{
private TCPServerMulti _master;
private byte[] _data;
private NetworkStream _networkStream;
private int _received;
private TcpClient _tcpClient;
private Thread _worker;
private bool _wrunning;
private string _id;
public TCPClientData(TCPServerMulti master,string id,TcpClient client)
{
_data = new byte[1024];
_master = master;
_id = id;
_tcpClient = client;
}
public string ID
{
get { return _id; }
}
private string ByteArrayToString(byte[] arr)
{
UTF8Encoding enc = new UTF8Encoding();
return enc.GetString(arr);
}
private byte[] StringToByteArray(string data)
{
UTF8Encoding enc = new UTF8Encoding();
return enc.GetBytes(data);
}
// Reading Data
private void Worker()
{
try
{
while (_wrunning)
{
if (_networkStream.DataAvailable)
{
_received = _networkStream.Read(_data, 0, _data.Length);
string s = ByteArrayToString(_data);
_master.ReceiveClient(this, s.Substring(0, _received));
}
else
{
Thread.Sleep(20);
}
}
_networkStream.Close();
_tcpClient.Close();
}
catch (Exception)
{ }
}
public void Dispose()
{
_worker.Abort();
_networkStream.Close();
_tcpClient.Close();
}
public void StartClient()
{
_networkStream = _tcpClient.GetStream();
_networkStream.ReadTimeout = 200;
_wrunning = true;
_worker = new Thread(Worker) { IsBackground = true };
_worker.Start();
}
public void StopClient()
{
_wrunning = false;
Thread.Sleep(700);
}
//Sending Data
public void WriteData(string msg)
{
byte[] b = StringToByteArray(msg);
_networkStream.Write(b,0,b.Length);
_networkStream.Flush();
}
}
Main Thread
static void Main(string[] args)
{
//Create Server
TCPServerPlugin.TCPServerMulti server=new TCPServerMulti(12345);
server.ClientConnected += Server_ClientConnected;
server.ReceiveMsg += Server_ReceiveMsg;
while (true)
{
}
}
//Callbacks
private static void Server_ClientConnected(string msg)
{
Console.WriteLine(msg);
}
private static void Server_ReceiveMsg(TCPClientData sender, string msg)
{
Console.WriteLine(string.Format("Sender: {0} -> MSG:{1}",sender.ID,msg));
if (msg == "!!X")
{
sender.Dispose();
sender = null;
}
else if(msg=="DATE")
sender.WriteData(DateTime.Now.ToLongDateString());
else if(msg=="TIME")
sender.WriteData(DateTime.Now.ToShortTimeString());
}
I’ve written TCP socket catcher based on server socket and events for transferring caught connections to high level.
public class TcpCatcher
{
private static readonly Exception IsStartedException = new Exception("Catcher has already been started.");
private int _port;
private int _openTimeout;
private Socket _serverSocket;
private SocketAsyncEventArgs _acceptAsyncArg;
public int Port
{
get => _port;
set
{
if (IsStarted)
throw IsStartedException;
_port = value;
}
}
public int OpenTimeout
{
get => _openTimeout;
set
{
if (IsStarted)
throw IsStartedException;
_openTimeout = value;
}
}
public bool IsStarted { get; protected set; }
public event Action<Socket> ConnectionCatched;
public event Action<string> Log;
public TcpCatcher()
{
_acceptAsyncArg = new SocketAsyncEventArgs();
_acceptAsyncArg.SetBuffer(null, 0, 0);
_acceptAsyncArg.Completed += OnAcceptSocket;
}
private void OnLog(string message)
{
if (Log != null)
Log(message);
}
private void AcceptSocket()
{
OnLog("AcceptSocket is executing");
bool isAsync;
do
{
isAsync = _serverSocket.AcceptAsync(_acceptAsyncArg);
if (!isAsync)
{
OnLog($"AcceptAsync finished Sync.");
if (_acceptAsyncArg.SocketError == SocketError.Success)
ConnectionCatched(_acceptAsyncArg.AcceptSocket);
}
} while (!isAsync);
OnLog("AcceptSocket finished");
}
private void OnAcceptSocket(object sender, SocketAsyncEventArgs asyncArg)
{
OnLog("Caught new socket!");
if (asyncArg.SocketError != SocketError.Success)
{
OnLog($"Socket has bad status:${asyncArg.SocketError}");
asyncArg.AcceptSocket?.Close();
}
else
{
ConnectionCatched(asyncArg.AcceptSocket);
}
OnLog($"Caught socket was processed");
asyncArg.AcceptSocket = null;
if (IsStarted)
AcceptSocket();
}
public void Start()
{
OnLog($"Starting");
IsStarted = true;
_serverSocket?.Close();
_serverSocket = new Socket(SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
_serverSocket.Bind(new IPEndPoint(IPAddress.Any, Port));
_serverSocket.Listen((int)SocketOptionName.MaxConnections);
AcceptSocket();
}
public void Finish()
{
OnLog($"Stopping");
_serverSocket.Disconnect(false);
_serverSocket.Close();
_serverSocket.Dispose();
_serverSocket = null;
IsStarted = false;
}
}
Catchers are used in a Windows Service and initialized when the service is started. They work about 2-4 days and stop for unknown reasons accepting sockets one by one. Can you explain why it may be and how this problem can be solved?
I trying to allow people to write to NFC tags using my app, so that my app gets launched with a custom parameter. I want to be able to reprogram NFC tags which already have data on them.
I am using the following code but the problem is, that WP always recognizes the action which is already on the NFC tag and interrupts because it wants to launch the NFC tag action which was written anytime before.
How can I tell the OS to stop triggering the action of the tag so that I can immediately rewrite it?
public enum NfcHelperState
{
Initializing,
Waiting,
Ready,
Writing,
Finished,
Error,
NoDeviceFound
}
public class NfcHelper
{
private NfcHelperState _state = NfcHelperState.Initializing;
public NfcHelperState State
{
get { return _state; }
}
private ProximityDevice _nfcDevice;
private long _subscriptionId;
public NfcHelper()
{
Init();
}
public void Init()
{
UpdateState();
_nfcDevice = ProximityDevice.GetDefault();
if (_nfcDevice == null)
{
UpdateState(NfcHelperState.NoDeviceFound);
return;
}
UpdateState(NfcHelperState.Waiting);
}
private void UpdateState(NfcHelperState? state = null)
{
if (state.HasValue)
{
_state = state.Value;
}
if (OnStatusMessageChanged != null)
{
OnStatusMessageChanged(this, _state);
}
}
public void WriteToTag()
{
UpdateState(NfcHelperState.Ready);
_subscriptionId = _nfcDevice.SubscribeForMessage("WriteableTag", WriteableTagDetected);
}
private void WriteableTagDetected(ProximityDevice sender, ProximityMessage message)
{
UpdateState(NfcHelperState.Writing);
try
{
var str = "action=my_custom_action";
str += "\tWindowsPhone\t";
str += CurrentApp.AppId;
_nfcDevice.PublishBinaryMessage("LaunchApp:WriteTag", GetBufferFromString(str),
WriteToTagComplete);
}
catch (Exception e)
{
UpdateState(NfcHelperState.Error);
StopWaitingForTag();
}
}
private void WriteToTagComplete(ProximityDevice sender, long messageId)
{
sender.StopPublishingMessage(messageId);
UpdateState(NfcHelperState.Finished);
StopWaitingForTag();
}
private void StopWaitingForTag()
{
_nfcDevice.StopSubscribingForMessage(_subscriptionId);
}
private static IBuffer GetBufferFromString(string str)
{
using (var dw = new DataWriter())
{
dw.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf16LE;
dw.WriteString(str);
return dw.DetachBuffer();
}
}
public delegate void NfcStatusMessageChangedHandler(object myObject, NfcHelperState newState);
public event NfcStatusMessageChangedHandler OnStatusMessageChanged;
}
WriteToTag is called when a button in my app is tapped and the app waits for a writable tag. If a writable tag is recognized, WriteableTagDetected gets called and immediately starts the writing process. However, this is interrupted by the WP dialog which asks whether to perform the NFC action or not. After writing, WriteToTagComplete should be called, where StopWaitingForTag gets called and ends the write process.
I hope you guys can help me :)
Turns out I thought the wrong way. I didn't need to wait for a tag to arrive in order to rewrite it. In fact, there's no need to do _nfcDevice.SubscribeForMessage("WriteableTag", WriteableTagDetected); before writing. Just start using PublishBinaryMessage and it will write to the tag once it arrives at the device.
My final code looks like the following:
public enum NfcHelperState
{
Initializing,
Ready,
WaitingForWriting,
FinishedWriting,
ErrorWriting,
NoDeviceFound
}
public class NfcHelper
{
private NfcHelperState _state = NfcHelperState.Initializing;
public NfcHelperState State
{
get { return _state; }
}
private ProximityDevice _nfcDevice;
private long? _writingMessageId;
public NfcHelper()
{
Init();
}
public void Init()
{
UpdateState();
_nfcDevice = ProximityDevice.GetDefault();
if (_nfcDevice == null)
{
UpdateState(NfcHelperState.NoDeviceFound);
return;
}
UpdateState(NfcHelperState.Ready);
}
private void UpdateState(NfcHelperState? state = null)
{
if (state.HasValue)
{
_state = state.Value;
}
if (OnStatusMessageChanged != null)
{
OnStatusMessageChanged(this, _state);
}
}
public void WriteToTag()
{
StopWritingMessage();
UpdateState(NfcHelperState.WaitingForWriting);
try
{
var str = new StringBuilder();
str.Append("action=my_custom_action");
str.Append("\tWindowsPhone\t{");
str.Append(CurrentApp.AppId);
str.Append("}");
_writingMessageId = _nfcDevice.PublishBinaryMessage("LaunchApp:WriteTag", GetBufferFromString(str.ToString()),
WriteToTagComplete);
}
catch
{
UpdateState(NfcHelperState.ErrorWriting);
StopWritingMessage();
}
}
private void WriteToTagComplete(ProximityDevice sender, long messageId)
{
UpdateState(NfcHelperState.FinishedWriting);
StopWritingMessage();
}
private void StopWritingMessage()
{
if (_writingMessageId.HasValue)
{
_nfcDevice.StopPublishingMessage(_writingMessageId.Value);
_writingMessageId = null;
}
}
private static IBuffer GetBufferFromString(string str)
{
using (var dw = new DataWriter())
{
dw.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf16LE;
dw.WriteString(str);
return dw.DetachBuffer();
}
}
public delegate void NfcStatusMessageChangedHandler(object myObject, NfcHelperState newState);
public event NfcStatusMessageChangedHandler OnStatusMessageChanged;
}
I have a class which contains a method for receiving UDP data in a separate thread. I do this to avoid the main application (which is running in Unity3D) from stalling.
I need to pass the data that is received in the separate thread to another class, which runs on the original thread and, as such, is able to interact with Unity3D.
Here is roughly what the UDPReceiver looks like:
public class UDPReciever {
//...
public UDPReciever() {
m_Port = 12345;
m_Worker = new Thread(new ThreadStart(recvData));
m_Worker.IsBackground = true;
m_Worker.Start();
}
void recvData() {
m_UDPClient = new UdpClient(m_Port);
while (true) {
try {
IPEndPoint anyIP = new IPEndPoint(IPAddress.Any, 0);
byte[] data = (m_UDPClient.Receive(ref anyIP));
// TODO: Hand 'data' to NetworkController class (running in the original thread) for processing
} catch (Exception err) {
print(err.ToString());
}
}
}
}
This is roughly what the NetworkController class needs to look like. Ideally the "OnNewData" method would be called every time a new packet is received with the data passed as an argument.
public class NetworkController {
//...
void OnNewData(pData) {
// Process the data in this thread
}
}
How would I go about achieving this? Thanks in advance.
Here is how it could be done (not tested):
public class Dispatcher : MonoBehaviour
{
private static readonly BlockingCollection<Action> tasks = new BlockingCollection<Action>();
public static Dispatcher Instance = null;
static Dispatcher()
{
Instance = new Dispatcher();
}
private Dispatcher()
{
}
public void InvokeLater(Action task)
{
tasks.Add(task);
}
void FixedUpdate()
{
if (tasks.Count > 0)
{
foreach (Action task in tasks.GetConsumingEnumerable())
{
task();
}
}
}
}
...
NetworkController networkControllerInstance;
void recvData()
{
m_UDPClient = new UdpClient(m_Port);
while (true)
{
try
{
IPEndPoint anyIP = new IPEndPoint(IPAddress.Any, 0);
byte[] data = (m_UDPClient.Receive(ref anyIP));
Dispatcher.Instance.InvokeLater(() => networkControllerInstance.OnNewData(data));
}
catch (Exception err)
{
print(err.ToString());
}
}
}
EDIT:
A version that should be compliant with .Net 3.5:
public class Dispatcher : MonoBehaviour
{
private static readonly Queue<Action> tasks = new Queue<Action>();
public static Dispatcher Instance = null;
static Dispatcher()
{
Instance = new Dispatcher();
}
private Dispatcher()
{
}
public void InvokeLater(Action task)
{
lock (tasks)
{
tasks.Enqueue(task);
}
}
void FixedUpdate()
{
while (tasks.Count > 0)
{
Action task = null;
lock (tasks)
{
if (tasks.Count > 0)
{
task = tasks.Dequeue();
}
}
task();
}
}
}
EDIT 2:
if you want to avoid freezing the main thread during a too long period:
void FixedUpdate()
{
if (tasks.Count != 0)
{
Action task = null;
lock (tasks)
{
if (tasks.Count != 0)
{
task = tasks.Dequeue();
}
}
task();
}
}