With the help of some nice people from SO I have slowly built a small P2P app that sends and receives a stream of images of about 4kb each.
On 127.0.0.1 the receive keeps up with the sending, but when I try it on a remote machine it seems to me the receiving cannot keep up, I have perhaps sent 6 images but the receiver has only received one so far... and as time passes the difference gets bigger, until you see yourself a whole minute ago on the other screen. Its worth noting I would like this to work well on a connection that is about 64kbps-100kbps in another country where the ping times might be very big like 250ms or more.
What are the synchronisation options I have?
I have been advised by my brother of a simple solution which is to implement a 1:1 send/receive. So I only send an image when I receive one.
As I am a total beginner in network programming, any other tips are most welcome, here is my complete code :
namespace MyPrivateChat
{
using System;
using System.Drawing;
using System.Windows.Forms;
using AForge.Video;
using AForge.Video.DirectShow;
using System.Drawing.Imaging;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Net.Sockets;
using System.Diagnostics;
using AForge.Imaging.Filters;
public partial class fChat : Form
{
public fChat()
{
InitializeComponent();
}
private void fChat_Load(object sender, EventArgs e)
{
// get ip
_ownExternalIp = GetPublicIP();
Text = "My Private Chat - IP: " + _ownExternalIp;
// get video cam
var _videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
if (_videoDevices.Count != 0)
{
_videoDevice = new VideoCaptureDevice(_videoDevices[0].MonikerString);
btnStart.Enabled = true;
}
// fire up listener
listeningThread.RunWorkerAsync();
}
public string GetPublicIP()
{
string ip = "";
using (WebClient wc = new WebClient())
{
Match m = Regex.Match(wc.DownloadString("http://checkip.dyndns.org/"), #"(?<IP>\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})");
if (m.Success)
{
ip = m.Groups["IP"].Value;
}
}
return ip;
}
private void mnuPasteOwnIP_Click(object sender, EventArgs e)
{
txtPartnerIP.Text = _ownExternalIp;
}
private void btnStart_Click(object sender, EventArgs e)
{
if (_tcpOut == null)
{
// tcp server setup
_tcpOut = new TcpClient();
_tcpOut.Connect(txtPartnerIP.Text, 54321);
tmrLive.Enabled = true;
}
else
{
tmrLive.Enabled = false;
_tcpOut.Client.Disconnect(true);
_tcpOut.Close();
_tcpOut = null;
}
if (!_videoDevice.IsRunning)
{
_videoDevice.NewFrame += new NewFrameEventHandler(NewFrameReceived);
_videoDevice.DesiredFrameSize = new Size(640, 480);
_videoDevice.DesiredFrameRate = 100;
_videoDevice.Start();
btnStart.Text = "Stop";
}
else
{
_videoDevice.SignalToStop();
btnStart.Text = "Start";
}
}
private void NewFrameReceived(object sender, NewFrameEventArgs e)
{
Bitmap img = (Bitmap)e.Frame.Clone();
byte[] imgBytes = EncodeToJpeg(img, 25).ToArray();
if (_tcpOut.Connected)
{
NetworkStream ns = _tcpOut.GetStream();
if (ns.CanWrite)
{
ns.Write(BitConverter.GetBytes(imgBytes.Length), 0, 4);
ns.Write(imgBytes, 0, imgBytes.Length);
_totalFramesSent++;
}
}
}
private void listeningThread_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
_tcpIn = new TcpListener(IPAddress.Any, 54321);
_tcpIn.Start();
TcpClient _inClient = _tcpIn.AcceptTcpClient();
lblStatus.Text = "Connected - Receiving Broadcast";
tmrLive.Enabled = true;
NetworkStream ns = _inClient.GetStream();
while (true)
{
// read image size.
Byte[] imgSizeBytes = new Byte[4];
int totalBytesRead = 0;
do
{
int bytesRead = ns.Read(imgSizeBytes, totalBytesRead, 4 - totalBytesRead);
if (bytesRead == 0)
{
break; // problem
}
totalBytesRead += bytesRead;
} while (totalBytesRead < 4);
// read image
int imgSize = BitConverter.ToInt32(imgSizeBytes, 0);
Byte[] imgBytes = new Byte[imgSize];
totalBytesRead = 0;
do
{
int bytesRead = ns.Read(imgBytes, totalBytesRead, imgSize - totalBytesRead);
if (bytesRead == 0)
{
break; // problem
}
totalBytesRead += bytesRead;
} while (totalBytesRead < imgSize);
picVideo.Image = Image.FromStream(new MemoryStream(imgBytes));
_totalFramesReceived++;
}
}
private void CloseVideoDevice()
{
if (_videoDevice != null)
{
if (_videoDevice.IsRunning)
{
_videoDevice.SignalToStop();
}
_videoDevice = null;
}
}
private void fChat_FormClosing(object sender, FormClosingEventArgs e)
{
CloseVideoDevice();
}
private void btnClose_Click(object sender, EventArgs e)
{
Close();
}
private void tmrLive_Tick(object sender, EventArgs e)
{
_totalSecondsLive++;
lblStats.Text = "S:"+_totalFramesSent + " R:" + _totalFramesReceived + " T:"+ _totalSecondsLive;
if (_totalSecondsLive == 60)
{
MessageBox.Show("Total Frames : " + _totalFramesSent);
}
}
#region ENCODING JPEG
private MemoryStream EncodeToJpeg(Bitmap img, long quality)
{
using (EncoderParameters myEncoderParameters = new EncoderParameters(1))
{
MemoryStream ms = new MemoryStream();
myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality);
img.Save(ms, GetEncoder(ImageFormat.Jpeg), myEncoderParameters);
return ms;
}
}
private ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
#endregion
VideoCaptureDevice _videoDevice;
TcpClient _tcpOut;
TcpListener _tcpIn;
string _ownExternalIp;
int _totalFramesSent;
int _totalFramesReceived;
int _totalSecondsLive;
}
}
Well this is not a TCP specific problem. You are producing faster that you are consuming. Therefore you need to throttle your producer.
I would change the producer to wait for an acknowledgement from the consumer before I send the next image. I would discard all new images in the meantime.
On the producer you would keep a status flag which allows you to track if a frame has been sent and it has not been acknowledged. While this flag is true you discard new images as they appear. When it is false you send an image and set it to true. When the acknowledgement comes you set the flag to false.
Edit: I would implement the acknowledgement as a "bool" (a byte on the network) because this will acknowledge much faster than sending an image as a response. I would define two "messages": MessageType.Image and MessageType.Acknowledgement. The receiver can then see which MessageType arrived and either update the screen or start sending the next image.
Edit 2: You do not need to just discard images. You can have a variable Image latestUnsentImage. When the cam produces an image you unconditionally overwrite this variable. When you need to send an image you just access this variable. This will send always the latest available and unsent image.
What happens here, is that you fill up the buffers on the sending machine by generating data to send much faster, than it can be sent over the network. My approach would be
create two boolean flags newImageAvailable and readyToSend on the class level, both starting at false
In NewFrameReceived create a new image, but don't yet send it. Atomically store the byte[] in a class vairable instead and set newImageAvailable to true, then call new function TrySendImage() (see below for info)
On Tcp connect set readyToSend to true and call TrySendImage()
In TrySendImage()
.... check if BOTH newImageAvailable and readyToSend are true, if not do
nothing
.... set newImageAvailable and readyToSend to false
.... Send image (Size + data) asynchronously (BeginSend())
In the completion notification for BeginSend() set readyToSend to true
While this is a bit complicated, it makes sure, that allways the newest image is sent, and only if the image before is "on the wire".
I consider this superior to a "1:1 send/receive" solution, as you often have cases, where bandwidth is different in the two directions - in this case the 1:1 solution would degrade performance of the good direction to performance of the bad direction.
An optimization I have used on a similar project is, to dynamically adapt the quality parameter of the JPEG encoding: If framerate falls below a threshold reduce quality by a factor, until a minimum is reached, if framerate climbs above another threshold increase quality by a factor, again until a mmaximum.
Well as for what "usr" has said about throttling, instead of discarding your images, you might want to add these captured images to a queue. From te queue you should be able to pick them up one by one and via another thread and transport them over the network. You might even add a sequence number to the images being transported apart from the boolean acknowledgement to ensure a transport of images. Anything that is not transported should be enqueued again. Sequencing should allow you to sort the correct order of images and produce a lesser loss of data.
Sender
create sequence number
assign bool flag
Enqueue in transport queue
Dequeue and transport (separate thread)
Re queue if no acknowledgement
Receiver (possibly what you can do)
Receive from network
Send acknowledgement
Check sequence number and sort (may be storage required to sequence
numbering check)
Related
My window should display the status of the video stream
and the data sent and received in tcpip.
Every two seconds, the video appears to pause
when writing and reading data on TCP/IP.
How can we solve this phenomenon?
For your information,
The application programs that deal with video stream,
the stream look natural regardless of the tcpip I send and receive.
enter code here
private MJPEGStream m_Stream;
private DispatcherTimer m_Timer = null;
public TcpClient clientTcp = new TcpClient;
public NetworkStream streamTcp = new NetworkStream;
public MainWindow()
{
InitializeComponent();
IpCamera();
OpenTcpIpCommuncation();
}
public void OpenTcpIpCommuncation()
{
var result= clientTcp.BeginConnect("192.168.0.3","24", null, null);
streamTcp = clientTcp.GetStream();
m_Timer = new DispatcherTimer();
m_Timer.Interval = TimeSpan.FromSeconds(2.0);
m_Timer.Tick += new EventHandler(TcpTimer_Tick);
m_Timer.Start();
}
private void TcpTimer_Tick(object sender, EventArgs e)
{
for (int=0; i<20; i++)
{
stream.Write(send_status_packet, 0, Length);
Thread.Sleep(50);
NumberOfBytes = stream.Read(data, 0, data.Length);
}
// label, text etc upate from data
}
public void IpCamera()
{
string sUrl = "http://192.168.0.100:8080" "//" + kCameraInfo.SubUrl;
m_Stream = new MJPEGStream(sUrl);
m_Stream.Login = "admin";
m_Stream.Password = "1234";
m_Stream.NewFrame += Camera_Frame;
m_Stream.Start();
}
private void Camera_Frame(object sender, NewFrameEventArgs eventArgs)
{
try
{
BitmapImage bi;
using (var bitmap = (Bitmap)eventArgs.Frame.Clone())
{
bi = bitmap.ToBitmapImage();
}
bi.Freeze();
Dispatcher.BeginInvoke(new ThreadStart(delegate { imgCamera.Source = bi; }));
}
catch (Exception e)
{
}
}
Thread.Sleep blocks the UI thread.
Not sure why exactly you have this for loop, but it blocks the UI thread for at least 20 * 50 milliseconds, i.e. one second.
As a workaround, you may declare the Tick event handler async and use Task.Delay instead. You may perhaps also use the async versions of the Write and Read methods:
private async void TcpTimer_Tick(object sender, EventArgs e)
{
for (int i = 0; i < 20; i++)
{
await stream.WriteAsync(send_status_packet, 0, Length);
await Task.Delay(50);
NumberOfBytes = await stream.ReadAsync(data, 0, data.Length);
}
...
}
It might also be better not have the loop at all, and run the timer at a shorter interval.
I was just starting to use C# last week and tried to create a simple serial monitor program. I want my program to read data from serialport continuously while displaying those data in the Form Application at the same time.
I use the code from here as reference https://github.com/ahelsayd/Serial-Lab
I use the same BeginInvoke() function. However I can not pass the variable that I want to write.
This is the original code
private void rx_data_event(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (mySerial.IsOpen)
{
try
{
//=====Only this part is different=======================
int dataLength = mySerial.BytesToRead;
byte[] dataReceived = new byte[dataLength];
int nbytes = mySerial.Read(dataReceived, 0, dataLength);
if (nbytes == 0) return;
//=====Only this part is different=======================
this.BeginInvoke((Action)(() =>
{
data = System.Text.Encoding.Default.GetString(dataReceived);
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}));
}
catch { alert("Can't read form " + mySerial.PortName + " port it might be opennd in another program"); }
}
}
//And then update the UI
private void update_rxtextarea_event(object sender, DoWorkEventArgs e)
{
this.BeginInvoke((Action)(() =>
{
if (rx_textarea.Lines.Count() > 5000)
rx_textarea.ResetText();
rx_textarea.AppendText("[RX]> " + data);
}));
}
This code can read the Serialport and Write into the Form simultaneously. However, it does not receive all data from the serialport. So I modified the code to write the data into a buffer first until all data is received.
The modified code
private void rx_data_event(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (mySerial.IsOpen)
{
try
{
//=====Only this part is different=======================
string Data = mySerial.ReadExisting();
serialBuffer.Append(Data);
string bufferString = serialBuffer.ToString();
int index = -1;
do
{
index = bufferString.IndexOf(terminationSequence);
if (index > -1)
{
string message = bufferString.Substring(0, index);
bufferString = bufferString.Remove(0, index + terminationSequence.Length);
}
}
while (index > -1);
serialBuffer = new StringBuilder(bufferString);
byte[] bytes = new byte[30];
if (serialBuffer.Length == 15) {
Console.WriteLine("data:" + serialBuffer);
bytes = Encoding.ASCII.GetBytes(serialBuffer.ToString());
}
//=====Only this part is different=======================
this.BeginInvoke((Action)(() =>
{
data = Encoding.ASCII.GetString(bytes);
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}));
}
catch { alert("Can't read form " + mySerial.PortName + " port it might be opennd in another program"); }
}
}
The problem is in the Form application the value of bytes is always null although when I checked by writing the output value to console window I can see the value of bytes updated.
I am very confused why the variable dataReceived value can be accessed by BeginInvoke while the variable bytes keep having null value. Is there something that I've missed that causing the value not get updated?
So I wasn't able to do it yesterday, but I promised the OP I would write something in an answer related to a comment of mine. From what I understand he/she is trying to update something in a Form based on received data from a serial port. Here's how I do it:
First off you need to declare a delegate and an event (which is basically a list of delegates):
public delegate void Data_Received_EventHandler(object sender, Data_Received_EventArgs e);
public event Data_Received_EventHandler Data_Received;
These are used to substitute the original "data received" event and event args from the serial port.
I usually define the Data_Received_EventArgs type as something based of an array of bytes, for simplicity:
public class Data_Received_EventArgs : EventArgs
{
public byte[] data;
internal Data_Received_EventArgs(int length)
{
data = new byte[length];
}
}
Then, in the original data reception event (let's say your serial port is called serialPort1):
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// check how many bytes you need to read:
int bytesToRead = serialPort1.BytesToRead;
// declare your arguments for the event based on that number of bytes (check the Data_Received_EventArgs constructor):
Data_Received_EventArgs args = new Data_Received_EventArgs(bytesToRead);
// copy the bytes from your serial port into the arguments:
for (int i = 0; i < bytesToRead; i++)
args.data[i] = (byte)serialPort1.ReadByte();
// verify if there are subscribers to the event (list not empty) and fire your event using BeginInvoke:
if (Data_Received != null)
BeginInvoke((MethodInvoker)delegate () { Data_Received(this, args); });
}
What we now have is an event which is guaranteed to execute its handlers on the UI thread.
To use it, you can subscribe to the event like so:
Data_Received += My_Data_Received_Handler;
Your handler should have the same signature as the delegate we declared in the first line of code (should be void and have specified parameters):
private void My_Data_Received_Handler(object sender, Data_Received_EventArgs e)
{
// read bytes from the arguments as you normally would from an array and do whatever e.g.:
some_Label.Text = e.data[0].ToString(); // without worrying about crossthreading
}
I know this is not the answer the OP wanted, but I hope it helps in simplifying what he/she was trying to do in the first place.
I mean that if i give a List for exmaple of 30 files to upload it will open 30 slots of uploading will upload all the files in real time so i will see 30 progressBars and it will upload all the files paralel.
In form1 designer i have already one progressBar.
This is the class code for the ftp upload:
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text;
namespace FTP_ProgressBar
{
/// <summary>
/// The FtpProgress class is derived from background worker, so it has the built-in functions of
/// run, report progress, and completed.
/// It only does upload, as a demonstration of how to transfer a file by FTP and report the progress.
/// </summary>
public partial class FtpProgress : BackgroundWorker
{
public FtpProgress()
{
InitializeComponent();
}
public FtpProgress(IContainer container)
{
container.Add(this);
InitializeComponent();
}
private void FtpProgress_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
FtpSettings f = e.Argument as FtpSettings;
// set up the host string to request. this includes the target folder and the target file name (based on the source filename)
string UploadPath = String.Format("{0}/{1}{2}", f.Host, f.TargetFolder == "" ? "" : f.TargetFolder + "/", Path.GetFileName(f.SourceFile));
if(!UploadPath.ToLower().StartsWith("ftp://"))
UploadPath = "ftp://" + UploadPath;
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(UploadPath);
request.UseBinary = true;
request.UsePassive = f.Passive;
request.Method = WebRequestMethods.Ftp.UploadFile;
request.Credentials = new NetworkCredential(f.Username, f.Password);
// Copy the contents of the file to the request stream.
long FileSize = new FileInfo(f.SourceFile).Length;
string FileSizeDescription = GetFileSize(FileSize); // e.g. "2.4 Gb" instead of 240000000000000 bytes etc...
int ChunkSize = 4096, NumRetries = 0, MaxRetries = 50;
long SentBytes = 0;
byte[] Buffer = new byte[ChunkSize]; // this buffer stores each chunk, for sending to the web service via MTOM
using(Stream requestStream = request.GetRequestStream())
{
using(FileStream fs = File.Open(f.SourceFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
int BytesRead = fs.Read(Buffer, 0, ChunkSize); // read the first chunk in the buffer
// send the chunks to the web service one by one, until FileStream.Read() returns 0, meaning the entire file has been read.
while(BytesRead > 0)
{
try
{
if(bw.CancellationPending)
return;
// send this chunk to the server. it is sent as a byte[] parameter, but the client and server have been configured to encode byte[] using MTOM.
requestStream.Write(Buffer, 0, BytesRead);
// sentBytes is only updated AFTER a successful send of the bytes. so it would be possible to build in 'retry' code, to resume the upload from the current SentBytes position if AppendChunk fails.
SentBytes += BytesRead;
// update the user interface
string SummaryText = String.Format("Transferred {0} / {1}", GetFileSize(SentBytes), FileSizeDescription);
bw.ReportProgress((int)(((decimal)SentBytes / (decimal)FileSize) * 100), SummaryText);
}
catch(Exception ex)
{
Debug.WriteLine("Exception: " + ex.ToString());
if(NumRetries++ < MaxRetries)
{
// rewind the filestream and keep trying
fs.Position -= BytesRead;
}
else
{
throw new Exception(String.Format("Error occurred during upload, too many retries. \n{0}", ex.ToString()));
}
}
BytesRead = fs.Read(Buffer, 0, ChunkSize); // read the next chunk (if it exists) into the buffer. the while loop will terminate if there is nothing left to read
}
}
}
using(FtpWebResponse response = (FtpWebResponse)request.GetResponse())
System.Diagnostics.Debug.WriteLine(String.Format("Upload File Complete, status {0}", response.StatusDescription));
}
/// <summary>
/// Returns a description of a number of bytes, in appropriate units.
/// e.g.
/// passing in 1024 will return a string "1 Kb"
/// passing in 1230000 will return "1.23 Mb"
/// Megabytes and Gigabytes are formatted to 2 decimal places.
/// Kilobytes are rounded to whole numbers.
/// If the rounding results in 0 Kb, "1 Kb" is returned, because Windows behaves like this also.
/// </summary>
public static string GetFileSize(long numBytes)
{
string fileSize = "";
if(numBytes > 1073741824)
fileSize = String.Format("{0:0.00} Gb", (double)numBytes / 1073741824);
else if(numBytes > 1048576)
fileSize = String.Format("{0:0.00} Mb", (double)numBytes / 1048576);
else
fileSize = String.Format("{0:0} Kb", (double)numBytes / 1024);
if(fileSize == "0 Kb")
fileSize = "1 Kb"; // min.
return fileSize;
}
}
public class FtpSettings
{
public string Host, Username, Password, TargetFolder, SourceFile;
public bool Passive;
public int Port = 21;
}
}
And in form1: Upload button click event:
private void btnUpload_Click(object sender, EventArgs e)
{
// the upload button is also used as a cancel button, depending on the state of the FtpProgress thread
if(this.ftpProgress1.IsBusy)
{
this.ftpProgress1.CancelAsync();
this.btnUpload.Text = "Upload";
}
else
{
// create a new FtpSettings class to store all the paramaters for the FtpProgress thread
FtpSettings f = new FtpSettings();
f.Host = this.txtHost.Text;
f.Username = this.txtUsername.Text;
f.Password = this.txtPassword.Text;
f.TargetFolder = this.txtDir.Text;
f.SourceFile = this.txtUploadFile.Text;
f.Passive = this.chkPassive.Checked;
try
{
f.Port = Int32.Parse(this.txtPort.Text);
}
catch { }
this.toolStripProgressBar1.Visible = true;
this.ftpProgress1.RunWorkerAsync(f);
this.btnUpload.Text = "Cancel";
}
}
Backgorundworker progresschanged:
private void ftpProgress1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.toolStripStatusLabel1.Text = e.UserState.ToString(); // the message will be something like: 45 Kb / 102.12 Mb
this.toolStripProgressBar1.Value = Math.Min(this.toolStripProgressBar1.Maximum, e.ProgressPercentage);
}
And completed event:
private void ftpProgress1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Error != null)
MessageBox.Show(e.Error.ToString(), "FTP error");
else if(e.Cancelled)
this.toolStripStatusLabel1.Text = "Upload Cancelled";
else
this.toolStripStatusLabel1.Text = "Upload Complete";
this.btnUpload.Text = "Upload";
this.toolStripProgressBar1.Visible = false;
}
There are in form1 designer some textboxes that i enter password user name ftp address and file to upload.
This is the file browser for uploading:
private void btnBrowse_Click(object sender, EventArgs e)
{
if(this.openFileDialog1.ShowDialog() != DialogResult.Cancel)
this.txtUploadFile.Text = this.openFileDialog1.FileName;
}
Now instead one single file i want to be able to select multiple files and if i selected more then one file then it will upload the number of selected file at once paralel in real time.
If i selected 2 files it will create another new progressBar and will display on the two progressBars the files upload.
If i will select 34 files it will create 34 progressBars and will display on them the uploading progress.
When you browse multiple files at a time, the action takes place only once. i.e; all the files selected will reach the location(Server) which you have specified. So, that is the reason you are able to see only one progress bar.
If you need multiple progress bars, you should try placing multiple buttons for selecting each file through each button.
I have an application which connects with an external protocol
using serial communication.
I need know if the wakeup bit is set on each packet it sends to me (the 9 bit), and as communication rates must be below 40ms, and response must be sent under 20 ms.
The framework, encapsulates the bits read from the port, and only send back the 8 bits of data to me. Also, I cannot wait for the parity error event, because of timing issues.
I need to know how can I read the 9 bit, or if there is a free alternative to http://www.wcscnet.com/CdrvLBro.htm
Did you try to put your serial read function right in the parity error event handler? Depending on the driver, this might be fast enough.
This wouldn't happen to be for a certain slot machine protocol, would it? I did this for fun for you. Maybe it will work?
{
public Form1()
{
InitializeComponent();
}
SerialPort sp;
private void Form1_Load(object sender, EventArgs e)
{
sp = new SerialPort("COM1", 19200, Parity.Space, 8, StopBits.One);
sp.ParityReplace = 0;
sp.ErrorReceived += new SerialErrorReceivedEventHandler(sp_SerialErrorReceivedEventHandler);
sp.ReadTimeout = 5;
sp.ReadBufferSize = 256;
sp.Open();
}
object msgsLock = new object();
Queue<byte[]> msgs = new Queue<byte[]>();
public void sp_SerialErrorReceivedEventHandler(Object sender, SerialErrorReceivedEventArgs e)
{
if (e.EventType == SerialError.RXParity)
{
byte[] buffer = new byte[256];
try
{
int cnt = sp.Read(buffer, 0, 256);
byte[] msg = new byte[cnt];
Array.Copy(buffer, msg, cnt);
if (cnt > 0)
{
lock (msgsLock)
{
msgs.Enqueue(msg);
}
}
}
catch
{
}
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if (msgs.Count > 0)
{
lock (msgsLock)
{
listBox1.Items.Insert(0, BitConverter.ToString(msgs.Dequeue()));
}
}
}
}
}
Anyways, for more control over the serial port I suggest using the win32 calls to get what you want.
http://msdn.microsoft.com/en-us/magazine/cc301786.aspx
I have some SerialPort code that constantly needs to read data from a serial interface (for example COM1). But this seems to be very CPU intensive and if the user moves the window or a lot of data is being displayed to the window (such as the bytes that are received over the serial line) then communication gets messed up.
Considering the following code:
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
byte[] buffer = new byte[port.ReadBufferSize];
var count = 0;
try
{
count = port.Read(buffer, 0, buffer.Length);
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
if (count == 0)
return;
//Pass the data to the IDataCollector, if response != null an entire frame has been received
var response = collector.Collect(buffer.GetSubByteArray(0, count));
if (response != null)
{
this.OnDataReceived(response);
}
The code needs to be collected as the stream of data is constant
and the data has to be analyzed for (frames/packets).
port = new SerialPort();
//Port configuration code here...
this.collector = dataCollector;
//Event handlers
port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
port.Open();
If there is no user interaction and nothing being added to the window,
this works fine but as soon as there is interaction communication really gets messed up.
Timeouts occur etc....
For example, this messes everything up:
Dispatcher.BeginInvoke(new Action(() =>
{
var builder = new StringBuilder();
foreach (var r in data)
{
builder.AppendFormat("0x{0:X} ", r);
}
builder.Append("\n\n");
txtHexDump.AppendText(builder.ToString());
txtHexDump.ScrollToEnd();
}),System.Windows.Threading.DispatcherPriority.ContextIdle);
});
But even simple calls to log4net cause problems.
Are there any best practices to optimize SerialPort communication
or can someone tell me what I'm doing wrong...
Update:
In case the above didn't make much sence. I made a very simple (and stupid) little example:
class Program
{
static void Main(string[] args)
{
var server = new BackgroundWorker();
server.DoWork += new DoWorkEventHandler(server_DoWork);
server.RunWorkerAsync();
var port = new SerialPort();
port.PortName = "COM2";
port.Open();
string input = "";
Console.WriteLine("Client on COM2: {0}", Thread.CurrentThread.ManagedThreadId);
while (input != "/quit")
{
input = Console.ReadLine();
if (input != "/quit")
{
var data = ASCIIEncoding.ASCII.GetBytes(input);
port.Write(data, 0, data.Length);
}
}
port.Close();
port.Dispose();
}
static void server_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("Listening on COM1: {0}", Thread.CurrentThread.ManagedThreadId);
var port = new SerialPort();
port.PortName = "COM1";
port.Open();
port.ReceivedBytesThreshold = 15;
port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
}
static void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var port = (SerialPort)sender;
int count = 0;
byte[] buffer = new byte[port.ReadBufferSize];
count = ((SerialPort)sender).Read(buffer, 0, buffer.Length);
string echo = ASCIIEncoding.ASCII.GetString(buffer,0,count);
Console.WriteLine("-->{1} {0}", echo, Thread.CurrentThread.ManagedThreadId);
}
}
The result might look like this:
Listening on COM1: 6
Client on COM2: 10
This is some sample data that I send
---> 6 This is some sample data that I send
So reading the data from the port happens on the main thread....
Might this be part of what's causing my problems?
I am surprised no one caught this. The SerialPort class utilizes its own thread when using the DataReceived event. This means that if the subscriber, for example, is accessing any Form elements, then it must be done so with either an Invoke, or BeginInvoke method(s). Otherwise, you end up with a cross thread operation. In older versions of .net, this would go along unnoticed with unpredictable behaviour (depending on the CPU cores in the PC) and in later versions, should raise an exception.
Your last conclusion, that the event runs on the Main thread, may not be true for Windows App. Don't test this in a Console.
The proper way to tune this is:
set a large enough buffer, although the minimum 4096 is usually Ok
set the ReceivedBytesThreshold as high as tolerable (and do it before the Open())
do as little as possible in the Received event, pass the
data to a Queue or MemoryStream if you need more time
You should rewrite port_DataReceived procedure to read data until port.BytesToRead is greater then zero, like this:
private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
var port = (SerialPort)sender;
while (port.BytesToRead > 0)
{
int byte_count = port.BytesToRead;
byte[] buffer = new byte[byte_count];
int read_count = port.Read(buffer, 0, byte_count);
// PROCESS DATA HERE
}
}
Also I would recommend you to just insert data in queue list in procedure port_DataReceived, and perform data processing in separate thread.
The classic solution is to have a FIFO buffer. Ensure that the size of the FIFO is large enough to handle any critical case where there is a lot of input and the processor block is being taken up.
You could even have a 2-buffer system:
--->|Reader|-->FIFO-->|Processor|--->FIFO2--->|Displayer|