how to read integer values from serial port in C#? - c#

I have a Keyence camera which communicates through RS-232. It is configured to output three integer values when triggered. I'm having trouble reading the integer values. I try to use a char array buffer but it only reads the first + sign in the output. I tested it using putty and output is something like this
+346.0,+261.0,098
I want to know if there is anything I need to use to read integer values like these?
static void Main(string[] args)
{
char[] buffer1 = new char[200] ;
SerialPort port = new SerialPort("COM4", 9600, Parity.None, 8, StopBits.One);
port.Open();
if (port.IsOpen) { Console.WriteLine("port is now open"); } else { Console.WriteLine("port not opened correctly"); }
port.Write("T"); //triggers the camera
port.Read(buffer1, 0, 200);
for (int i = 0; i < 200; i++)
{
Console.WriteLine(buffer1[i]);
}
Console.ReadLine();
}

I've had issues before with reading from the serial port and not reading in everything expected.
Turns out I was reading in the response from the device and it wasn't done yet writing. I figured the serial port object would continue trying to fill the buffer until the read timeout was hit, and that was not what was happening.
In my scenario I knew how many characters I was going to be reading from the serial port. So if you know that you could implement a repeat on the read until your character buffer is full. I don't know if the same would apply if you are reading from SerialPort.BaseStream.
SerialPort serialPort;
char[] buffer = new char[expectedLength];
int totalBytesRead = 0;
//continue to read until all of the expected characters have been read
while (totalBytesRead < expectedLength)
{
totalBytesRead += serialPort.Read(buffer, totalBytesRead, expectedLength - totalBytesRead);
}

This is the code I use (simplified):
public class Scanner : SerialPort
{
private string _word;
private int _globalCounter;
private readonly char[] _rxArray = new char[2047];
public Scanner()
{
DataReceived += MyDataReceivedEventHandler;
}
public event EventHandler<CodeScannedEventArgs> CodeScanned;
private void MyDataReceivedEventHandler(object sender, SerialDataReceivedEventArgs e)
{
do
{
var rxByte = (byte)ReadByte();
// end of word
if (rxByte == 10)
{
// first byte (02) and last two bytes (13 and 10) are ignored
_word = new string(_rxArray, 1, _globalCounter - 2);
DisplayData(_word);
_globalCounter = 0;
}
else
{
_rxArray[_globalCounter] = (char)rxByte;
_globalCounter++;
}
} while (BytesToRead > 0);
}
private void DisplayData(string receivedText)
{
OnCodeScanned(new CodeScannedEventArgs(receivedText));
}
protected void OnCodeScanned(CodeScannedEventArgs e)
{
EventHandler<CodeScannedEventArgs> handler = CodeScanned;
if (handler != null)
{
handler(this, e);
}
}
}
The scanner I use adds byte 02 as a prefix and bytes 13 and 10 as postfix to everything it scans, so it is pretty easy for me to break it up into words. You'll obviously need to change the implementation slightly so it works for you.
Edit - CodeScannedEventArgs class:
public class CodeScannedEventArgs : EventArgs
{
public CodeScannedEventArgs(string scannedCode)
{
ScannedCode = scannedCode;
}
public string ScannedCode { get; set; }
}

I used the port.ReadTo("\r") and it works, as the output ends with a carriage return.
But I want to know what is the advantage of using a data received event?

Related

