I have what I believe to be a thread safety issue.
I have two printers, one is loaded with a small label and the other a large. I want to send 1 print job over a socket to each printer.
I have attempted to thread/background the first request (large label) as it can take a long time to complete.
99% of the time the script works as expected. The small and big labels come out of their respective printers.
However, every now and then both the big and small labels are sent to the same printer! Either both to the small or large.
I think it's related to thread safety but I am finding it very hard to track down and understand whats happening. I've tried to add a lock and to close the sockets after use, but whatever I try the issue persists.
I've attempted to reduce my code to the bare minimum but am aware this post is still very code heavy. Any advice would be greatly appreciated.
// stores the printer info
class PrinterBench
{
public PrinterBench(string sArg_PostageLabelIP, string sArg_SmallLabelIP)
{
PostageLabelIP = sArg_PostageLabelIP;
SmallLabelIP = sArg_SmallLabelIP;
}
public string PostageLabelIP;
public string SmallLabelIP;
}
// main entry point
class HomeController{
PrintController oPrintController;
List<string> lsLabelResults = new List<string>("label result");
PrinterBench pbBench = new PrinterBench("192.168.2.20","192.168.2.21");
void Process(){
oPrintController = new PrintController(this);
if(GetLabel()){
// should always come out of the big printer (runs in background)
oPrintController.PrintBySocketThreaded(lsLabelResults, pbBench.PostageLabelIP);
// should always come out of the small printer
oPrintController.PrintWarningLabel();
}
}
}
class PrintController{
HomeController oHC;
public EndPoint ep { get; set; }
public Socket sock { get; set; }
public NetworkStream ns { get; set; }
private static Dictionary<string, Socket> lSocks = new Dictionary<string, Socket>();
private BackgroundWorker _backgroundWorker;
static readonly object locker = new object();
double dProgress;
bool bPrintSuccess = true;
public PrintController(HomeController oArg_HC)
{
oHC = oArg_HC;
}
public bool InitSocks()
{
// Ensure the IP's / endpoints of users printers are assigned
if (!lSocks.ContainsKey(oHC.pbBench.PostageLabelIP))
{
lSocks.Add(oHC.pbBench.PostageLabelIP, null);
}
if (!lSocks.ContainsKey(oHC.pbBench.SmallLabelIP))
{
lSocks.Add(oHC.pbBench.SmallLabelIP, null);
}
// attempt to create a connection to each socket
foreach (string sKey in lSocks.Keys.ToList())
{
if (lSocks[sKey] == null || !lSocks[sKey].Connected )
{
ep = new IPEndPoint(IPAddress.Parse(sKey), 9100);
lSocks[sKey] = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
lSocks[sKey].Connect(ep);
}
}
return true;
}
public bool PrintBySocketThreaded(List<string> lsToPrint, string sIP)
{
// open both the sockets
InitSocks();
bBatchPrintSuccess = false;
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
_backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.WorkerSupportsCancellation = true;
object[] parameters = new object[] { lsToPrint, sIP, lSocks };
_backgroundWorker.RunWorkerAsync(parameters);
return true;
}
// On worker thread, send to print!
public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
object[] parameters = e.Argument as object[];
double dProgressChunks = (100 / ((List<string>)parameters[0]).Count);
int iPos = 1;
Dictionary<string, Socket> dctSocks = (Dictionary<string, Socket>)parameters[2];
foreach (string sLabel in (List<string>)parameters[0] )
{
bool bPrinted = false;
// thread lock print by socket to ensure its not accessed twice
lock (locker)
{
// get relevant socket from main thread
bPrinted = PrintBySocket(sLabel, (string)parameters[1], dctSocks[(string)parameters[1]]);
}
iPos++;
}
while (!((BackgroundWorker)sender).CancellationPending)
{
((BackgroundWorker)sender).CancelAsync();
((BackgroundWorker)sender).Dispose();
//Thread.Sleep(500);
}
return;
}
// Back on the 'UI' thread so we can update the progress bar (have access to main thread data)!
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null) MessageBox.Show(e.Error.Message);
if (bPrintSuccess) oHC.WriteLog("Printing Complete");
bBatchPrintSuccess = true;
((BackgroundWorker)sender).CancelAsync();
((BackgroundWorker)sender).Dispose();
}
/// sends to printer via socket
public bool PrintBySocket(string sArg_ToPrint, string sIP, Socket sock = null)
{
Socket sTmpSock = sock;
if (sTmpSock == null)
{
InitSocks();
if (!lSocks.ContainsKey(sIP)){
throw new Exception("Sock not init");
}else{
sTmpSock = lSocks[sIP];
}
}
using (ns = new NetworkStream(sTmpSock))
{
byte[] toSend = Encoding.ASCII.GetBytes(sEOL + sArg_ToPrint);
ns.BeginWrite(toSend, 0, toSend.Length, OnWriteComplete, null);
ns.Flush();
}
return true;
}
public bool PrintWarningLabel()
{
string sOut = sEOL + "N" + sEOL;
sOut += "LL411" + sEOL;
sOut += "R40,0" + sEOL;
sOut += "S5" + sEOL;
sOut += "D15" + sEOL;
sOut += "A0,0,0,4,4,3,N,\"!!!!!!!!!!!!!!!!!!!!!!!\"" + sEOL;
sOut += "A0,150,0,4,3,3,N,\"WARNING MESSAGE TO PRINT\"" + sEOL;
sOut += "A0,280,0,4,4,3,N,\"!!!!!!!!!!!!!!!!!!!!!!!\"" + sEOL;
sOut += "P1";
sOut += sEOL;
if (PrintBySocket(sOut, oHC.pbBench.SmallLabelIP))
{
oHC.WriteLog("WARNING LABEL PRINTED");
return true;
}
return false;
}
}
You've got this field in PrintController:
public NetworkStream ns { get; set; }
It's only used here:
using (ns = new NetworkStream(sTmpSock))
{
byte[] toSend = Encoding.ASCII.GetBytes(sEOL + sArg_ToPrint);
ns.BeginWrite(toSend, 0, toSend.Length, OnWriteComplete, null);
ns.Flush();
}
If two threads execute this at the same time, one can change ns to a different NetworkStream when the other is about to write to it.
Since ns is used and disposed right here, there doesn't seem to be any reason to declare it as a field, which means multiple threads can overwrite it. Instead, delete the field and change your code to this:
using (var ns = new NetworkStream(sTmpSock))
Then multiple threads executing this will create their own NetworkStream as a local variable instead of fighting over one.
I'd check all the other fields too and see if they need to be fields or if they can just be declared as local variables.
Unintentional shared state is bad for multithreaded code. It will behave exactly as you described. It works, it works, it works, and then it doesn't work, and reproducing the problem when you want to see it will be nearly impossible.
Related
I'm changing some old sync/threaded code to async,
basically im implementing a 'server emulator' for an old flash game,
and trying to make the packet reading async to hopefully speed things up a little.
private List<byte> currentPacket = new List<byte>();
private byte[] workBuffer = new byte[1028];
private bool dcLock = false;
public GameClient(Socket clientSocket)
{
ClientSocket = clientSocket;
RemoteIp = clientSocket.RemoteEndPoint.ToString();
if (RemoteIp.Contains(":"))
RemoteIp = RemoteIp.Substring(0, RemoteIp.IndexOf(":"));
Logger.DebugPrint("Client connected # " + RemoteIp);
kickTimer = new Timer(new TimerCallback(kickTimerTick), null, kickInterval, kickInterval);
warnTimer = new Timer(new TimerCallback(warnTimerTick), null, warnInterval, warnInterval);
minuteTimer = new Timer(new TimerCallback(minuteTimerTick), null, oneMinute, oneMinute);
connectedClients.Add(this);
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
e.Completed += receivePackets;
e.SetBuffer(workBuffer, 0, workBuffer.Length);
ClientSocket.ReceiveAsync(e);
}
public static void CreateClient(object sender, SocketAsyncEventArgs e)
{
Socket eSocket = e.AcceptSocket;
e.AcceptSocket = null;
socket.AcceptAsync(e);
GameClient client = new GameClient(eSocket);
}
private void receivePackets(object sender, SocketAsyncEventArgs e)
{
// HI1 Packets are terminates by 0x00 so we have to read until we receive that terminator
if (e.SocketError == SocketError.Success && !isDisconnecting)
{
int availble = e.BytesTransferred;
if (availble >= 1)
{
for (int i = 0; i < availble; i++)
{
currentPacket.Add(e.Buffer[i]);
if (e.Buffer[i] == PacketBuilder.PACKET_TERMINATOR)
{
parsePackets(currentPacket.ToArray());
currentPacket.Clear();
}
}
}
Array.Clear(e.Buffer);
ClientSocket.ReceiveAsync(e);
return;
}
else
{
Disconnect();
}
while (dcLock) { }; // Refuse to shut down until dcLock is cleared. (prevents TOCTOU issues.)
// Stop Timers
if (inactivityTimer != null)
inactivityTimer.Dispose();
if (warnTimer != null)
warnTimer.Dispose();
if (kickTimer != null)
kickTimer.Dispose();
// Call OnDisconnect
connectedClients.Remove(this);
GameServer.OnDisconnect(this);
LoggedIn = false;
// Close Socket
ClientSocket.Close();
ClientSocket.Dispose();
return;
}
now you see
e.Completed += receivePackets;
e.SetBuffer(workBuffer, 0, workBuffer.Length);
ClientSocket.ReceiveAsync(e);
for some reason receivePackets is never being called,
if i open up NetCat and connect to 127.0.0.1:12321 and send stuff there it works,
but from in the original flash-based client of the game? nothing happens.
no call to receivePackets ever happens, even if the buffer is only 1 byte.
even after disconnecting the client...
but it worked in my sync implementation where i just called clientSocket.Receive() in a loop.. so why doesnt it work now in async verison?
I'm trying to implement wrapper class which will simply connect to TCP server and wait for data. Once data submitted from server - I will receive this data and pass it onto subscribers of my class.
All this works. Now I want to add external functionality to "reset" this class on a timer (force reconnect every so often) to keep connection alive. My idea is that Init method can be called as many times as needed to get socket reset. However, I do get various exceptions with this.
Class code:
namespace Ditat.GateControl.Service.InputListener
{
using System;
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class BaseTCPSocketListener : IInputListener
{
#region Events/Properties
public event EventHandler<Exception> OnError;
public event EventHandler<string> OnDataReceived;
private string host;
private int port;
private int delayToClearBufferSeconds = 5;
private TcpClient client;
private readonly byte[] buffer = new byte[1024];
/// <summary>
/// Will accumulate data as it's received
/// </summary>
private string DataBuffer { get; set; }
/// <summary>
/// Store time of last data receipt. Need this in order to purge data after delay
/// </summary>
private DateTime LastDataReceivedOn { get; set; }
#endregion
public BaseTCPSocketListener()
{
// Preset all entries
this.LastDataReceivedOn = DateTime.UtcNow;
this.DataBuffer = string.Empty;
}
public void Init(string config)
{
// Parse info
var bits = config.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
this.host = bits[0];
var hostBytes = this.host.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
var hostIp = new IPAddress(new[] { byte.Parse(hostBytes[0]), byte.Parse(hostBytes[1]), byte.Parse(hostBytes[2]), byte.Parse(hostBytes[3]) });
this.port = int.Parse(bits[1]);
this.delayToClearBufferSeconds = int.Parse(bits[2]);
// Close open client
if (this.client?.Client != null)
{
this.client.Client.Disconnect(true);
this.client = null;
}
// Connect to client
this.client = new TcpClient();
if (!this.client.ConnectAsync(hostIp, this.port).Wait(2500))
throw new Exception($"Failed to connect to {this.host}:{this.port} in allotted time");
this.EstablishReceiver();
}
protected void DataReceived(IAsyncResult result)
{
// End the data receiving that the socket has done and get the number of bytes read.
var bytesCount = 0;
try
{
bytesCount = this.client.Client.EndReceive(result);
}
catch (Exception ex)
{
this.RaiseOnErrorToClient(new Exception(nameof(this.DataReceived)));
this.RaiseOnErrorToClient(ex);
}
// No data received, establish receiver and return
if (bytesCount == 0)
{
this.EstablishReceiver();
return;
}
// Convert the data we have to a string.
this.DataBuffer += Encoding.UTF8.GetString(this.buffer, 0, bytesCount);
// Record last time data received
this.LastDataReceivedOn = DateTime.UtcNow;
this.RaiseOnDataReceivedToClient(this.DataBuffer);
this.DataBuffer = string.Empty;
this.EstablishReceiver();
}
private void EstablishReceiver()
{
try
{
// Set up again to get the next chunk of data.
this.client.Client.BeginReceive(this.buffer, 0, this.buffer.Length, SocketFlags.None, this.DataReceived, this.buffer);
}
catch (Exception ex)
{
this.RaiseOnErrorToClient(new Exception(nameof(this.EstablishReceiver)));
this.RaiseOnErrorToClient(ex);
}
}
private void RaiseOnErrorToClient(Exception ex)
{
if (this.OnError == null) return;
foreach (Delegate d in this.OnError.GetInvocationList())
{
var syncer = d.Target as ISynchronizeInvoke;
if (syncer == null)
{
d.DynamicInvoke(this, ex);
}
else
{
syncer.BeginInvoke(d, new object[] { this, ex });
}
}
}
private void RaiseOnDataReceivedToClient(string data)
{
if (this.OnDataReceived == null) return;
foreach (Delegate d in this.OnDataReceived.GetInvocationList())
{
var syncer = d.Target as ISynchronizeInvoke;
if (syncer == null)
{
d.DynamicInvoke(this, data);
}
else
{
syncer.BeginInvoke(d, new object[] { this, data });
}
}
}
}
}
Client code (under button click on form)
private void ListenBaseButton_Click(object sender, EventArgs e)
{
if (this.bsl == null)
{
this.bsl = new BaseTCPSocketListener();
this.bsl.OnDataReceived += delegate (object o, string s)
{
this.DataTextBox.Text += $"Base: {DateTime.Now} - {s}" + Environment.NewLine;
};
this.bsl.OnError += delegate (object o, Exception x)
{
this.DataTextBox.Text += $"Base TCP receiver error: {DateTime.Now} - {x.Message}" + Environment.NewLine;
};
}
try
{
this.bsl.Init("192.168.33.70|10001|10");
this.DataTextBox.Text += "BEGIN RECEIVING BSL data --------------------------" + Environment.NewLine;
}
catch (Exception exception)
{
this.DataTextBox.Text += $"ERROR CONNECTING TO BSL ------------{exception.Message}" + Environment.NewLine;
}
}
Exceptions I get. First exception when button clicked 2nd time in from handler in DataReceived
The IAsyncResult object was not returned from the corresponding
asynchronous method on this class.
On following clicks I get exception from handler in EstablishReceiver
A request to send or receive data was disallowed because the socket is
not connected and (when sending on a datagram socket using a sendto
call) no address was supplied
How do I properly ensure socket closed and re-opened?
The IAsyncResult object was not returned from the corresponding
asynchronous method on this class.
This is a well known problem that happens when data callback (DataReceived()) is called for previous socket. In this case you will call Socket.EndReceive() with incorrect instance of IAsyncResult which throws above exception.
Asynchronous Client Socket Example contains possible workaround for this problem: store socket on which BeginReceive() was called in state object which is then passed to DataReceived callback:
StateObject class
public class StateObject
{
public Socket Socket { get; set; }
public byte[] Buffer { get; } = new byte[1024];
public StateObject(Socket socket)
{
Socket = socket;
}
}
EstablishReceiver() method:
private void EstablishReceiver()
{
try
{
var state = new StateObject(client.Client);
// Set up again to get the next chunk of data.
this.client.Client.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, this.DataReceived, state);
}
catch (Exception ex)
{
this.RaiseOnErrorToClient(new Exception(nameof(this.EstablishReceiver)));
this.RaiseOnErrorToClient(ex);
}
}
DataReceived() method:
protected void DataReceived(IAsyncResult result)
{
var state = (StateObject) result.AsyncState;
// End the data receiving that the socket has done and get the number of bytes read.
var bytesCount = 0;
try
{
SocketError errorCode;
bytesCount = state.Socket.EndReceive(result, out errorCode);
if (errorCode != SocketError.Success)
{
bytesCount = 0;
}
}
catch (Exception ex)
{
this.RaiseOnErrorToClient(new Exception(nameof(this.DataReceived)));
this.RaiseOnErrorToClient(ex);
}
if (bytesCount > 0)
{
// Convert the data we have to a string.
this.DataBuffer += Encoding.UTF8.GetString(state.Buffer, 0, bytesCount);
// Record last time data received
this.LastDataReceivedOn = DateTime.UtcNow;
this.RaiseOnDataReceivedToClient(this.DataBuffer);
this.DataBuffer = string.Empty;
this.EstablishReceiver();
}
}
A request to send or receive data was disallowed because the socket is
not connected and (when sending on a datagram socket using a sendto
call) no address was supplied
Above DataReceived() method also contains the fix for the second exception. Exception is caused by calling BeginReceive() (from EstablishReceiver()) on disconnected socket. You should not call BeginReceive() on a socket if previous read brought 0 bytes.
First of all, you're closing the socket being held by the TcpClient, but not disposing the client itself. Try the following:
// Close open client
this.client?.Close(); // Disposes and releases resources
this.client = null;
The issue is that DataReceived will be called when you close the client. You simply need to identify to the method that it should not do anything because you have deliberately ended the process. You could just add a bool:
private bool ignoreCallback;
public void Init(string config)
{
// Parse info
var bits = config.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
this.host = bits[0];
var hostBytes = this.host.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
var hostIp = new IPAddress(new[] { byte.Parse(hostBytes[0]), byte.Parse(hostBytes[1]), byte.Parse(hostBytes[2]), byte.Parse(hostBytes[3]) });
this.port = int.Parse(bits[1]);
this.delayToClearBufferSeconds = int.Parse(bits[2]);
// Close open client
if (this.client?.Client != null)
{
ignoreCallback = true;
this.client.Client.Disconnect(true);
this.client = null;
}
// Connect to client
this.client = new TcpClient();
if (!this.client.ConnectAsync(hostIp, this.port).Wait(2500))
throw new Exception($"Failed to connect to {this.host}:{this.port} in allotted time");
this.EstablishReceiver();
}
protected void DataReceived(IAsyncResult result)
{
if (ignoreCallback)
{
ignoreCallback = false;
return;
}
...
I've been using SocketAsyncEventArgs for a project recently and I've come across issues where it appears that ReceiveAsync is occasionally getting data in a different order from what is being sent via SendAsync. Each block of data sent in the SendAsync method is maintained, but the blocks are not necessarily in the right order. Maybe I have an incorrect understanding of the SendAsync method, but I thought that especially using SocketType.Stream and ProtocolType.Tcp would ensure the order is maintained. I understand that the underlying process will inevitably break the message up and that ReceiveAsync will commonly read less than the buffer allocation. But I assumed that the send and receive streams would maintain order.
I carved out a test console program which shows the issue. It tries to run about 20 times using a different set of sockets and ports each time. On my laptop, it usually makes it through one time and then fails the second time; usually receiving a later block when it's expecting the second. From other testing, I know that expected block eventually does come, just out of sequence.
One caveat is that I was able to test it on a Windows 2008 remote server and had no issues. However, it has never come close to completing on my laptop. In fact, if I let the debug execution hang in the exception break for a while I've had it completely freeze my laptop more than once and had to do a hard reboot. This is my work laptop running on Windows 7, using VS2017. I'm not sure if it could be a factor, but it is running Symantec Endpoint Protection though I haven't found anything in the logs.
So my question is, do I have an incorrect view of how the SocketAsyncEventArgs operate? Or is my code a disaster (perhaps both)? Is it somehow unique to my laptop? (This last one makes me feel like I'm setting up for embarrassment like when you're new to programming and you think there must be something wrong with the compiler.)
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
static class DumTest
{
static void Main(string[] args)
{
for (int i = 9177; i < 9199; i++)
{
RunDum(i);
//Thread.Sleep(350);
}
Console.WriteLine("all done.");
Console.ReadLine();
}
static void RunDum(int port)
{
var dr = new DumReceiver(port);
var ds = new DumSender(port);
dr.Acception.Wait();
ds.Connection.Wait();
dr.Completion.Wait();
ds.Completion.Wait();
Console.WriteLine($"Completed {port}. " +
$"sent: {ds.SegmentsSent} segments, received: {dr.SegmentsRead} segments");
}
}
class DumReceiver
{
private readonly SocketAsyncEventArgs eva = new SocketAsyncEventArgs();
private readonly TaskCompletionSource<object> tcsAcc = new TaskCompletionSource<object>();
private TaskCompletionSource<object> tcsRcv;
private Socket socket;
internal DumReceiver(int port)
{
this.eva.Completed += this.Received;
var lstSock = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var localIP = Dns.GetHostEntry(Dns.GetHostName()).AddressList
.First(i => i.AddressFamily == AddressFamily.InterNetwork);
lstSock.Bind(new IPEndPoint(localIP, port));
lstSock.Listen(1);
var saea = new SocketAsyncEventArgs();
saea.Completed += this.AcceptCompleted;
lstSock.AcceptAsync(saea);
}
internal Task Acception => this.tcsAcc.Task;
internal Task Completion { get; private set; }
internal int SegmentsRead { get; private set; }
private void AcceptCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
this.socket = e.AcceptSocket;
e.Dispose();
try
{
this.Completion = this.ReceiveLupeAsync();
}
finally
{
this.tcsAcc.SetResult(null);
}
}
else
{
this.tcsAcc.SetException(new SocketException((int)e.SocketError));
}
}
private async Task ReceiveLupeAsync()
{
var buf = new byte[8196];
byte bufSeg = 1;
int pos = 0;
while (true)
{
this.tcsRcv = new TaskCompletionSource<object>();
this.eva.SetBuffer(buf, pos, 8196 - pos);
if (this.socket.ReceiveAsync(this.eva))
{
await this.tcsRcv.Task.ConfigureAwait(false);
}
if (this.eva.SocketError != SocketError.Success)
{
throw new SocketException((int)eva.SocketError);
}
if (this.eva.BytesTransferred == 0)
{
if (pos != 0)
{
throw new EndOfStreamException();
}
break;
}
pos += this.eva.BytesTransferred;
if (pos == 8196)
{
pos = 0;
for (int i = 0; i < 8196; i++)
{
if (buf[i] != bufSeg)
{
var msg = $"Expected {bufSeg} but read {buf[i]} ({i} of 8196). " +
$"Last read: {this.eva.BytesTransferred}.";
Console.WriteLine(msg);
throw new Exception(msg);
}
}
this.SegmentsRead++;
bufSeg = (byte)(this.SegmentsRead + 1);
}
}
}
private void Received(object s, SocketAsyncEventArgs e) => this.tcsRcv.SetResult(null);
}
class DumSender
{
private readonly SocketAsyncEventArgs eva = new SocketAsyncEventArgs();
private readonly Socket socket = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private readonly TaskCompletionSource<object> tcsCon = new TaskCompletionSource<object>();
private TaskCompletionSource<object> tcsSnd;
internal DumSender(int port)
{
this.eva.Completed += this.Sent;
var saea = new SocketAsyncEventArgs();
var localIP = Dns.GetHostEntry(Dns.GetHostName()).AddressList
.First(i => i.AddressFamily == AddressFamily.InterNetwork);
saea.RemoteEndPoint = new IPEndPoint(localIP, port);
saea.Completed += this.ConnectionCompleted;
this.socket.ConnectAsync(saea);
}
internal Task Connection => this.tcsCon.Task;
internal Task Completion { get; private set; }
internal int SegmentsSent { get; private set; }
private void ConnectionCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
e.Dispose();
try
{
this.Completion = this.SendLupeAsync();
}
finally
{
this.tcsCon.SetResult(null);
}
}
else
{
this.tcsCon.SetException(new SocketException((int)e.SocketError));
}
}
private async Task SendLupeAsync()
{
var buf = new byte[8196];
byte bufSeg = 1;
while (true)
{
for (int i = 0; i < 8196; i++)
{
buf[i] = bufSeg;
}
this.tcsSnd = new TaskCompletionSource<object>();
this.eva.SetBuffer(buf, 0, 8196);
if (this.socket.SendAsync(this.eva))
{
await this.tcsSnd.Task.ConfigureAwait(false);
}
if (this.eva.SocketError != SocketError.Success)
{
throw new SocketException((int)this.eva.SocketError);
}
if (this.eva.BytesTransferred != 8196)
{
throw new SocketException();
}
if (++this.SegmentsSent == 299)
{
break;
}
bufSeg = (byte)(this.SegmentsSent + 1);
}
this.socket.Shutdown(SocketShutdown.Both);
}
private void Sent(object s, SocketAsyncEventArgs e) => this.tcsSnd.SetResult(null);
}
I believe the problem is in your code.
You must check the return of Socket's *Async methods that use SocketAsyncEventArgs. If they return false, them the SocketAsyncEventArgs.Completed event won't be raised, you must handle the result synchronously.
Reference documentation: SocketAsyncEventArgs Class. Search for willRaiseEvent.
In DumReceiver's constructor, you don't check AcceptAsync's result and you don't handle the case when it completes synchronously.
In DumSender's constructor, you don't check ConnectAsync's result and you don't handle the case when it completes synchronously.
On top of this, the SocketAsyncEventArgs.Completed event may be raised in some other thread, most probably an I/O thread from the ThreadPool.
Each time you assign to DumReceiver.tcsRcv and DumSender.tcsSnd without proper synchronization, you can't be sure that DumReceiver.Received and DumSender.Sent are using the latest TaskCompletionSource.
Actually, you could get a NullReferenceException on the first iteration.
You lack synchronization in:
DumReceiver, the fields tcsRcv and socket and the properties Completion and SegmentsRead
DumSender, the field tcsSnd and the properties Completion and SegmentsSent
I suggest you consider using a single SemaphoreSlim instead of creating a new TaskCompletionSource on each time you invoke ReceiveAsync and SendAsync. You'd initialize the semaphore to 0 in the constructor. If the *Async operation is pending, you'd await WaitAsync on the semaphore, and the Completed event would Release the semaphore.
This should be enough to get rid of the race conditions in the TaskCompletionSource fields. You'd still need proper synchronization on the other fields and properties. For instance, there's no reason why Completion can't be created in the constructors, and SegmentsRead and SegmentsSent could be read-only and refer to a field which would be accessed internally with one or more of the Interlocked methods (e.g. Interlocked.Increment or Interlocked.Add).
I wrote the simplified version of my program below. Process A launches a child process (Process B). I use an anonymous pipe to write information about the progress of a method running on process B. Meanwhile I have a function in process A that continually reads from a stream to see if there is a new update coming in from the pipe. If there is, the form on process A is updated to reflect the progress. This works as expected, however I am wondering if there is a better way to accomplish this without having to continually check the stream to see if there are any new updates to the progress.
/////////////////
///Process A ////
/////////////////
public void LaunchProcessB()
{
using (AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(PipeDirection.In,
HandleInheritability.Inheritable))
{
var _Process = new Process();
_Process.StartInfo.FileName = exeString;
_Process.StartInfo.Arguments = pipeServer.GetClientHandleAsString()
_Process.StartInfo.RedirectStandardOutput = true;
_Process.StartInfo.RedirectStandardInput = true;
_Process.StartInfo.CreateNoWindow = true;
_Process.StartInfo.UseShellExecute = false;
_Process.Start(); //launches process B
pipeServer.DisposeLocalCopyOfClientHandle();
using (StreamReader sr = new StreamReader(pipeServer))
{
try
{
while (true)
{
string temp = sr.ReadLine();
if (temp == null) break;
int result;
if (Int32.TryParse(temp, out result))
ShowDocumentProgress(result);
else ShowProgress(temp);
}
}
catch (Exception)
{
//error occured when reading from stream.
}
}
if (!_Process.Responding && !_Process.HasExited)
{
_Process.Kill();
return;
}
_Process.WaitForExit(10000);
}
}
private void ShowProgressPercent(int percentage)
{
if (percentage > currentPercentage)
{
progressBar.Value = percentage;
}
}
private void ShowProgress(string progressString)
{
labelMessage.Text = progressString;
}
/////////////////
///Process B ////
/////////////////
private StreamWriter _progressWriter;
private PipeStream _progressPipe;
static int Main(string[] args)
{
using (progressPipe = new AnonymousPipeClientStream(PipeDirection.Out, args[0]))
using (_progressWriter = new StreamWriter(_progressPipe))
{
RunLongProcess()
}
}
private void RunLongProcess()
{
//attaches events to PercentProgress and StageProgress methods.
}
private void PercentProgress(int percentage)
{
_progressWriter.WriteLine(percentage.ToString());
_progressPipe.WaitForPipeDrain();
}
private void StageProgress(string stage)
{
_progressWriter.WriteLine(stage);
_progressPipe.WaitForPipeDrain();
}
The while condition is not necessary. Simply read until temp is null. That's the end signal of the stream.
Make this a while(true) loop.
I think you also need to add exception handling to catch the process terminating and severing the pipe. !_Process.HasExited && pipeServer.IsConnected is not enough because it might be true but immediately switch to false after the test.
I also would add a WaitForExit at the end to make sure the system is quiesced before you continue.
I'm searching for a Streamclass which contains:
- a method for sending/receiving a byte-array
- a method for sending/receiving a string
The only Class I've found was NetworkStream. But the disadvantage with the NetworkStream-Class is, that if i want sending a string, i must befor convert this string into a byte-array and send this byte-array, because there is no method for sending strings directly.
And on the other side classes like Streamwriter have methods for sending/receiving strings, but there have no methods for sending/receiving a byte-array.
And if i try to combine these two Streamclasses like this:
TcpClient clientConnection = new TcpClient();
NetworkStream nws = clientConnection.GetStream();
StreamWriter sw = new StreamWriter(nws);
sw.writeLine("ABC");
sw.Flush();
nws.Write(byteArray, 0, lengthToSend);
i get a lot of strange errors (like byteArray will not receive on the other side completly), because i'm using here the same one stream in two different ways.
So, must i used NetworkStream-Class for my plan or exists there a better way?
I had the same problem,and the point is that the other side doesnt know what you are sending byte array or string so what i did is putting a header for each msg specially when dealing with serious server/client application coz you will have multiple data (user info, requesting info,replying info .. etc)
i am using streamwriter to send and streamreader to receive but i am also using threads
the connection remains open as long as the client is connected so i declare them once
here is a full example of my codes
public class Client
{
private StreamWriter swSender;
private StreamReader srReceiver;
private TcpClient tcpServer;
private Thread thrMessaging;
private string UserName = "UK";
private byte Tries = 0;
private bool Connected = false;
public void Connect()
{
if (!Connected)
{
IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
string User = localIPs[0].ToString();
string ServIP = "127.0.0.1";//change this to your server ip
InitializeConnection(ServIP, User);
}
else
{
CloseConnection("Disconnected at user's request.");
}
}
private void InitializeConnection(string ServIp, string User)
{
IPAddress ipAddr = IPAddress.Parse(ServIp);
tcpServer = new TcpClient();
try
{
tcpServer.Connect(ipAddr, 1986);//change that 1986 to your server port
}
catch
{
if (Connected) CloseConnection("");
MessageBox.Show("Connecteing to " + ServIp + "\r\nServer is Down ... Try nomber " + Tries); return;
}
Connected = true;
UserName = User;
swSender = new StreamWriter(tcpServer.GetStream());
swSender.WriteLine(User);
swSender.Flush();
thrMessaging = new Thread(new ThreadStart(ReceiveMessages));
thrMessaging.Start();
}
private void ReceiveMessages()
{
srReceiver = new StreamReader(tcpServer.GetStream());
string ConResponse = srReceiver.ReadLine();
if (ConResponse[0] == '1')
{
}
else
{
string Reason = "Not Connected: ";
Reason += ConResponse.Substring(2, ConResponse.Length - 2);
return;
}
while (Connected)
{
try
{
string NewMsg = srReceiver.ReadLine();
if (NewMsg != null && NewMsg != "")
PacketHandler.HandlePacket(NewMsg, this);
}
catch { }
}
}
public void CloseConnection(string Reason)
{
try
{
Connected = false;
swSender.Close();
srReceiver.Close();
tcpServer.Close();
}
catch { }
}
public void SendMessage(string Msg)
{
if (Msg.Length >= 1)
{
try
{
Tries++;
swSender.WriteLine(Msg);
swSender.Flush();
Tries = 0;
}
catch
{
if (Tries < 5)
{
try
{
CloseConnection("No connection made");
Connect();
}
catch { }
SendMessage(Msg);
}
else { MessageBox.Show("Connecting to server faild for 5 tries"); Tries = 0; }
}
}
}
then at the packet handler i do my handling to check what kind of data the client received
something like this
public static void HandlePacket(string MsgRec, Client Client)
{
string[] Info = MsgRec.Split('|');
string Type = Info[0];
if (Type == "")
{
return;
}
string subtype = Info[1];
int TLen = Type.Length + subtype.Length + 2;
string Data = MsgRec.Remove(0, TLen);//this is the main data the server sent
ushort PacketType = ushort.Parse(Type);
ushort SubType = ushort.Parse(subtype);
switch ((Structs.PacketType)PacketType)
{
case Structs.PacketType.Login:
{
//do your stuff here
break
}
case Structs.PacketType.Image:
{
//convert the Data back to byte array then get the image out from it
break
}
case Structs.PacketType.ByteArray:
{
//convert the Data back to byte array
break
}
}
}
i know its kinda messy and not the perfect way to do it , but it works for me
and remember that at the other side when sending something you need to add the packet type and subtype , or just any header with any splitter if u doin something simple
Finally : i think using Sockets and packets would be much easier if u are sending small packets length