extract number out strings c# [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I Have the string :
Humidity: 33 %
Temperature: 25.7 deg C
Visible light: 112 lx
Infrared radiation: 1802.5 mW/m2
UV index: 0.12
CO2: 404 ppm CO2
Pressure: 102126 Pa
I have to extract all the numbers coming after 'Humidity:' , ..
I was thinking to use the Regex class but i dont know exactly how to do it
My code for getting the serial data :
namespace Demo1Arduino
{
public partial class MainWindow : Window
{
private SerialPort port;
DispatcherTimer timer = new DispatcherTimer();
private string buff;
public MainWindow()
{
InitializeComponent();
}
private void btnOpenPort_Click(object sender, RoutedEventArgs e)
{
timer.Tick += timer_Tick;
timer.Interval = new TimeSpan(0, 0, 0, 0, 500);
timer.Start();
try
{
port = new SerialPort(); // Create a new SerialPort object with default settings.
port.PortName="COM4";
port.BaudRate = 115200; // Opent de seriele poort, zet data snelheid op 9600 bps.
port.StopBits = StopBits.One; // One Stop bit is used. Stop bits separate each unit of data on an asynchronous serial connection. They are also sent continuously when no data is available for transmission.
port.Parity = Parity.None; // No parity check occurs.
port.DataReceived += Port_DataReceived;
port.Open(); // Opens a new serial port connection.
buff = "";
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void timer_Tick(object sender, EventArgs e)
{
try
{
if(buff != "")
{
textBox.Text += buff;
buff = "";
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
byte[] buffer = new byte[128];
int len = port.Read(buffer, 0, buffer.Length); // .Read --> Reads a number of characters from the SerialPort input buffer and writes them into an array of characters at a given offset.
if(len>0)
{
string str = "";
for (int i=0; i<len; i++)
{
if (buffer[i] != 0)
{
str = str + ((char)buffer[i]).ToString();
}
}
buff += str;
}
// throw new NotImplementedException();
}
}
Thank you
Try regular expression, the only trick is CO2 and m2 - we don't want 2 that's why I've added \b:
string source =
#"Humidity: 33 %
Temperature: 25.7 deg C
Visible light: 112 lx
Infrared radiation: 1802.5 mW/m2
UV index: 0.12
CO2: 404 ppm CO2
Pressure: 102126 Pa";
string[] numbers = Regex
.Matches(source, #"\b[0-9]+(?:\.[0-9]+)?\b")
.OfType<Match>()
.Select(match => match.Value)
.ToArray();
Test
Console.Write(string.Join("; ", numbers));
Outcome
33; 25.7; 112; 1802.5; 0.12; 404; 102126
It makes no sense to get multiple number without know the type. I put values into a dictionary to make it easy to use the number later in the code. See code below and https://msdn.microsoft.com/en-us/library/az24scfc(v=vs.110).aspx:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ConsoleApplication55
{
class Program
{
static void Main(string[] args)
{
string[] inputs = {
"Humidity: 33 %",
"Temperature: 25.7 deg C",
"Visible light: 112 lx",
"Infrared radiation: 1802.5 mW/m2",
"UV index: 0.12",
"CO2: 404 ppm CO2",
"Pressure: 102126 Pa"
};
string pattern = #"^(?'name'[^:]+):\s(?'value'[\d.]+)";
Dictionary<string, decimal> dict = new Dictionary<string,decimal>();
foreach(string input in inputs)
{
Match match = Regex.Match(input,pattern);
string name = match.Groups["name"].Value;
decimal value = decimal.Parse(match.Groups["value"].Value);
Console.WriteLine("name = '{0}', value = '{1}'", name, value);
dict.Add(name, value);
}
Console.ReadLine();
}
}
}

Need to know how to put a space between every two characters and a line between the text each time I scan

public partial class Form1 : Form
{
public delegate void AddDataDelegate(String myString);
public AddDataDelegate myDelegate;
string[] Tags;
public Form1()
{
InitializeComponent();
SerialPort RFID = new SerialPort();
RFID.PortName = "COM5";
RFID.BaudRate = 9600;
RFID.DataBits = 8;
RFID.Parity = Parity.None;
RFID.StopBits = StopBits.One;
RFID.Open();
RFID.DataReceived += new SerialDataReceivedEventHandler(RFID_DataReceived);
this.myDelegate = new AddDataDelegate(AddDataMethod);
}
public void AddDataMethod(String myString)
{
textBox1.AppendText(myString);
}
public void RFID_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort RFID = (SerialPort)sender;
int buffer = RFID.ReadBufferSize;
byte[] data = new byte[buffer];
RFID.Read(data, 0, buffer);
string s;
for (int i = 0; i < 18; i++)
{
s = Convert.ToString(data[i], 16);
//Console.WriteLine(Convert.ToString(data[i], 16));
textBox1.Invoke(this.myDelegate, new Object[] { s });
}
//Console.WriteLine(Convert.ToString(i,16));
//textBox1.Invoke(this.myDelegate, new Object[] { s });
}
The above prints the data to a text box. How can I create a space every two characters and leave a line between the data from each scan.
Current output after three scans:
00e203027313032180000000605641acff00000000000000e20302731301716606c200001b5ff00000000000000000e203027313000000000001716606c21b5ff000000000
You may append a space after each converted byte and append a new line (Environment.NewLine) after you've converted the entire buffer.
Please also note that myDelegate should be called after the loop:
public void RFID_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort RFID = (SerialPort)sender;
int buffer = RFID.ReadBufferSize;
byte[] data = new byte[buffer];
RFID.Read(data, 0, buffer);
string s = "";
for (int i = 0; i < 18; i++)
{
s += Convert.ToString(data[i], 16) + " ";
}
s += Environment.NewLine;
textBox1.Invoke(this.myDelegate, new Object[] { s });
}
Alternative solution:
With a little help of LINQ you may format your output string in one go:
public void RFID_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort RFID = (SerialPort)sender;
int buffer = RFID.ReadBufferSize;
byte[] data = new byte[buffer];
RFID.Read(data, 0, buffer);
string s = string.Join(" ", data.Select(b => b.ToString("x2"))) + Environment.NewLine;
textBox1.Invoke(this.myDelegate, new Object[] { s });
}
Where
data.Select(b => b.ToString("x2")) converts an array of bytes to an array of the bytes' string representations using x2 format.
string.Join(" ", ...) concatenates this array of strings onto one string with the elements delimited by a space
+ Environment.NewLine finally ensures the next data portion will go to a new line.

C# program Slowing down due to function

I am writing a C# program to interface with an Arduino through a serial port and I have a function updateRPMs() that slows down the program so much it is unusable. It gets called every 1 second when it is used. The program runs a few PWM fans.
Here is the function:
private void updateRPMs()
{
TextBox[] RPMS = { Fan1RPM, Fan2RPM, Fan3RPM, Fan4RPM, Fan5RPM, Fan6RPM, Fan7RPM, Fan8RPM, Fan9RPM, Fan10RPM, Fan11RPM, Fan12RPM };
List<String> sepData = new List<String>();
if (CONNECTED)
{
String data = serialPort1.ReadLine();
// MessageBox.Show(data);
sepData = (data.Split(';').ToList());
if (sepData.Count == 12)
{
for (int i = 0; i < 12; i++)
{
RPMS[i].Text = sepData[i];
}
}
serialPort1.DiscardOutBuffer();
}
}
This is something the Arduino would send to the program:
a840.00;b885.00;c0;d0;e0;f0;g1635.00;h2070.00;i0;j0;k0;l0
I know I can push this to a different thread but I am trying to have it update as soon as the timer fires.
I was wondering if there is anything I could change or if there was anything stupid I did. I am new to C# and any help would be appreciated.
For reading from serial, I suggest you to avoid using a timer to read from the serial. You can use the DataReceived event, which is fired every time some data is received from the serial.
Of course you could receive a partial packet, so it's better to store the data in a buffer and then analyze it.
String readBuffer = "";
private static void DataReceivedHandler(
object sender,
SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
readBuffer += sp.ReadExisting();
int newLineIndex = -1;
while ((newLineIndex = readBuffer.IndexOf("\n")) >= 0)
{ // Analyze buffer
String currentLine = readBuffer.Substring(0,newLineIndex);
if (currentLine.length() > 0)
analyzeLine(currentLine);
readBuffer = readBuffer.Substring(newLineIndex+1);
}
}
public void analyzeLine(String data)
{
static TextBox[] RPMS = { Fan1RPM, Fan2RPM, Fan3RPM, Fan4RPM, Fan5RPM, Fan6RPM, Fan7RPM, Fan8RPM, Fan9RPM, Fan10RPM, Fan11RPM, Fan12RPM };
List<String> sepData = (data.Split(';').ToList());
if (sepData.Count == 12)
{
for (int i = 0; i < 12; i++)
{
RPMS[i].Text = sepData[i];
}
}
}
I assume you know how to attach to an event, since you already used the timer one ini your code ;)

Play video frame by frame performance issues

I want to play a video (mostly .mov with Motion JPEG) in frame by frame mode with changing framerate. I have a function who gives me a framenumber and then I have to jump there. It will be mostly in one direction but can skip a few frames from time to time; also the velocity is not constant.
So I have a timer asking every 40ms about a new framenumber and setting the new position.
My first approach now is with DirectShow.Net (Interop.QuartzTypeLib). Therefore I render and open the video and set it to pause to draw the picture in the graph
FilgraphManagerClass media = new FilgraphManagerClass();
media.RenderFile(FileName);
media.pause();
Now I will just set a new position
media.CurrentPosition = framenumber * media.AvgTimePerFrame;
Since the video is in pause mode it will then draw every requested new position (frame). Works perfectly fine but really slow... the video keeps stuttering and lagging and its not the video source; there are enough frames recorded to play a fluent video.
With some performance tests I found out that the LAV-Codec is the bottleneck here. This is not included directly in my project since its a DirectShow-Player it will be cast through my codec pack I installed on my PC.
Ideas:
Using the LAV-Codec by myself directly in C#. I searched but everyone is using DirectShow it seems, building their own filters and not using existing ones directly in the project.
Instead of seeking or setting the time, can I get single frames just by the framenumber and draw them simply?
Is there a complete other way to archive what I want to do?
Background:
This project has to be a train simulator. We recorded real time videos of trains driving from inside the cockpit and know which frame is what position. Now my C# programm calculates the position of the train in dependence of time and acceleration, gives back the appropriate framenumber and draw this frame.
Additional Information:
There is another project (not written by me) in C/C++ who uses DirectShow and the avcodec-LAV directly with a similar way I do and it works fine! Thats because I had the idea to use a codec / filter like the avrcodec-lav by myself. But I can't find an interop or interface to work with C#.
Thanks everyone for reading this and trying to help! :)
Obtaining specific frame by seeking filter graph (the entire pipeline) is pretty slow since every seek operation involves the following on its backyard: flushing everything, possibly re-creating worker threads, seeking to first key frame/splice point/clean point/I-Frame before the requested time, start of decoding starting from found position skipping frames until originally requested time is reached.
Overall, the method works well when you scrub paused video, or retrieve specific still frames. When however you try to play this as smooth video, it eventually causes significant part of the effort to be wasted and spent on seeking within video stream.
Solutions here are:
re-encode video to remove or reduce temporal compression (e.g. Motion JPEG AVI/MOV/MP4 files)
whenever possible prefer to skip frames and/or re-timestamp them according to your algorithm instead of seeking
have a cached of decoded video frames and pick from there, populate them as necessary in worker thread
The latter two are unfortunately hard to achieve without advanced filter development (where continuous decoding without interruption by seeking operations is the key to achieving decent performance). With basic DirectShow.Net you only have basic control over streaming and hence the first item from the list above.
Wanted to post a comment instead of an answer, but don't have the reputation. I think your heading in the wrong direction with Direct Show. I've been messing with motion-jpeg for a few years now between C# & Android, and have gotten great performance with built-in .NET code (for converting byte-array to Jpeg frame) and a bit of multi-threading. I can easily achieve over 30fps from multiple devices with each device running in it's own thread.
Below is an older version of my motion-jpeg parser from my C# app 'OmniView'. To use, just send the network stream to the constructor, and receive the OnImageReceived event. Then you can easily save the frames to the hard-drive for later use (perhaps with the filename set to the timestamp for easy lookup). For better performance though, you will want to save all of the images to one file.
using OmniView.Framework.Helpers;
using System;
using System.IO;
using System.Text;
using System.Windows.Media.Imaging;
namespace OmniView.Framework.Devices.MJpeg
{
public class MJpegStream : IDisposable
{
private const int BUFFER_SIZE = 4096;
private const string tag_length = "Content-Length:";
private const string stamp_format = "yyyyMMddHHmmssfff";
public delegate void ImageReceivedEvent(BitmapImage img);
public delegate void FrameCountEvent(long frames, long failed);
public event ImageReceivedEvent OnImageReceived;
public event FrameCountEvent OnFrameCount;
private bool isHead, isSetup;
private byte[] buffer, newline, newline_src;
private int imgBufferStart;
private Stream data_stream;
private MemoryStream imgStreamA, imgStreamB;
private int headStart, headStop;
private long imgSize, imgSizeTgt;
private bool useStreamB;
public volatile bool EnableRecording, EnableSnapshot;
public string RecordPath, SnapshotFilename;
private string boundary_tag;
private bool tagReadStarted;
private bool enableBoundary;
public volatile bool OututFrameCount;
private long FrameCount, FailedCount;
public MJpegStream() {
isSetup = false;
imgStreamA = new MemoryStream();
imgStreamB = new MemoryStream();
buffer = new byte[BUFFER_SIZE];
newline_src = new byte[] {13, 10};
}
public void Init(Stream stream) {
this.data_stream = stream;
FrameCount = FailedCount = 0;
startHeader(0);
}
public void Dispose() {
if (data_stream != null) data_stream.Dispose();
if (imgStreamA != null) imgStreamA.Dispose();
if (imgStreamB != null) imgStreamB.Dispose();
}
//=============================
public void Process() {
if (isHead) processHeader();
else {
if (enableBoundary) processImageBoundary();
else processImage();
}
}
public void Snapshot(string filename) {
SnapshotFilename = filename;
EnableSnapshot = true;
}
//-----------------------------
// Header
private void startHeader(int remaining_bytes) {
isHead = true;
headStart = 0;
headStop = remaining_bytes;
imgSizeTgt = 0;
tagReadStarted = false;
}
private void processHeader() {
int t = BUFFER_SIZE - headStop;
headStop += data_stream.Read(buffer, headStop, t);
int nl;
//
if (!isSetup) {
byte[] new_newline;
if ((nl = findNewline(headStart, headStop, out new_newline)) >= 0) {
string tag = Encoding.UTF8.GetString(buffer, headStart, nl - headStart);
if (tag.StartsWith("--")) boundary_tag = tag;
headStart = nl+new_newline.Length;
newline = new_newline;
isSetup = true;
return;
}
} else {
while ((nl = findData(newline, headStart, headStop)) >= 0) {
string tag = Encoding.UTF8.GetString(buffer, headStart, nl - headStart);
if (!tagReadStarted && tag.Length > 0) tagReadStarted = true;
headStart = nl+newline.Length;
//
if (!processHeaderData(tag, nl)) return;
}
}
//
if (headStop >= BUFFER_SIZE) {
string data = Encoding.UTF8.GetString(buffer, headStart, headStop - headStart);
throw new Exception("Invalid Header!");
}
}
private bool processHeaderData(string tag, int index) {
if (tag.StartsWith(tag_length)) {
string val = tag.Substring(tag_length.Length);
imgSizeTgt = long.Parse(val);
}
//
if (tag.Length == 0 && tagReadStarted) {
if (imgSizeTgt > 0) {
finishHeader(false);
return false;
}
if (boundary_tag != null) {
finishHeader(true);
return false;
}
}
//
return true;
}
private void finishHeader(bool enable_boundary) {
int s = shiftBytes(headStart, headStop);
enableBoundary = enable_boundary;
startImage(s);
}
//-----------------------------
// Image
private void startImage(int remaining_bytes) {
isHead = false;
imgBufferStart = remaining_bytes;
Stream imgStream = getStream();
imgStream.Seek(0, SeekOrigin.Begin);
imgStream.SetLength(imgSizeTgt);
imgSize = 0;
}
private void processImage() {
long img_r = (imgSizeTgt - imgSize - imgBufferStart);
int bfr_r = Math.Max(BUFFER_SIZE - imgBufferStart, 0);
int t = (int)Math.Min(img_r, bfr_r);
int s = data_stream.Read(buffer, imgBufferStart, t);
int x = imgBufferStart + s;
appendImageData(0, x);
imgBufferStart = 0;
//
if (imgSize >= imgSizeTgt) processImageData(0);
}
private void processImageBoundary() {
int t = Math.Max(BUFFER_SIZE - imgBufferStart, 0);
int s = data_stream.Read(buffer, imgBufferStart, t);
//
int nl, start = 0;
int end = imgBufferStart + s;
while ((nl = findData(newline, start, end)) >= 0) {
int tag_length = boundary_tag.Length;
if (nl+newline.Length+tag_length > BUFFER_SIZE) {
appendImageData(start, nl+newline.Length - start);
start = nl+newline.Length;
continue;
}
//
string v = Encoding.UTF8.GetString(buffer, nl+newline.Length, tag_length);
if (v == boundary_tag) {
appendImageData(start, nl - start);
int xstart = nl+newline.Length + tag_length;
int xsize = shiftBytes(xstart, end);
processImageData(xsize);
return;
} else {
appendImageData(start, nl+newline.Length - start);
}
start = nl+newline.Length;
}
//
if (start < end) {
int end_x = end - newline.Length;
if (start < end_x) {
appendImageData(start, end_x - start);
}
//
shiftBytes(end - newline.Length, end);
imgBufferStart = newline.Length;
}
}
private void processImageData(int remaining_bytes) {
if (EnableSnapshot) {
EnableSnapshot = false;
saveSnapshot();
}
//
try {
BitmapImage img = createImage();
if (EnableRecording) recordFrame();
if (OnImageReceived != null) OnImageReceived.Invoke(img);
FrameCount++;
}
catch (Exception) {
// output frame error ?!
FailedCount++;
}
//
if (OututFrameCount && OnFrameCount != null) OnFrameCount.Invoke(FrameCount, FailedCount);
//
useStreamB = !useStreamB;
startHeader(remaining_bytes);
}
private void appendImageData(int index, int length) {
Stream imgStream = getStream();
imgStream.Write(buffer, index, length);
imgSize += (length - index);
}
//-----------------------------
private void recordFrame() {
string stamp = DateTime.Now.ToString(stamp_format);
string filename = RecordPath+"\\"+stamp+".jpg";
//
ImageHelper.Save(getStream(), filename);
}
private void saveSnapshot() {
Stream imgStream = getStream();
//
imgStream.Position = 0;
Stream file = File.Open(SnapshotFilename, FileMode.Create, FileAccess.Write);
try {imgStream.CopyTo(file);}
finally {file.Close();}
}
private BitmapImage createImage() {
Stream imgStream = getStream();
imgStream.Position = 0;
return ImageHelper.LoadStream(imgStream);
}
//-----------------------------
private Stream getStream() {return useStreamB ? imgStreamB : imgStreamA;}
private int findNewline(int start, int stop, out byte[] data) {
for (int i = start; i < stop; i++) {
if (i < stop-1 && buffer[i] == newline_src[0] && buffer[i+1] == newline_src[1]) {
data = newline_src;
return i;
} else if (buffer[i] == newline_src[1]) {
data = new byte[] {newline_src[1]};
return i;
}
}
data = null;
return -1;
}
private int findData(byte[] data, int start, int stop) {
int data_size = data.Length;
for (int i = start; i < stop-data_size; i++) {
if (findInnerData(data, i)) return i;
}
return -1;
}
private bool findInnerData(byte[] data, int buffer_index) {
int count = data.Length;
for (int i = 0; i < count; i++) {
if (data[i] != buffer[buffer_index+i]) return false;
}
return true;
}
private int shiftBytes(int start, int end) {
int c = end - start;
for (int i = 0; i < c; i++) {
buffer[i] = buffer[end-c+i];
}
return c;
}
}
}

Ideas to manage IP and Ports in Key/Value format?

I'm looking for a good and fast way to manage IP addresses and ports in a file. Sort of a DB Table that has 2 columns: IP and Port, but in a file, without using a DB.
It has to support adding, deleting and updating. I don't care from concurrency.
Below, some come to complete your task. I tried to go strictly to the point, so maybe something is missing.
I'd to create a "Record" class, to keep ip/port pairs
class Record : IPEndPoint, IComparable<Record>
{
internal long Offset { get; set; }
public bool Deleted { get; internal set; }
public Record() : base(0, 0)
{
Offset = -1;
Deleted = false;
}
public int CompareTo(Record other)
{
if (this.Address == other.Address && this.Address == other.Address )
return 0;
else if (this.Address == other.Address)
return this.Port.CompareTo(other.Port);
else
return
BitConverter.ToInt32(this.Address.GetAddressBytes(), 0).CompareTo(
BitConverter.ToInt32(other.Address.GetAddressBytes(), 0));
}
}
class RecordComparer : IComparer<Record>
{
public int Compare(Record x, Record y)
{
return x.CompareTo(y);
}
}
...And a "DatabaseFile" class to manage datafile interaction.
class DatabaseFile : IDisposable
{
private FileStream file;
private static int RecordSize = 7;
private static byte[] Deleted = new byte[] { 42 };
private static byte[] Undeleted = new byte[] { 32 };
public DatabaseFile(string filename)
{
file = new FileStream(filename,
FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
}
public IEnumerable<Record> Locate(Predicate<Record> record)
{
file.Seek(0, SeekOrigin.Begin);
while (file.Position < file.Length)
{
long offset = file.Position;
byte[] buffer = new byte[DatabaseFile.RecordSize];
file.Read(buffer, 0, DatabaseFile.RecordSize);
Record current = Build(offset, buffer);
if (record.Invoke(current))
yield return current;
}
}
public void Append(Record record)
{
// should I look for duplicated values? i dunno
file.Seek(0, SeekOrigin.End);
record.Deleted = false;
record.Offset = file.Position;
Write(record);
}
public void Delete(Record record)
{
if (record.Offset == -1) return;
file.Seek(record.Offset, SeekOrigin.Begin);
record.Deleted = true;
Write(record);
}
public void Update(Record record)
{
if (record.Offset == -1)
{
Append(record);
}
else
{
file.Seek(record.Offset, SeekOrigin.Begin);
Write(record);
}
}
private void Write(Record record)
{
file.Write(GetBytes(record), 0, DatabaseFile.RecordSize);
}
private Record Build(long offset, byte[] data)
{
byte[] ipAddress = new byte[4];
Array.Copy(data, 1, ipAddress, 0, ipAddress.Length);
return new Record
{
Offset = offset,
Deleted = (data[0] == DatabaseFile.Deleted[0]),
Address = new IPAddress(ipAddress),
Port = BitConverter.ToInt16(data, 5)
};
}
private byte[] GetBytes(Record record)
{
byte[] returnValue = new byte[DatabaseFile.RecordSize];
Array.Copy(
record.Deleted ? DatabaseFile.Deleted : DatabaseFile.Undeleted, 0,
returnValue, 0, 1);
Array.Copy(record.Address.GetAddressBytes(), 0,
returnValue, 1, 4);
Array.Copy(BitConverter.GetBytes(record.Port), 0,
returnValue, 5, 2);
return returnValue;
}
public void Pack()
{
long freeBytes = 0;
byte[] buffer = new byte[RecordSize];
Queue<long> deletes = new Queue<long>();
file.Seek(0, SeekOrigin.Begin);
while (file.Position < file.Length)
{
long offset = file.Position;
file.Read(buffer, 0, RecordSize);
if (buffer[0] == Deleted[0])
{
deletes.Enqueue(offset);
freeBytes += RecordSize;
}
else
{
if (deletes.Count > 0)
{
deletes.Enqueue(offset);
file.Seek(deletes.Dequeue(), SeekOrigin.Begin);
file.Write(buffer, 0, RecordSize);
file.Seek(offset + RecordSize, SeekOrigin.Begin);
}
}
}
file.SetLength(file.Length - freeBytes);
}
public void Sort()
{
int offset = -RecordSize; // lazy method
List<Record> records = this.Locate(r => true).ToList();
records.Sort(new RecordComparer());
foreach (Record record in records)
{
record.Offset = offset += RecordSize;
Update(record);
}
}
public void Dispose()
{
if (file != null)
file.Close();
}
}
Below, a working example:
static void Main(string[] args)
{
List<IPEndPoint> endPoints = new List<IPEndPoint>(
new IPEndPoint[]{
new IPEndPoint(IPAddress.Parse("127.0.0.1"), 80),
new IPEndPoint(IPAddress.Parse("69.59.196.211"), 80),
new IPEndPoint(IPAddress.Parse("74.125.45.100"), 80)
});
using (DatabaseFile dbf = new DatabaseFile("iptable.txt"))
{
foreach (IPEndPoint endPoint in endPoints)
dbf.Append(new Record {
Address = endPoint.Address,
Port = endPoint.Port });
Record stackOverflow = dbf.Locate(r =>
Dns.GetHostEntry(r.Address)
.HostName.Equals("stackoverflow.com")).FirstOrDefault();
if (stackOverflow != null)
dbf.Delete(stackOverflow);
Record google = dbf.Locate(r =>
r.Address.ToString() == "74.125.45.100").First();
google.Port = 443;
dbf.Update(google);
foreach(Record http in dbf.Locate(r =>
!r.Deleted && r.Port == 80))
Console.WriteLine(http.ToString());
}
Console.ReadLine();
}
dBase III, I miss you.
Well, that was fun, thank you!
EDIT 1: Added Pack() and lazy Sort() code;
EDIT 2: Added missing IComparable/IComparer implementation
I personally will go for
192.100.10.1:500:20-21
192.100.10.2:27015-27016:80
Where the first is the Ip and every thing after the : is a port, We can also represent a range by - and if we want to be very crazy about it we can introduce a u which will represent the port type UDP or TCP for example:
192.100.10.2:27015-27016:80:90u
And explode() would work for the above quite easily.
When talking about Inserting Deleting and updating.. We can simply create a class structure such as
struct port{
int portnum;
char type;
port(int portnum = 0, char type = 't'){
this.portnum = portnum; this.type = type;
}
}
class Ip{
public:
string Ip_str;
list <port> prt;
}
And then you can have the main to look like
int main(){
list<Ip> Ips;
//Read the list from file and update the list.
//Sort delete update the list
//Rewrite the list back into file in the order mentioned obove
return 0;
}
The easiest way is probably to create a small class that contains your IP and port
class IpAddress
{
public string IP;
public int port;
}
and then create a list<IpAddress> of them. You can then use XML Serialization and Deserialization to read to and write from a file your list.
The .NET BCL does not offer what you are looking for as you want to query against a file without loading it into memory first and support add/remove. So you'd either have to roll your own embedded database or you could simply use something like SQLite http://www.sqlite.org/
IP and Port is a one to many relationship. I would consider something like this
\t192.168.1.1\r\n25\r\n26\r\n\t192.168.1.2\r\n2\r\n80\r\n110
where \t is a tab and \r\n is a carriage return followed by a newline
So when you parse, if you hit a tab character, you know everything that's in that line from there to the newline is an IP address, then everything in between the next newlines is a port number for that IP address until you hit a tab, in which case you're on a new IP address. That's simple and fast but not as human readable.
this has nothing to do with IP and ports.. the problem is that, as far as i know, windows does not allow to INSERT or remove bytes in/from the middle of a file..

Categories

Resources