0

For a school project, I'm writing a UDP listener (with C#) that will receive thousands of UDP packets and save the data on a database (Simulated). Right now it's saving the data on a remote database.

I'm wondering whats the best way to listen to packets and lose as few as possible.

So, I have a thread that only listens for packets, when a packet is received I create a new thread to parse and save the received message on a remote database, latter there will be a webpage that will display the data.

1 - Should I be creating threads to handle each packet or is there a better way? How many threads should be the maximum allowed?

2 - I'm thinking of using a local database instead of a remote database for the UDP listener to store the data. I think each thread will end faster this way. There is also a webpage that will use the same database to show information for the clients (The database is local to the php server) . The webpage will also query the database a lot, I'm not sure which is best... for the database to be local for the UDP listener or local for the php server creating the webpage.

3 - Should there be a lock for the db.query()? I have it commented and it seems to work fine. This function is an insert to the DB.

4 - Some people have told me I should open()/close() the database connection before/after each insert... Right now I open it when I start the listener and only check to see if it's closed before inserting.

5 - Could Async/Await help my code in this case? Maybe using Async to insert on the database will make the thread end faster?

6 - I display the data on a DataGridView, but this seems to be very slow, is there a better way to display the data on the Main Form?

I will implement a method so that each packet received is acknowledged, and the client will resend if the ack fails, but I still would like it to fail as least as possible when receiving several thousands packets each minute.
Any comments on how to make this process better will be appreciated.

class UdpListener
{
    UdpClient listener;
    int port;
    byte[] receivedBytes;
    byte[] sendData;
    IPEndPoint ipep;
    IPEndPoint sender;
    Thread packetWorkerThread;
    DataBase db;
    MainForm mainForm;
    int threadCount, queryCount;

    public UdpListener(MainForm mainForm)
    {
        try
        {
            port = 1988;
            ipep = new IPEndPoint(IPAddress.Any, port);
            listener = new UdpClient(ipep);
            sender = new IPEndPoint(IPAddress.Any, 0);
            db = new DataBase();
            this.mainForm = mainForm;
            threadCount = queryCount = 0;
        }
        catch (Exception e) { }
    }

    public void start()
    {
        // Open db connection.
        //-- Maybe I should open/close before/after each insert?!
        if (db.connect())
        {
            while (true)
            {
                try
                {
                    receivedBytes = listener.Receive(ref sender);
                    Packet packetData = new Packet(sender, ipep, receivedBytes);

                    if(threadCount<10)
                    { 
                        //Launch Thread to process received data and save it to Database
                        packetWorkerThread = new Thread(p =>
                        {
                            workerThread_ProcessPacket(packetData);
                        });
                        packetWorkerThread.Start();
                    }
                }
                catch (Exception e) { }
            }
        }   
    }

    private void workerThread_ProcessPacket(Packet packetData)
    {
        try
        {
            lock (this)
            {
                threadCount++;
                queryCount++;
            }
            //lock (db)
            //{
                db.sqlQuery("SOME INSERT SQL");
            //}
            string data = GetHexStringFrom(packetData.ReceivedBytes);

            string[] row = new string[] { DateTime.Now.ToString(), packetData.FIP, packetData.FPort, packetData.TIP, packetData.TPort, data, threadCount.ToString(), queryCount.ToString() };
            mainForm.dataGridViewPackets.Invoke((MethodInvoker)delegate { mainForm.dataGridViewPackets.Rows.Add(row); });

            if (mainForm.dataGridViewPackets.RowCount >= 100)
            {
                mainForm.dataGridViewPackets.Invoke((MethodInvoker)delegate { mainForm.dataGridViewPackets.Rows.RemoveAt(0); });
            }

            lock (this)
            {
                threadCount--;
            }
        }
        catch (Exception e) { }
    }
Renato
  • 193
  • 1
  • 4
  • 16
  • 2
    Don't spin up explicit threads to handle UDP connections let alone each packet as it won't scale and you will no doubt exhaust the OS. Instead use IOCP such as is offered via **async/await** –  Jun 20 '17 at 00:44

1 Answers1

1

1 - Should I be creating threads to handle each packet or is there a better way? How many threads should be the maximum allowed?

If it were me, I'd use only two threads; one to listen for UDP, one to talk to the database. They would communicate via a ConcurrentQueue.

private ConcurrentQueue<Packet> _queue = new ConcurrentQueue<Packet>();
private volatile bool _cancel;

void Listener() {
    while (!_cancel) {
        var packet = GetNextPacket(out packet);
        _queue.Enqueue(packet);
    }

void Processor() {
    while (!_cancel) {
        Packet packet;
        var ok = _queue.TryDequeue(out packet);
        if (ok) {
            SaveToDatabase(packet);
        }
        else {
            Sleep(100);
        }
    }
}

If you really want to take advantage of multiple threads and connections, you can start several instances of Processor; each packet in the queue is guaranteed by the ConcurrentQueue logic to be processed only once.

I would start no more than the number of cores your processor has. Be aware that the database will still be a bottleneck because all of the connections will usually be trying to write to the same data page in the database and will lock each other out briefly, but you might still get some gains.

2 - I'm thinking of using a local database instead of a remote database for the UDP listener to store the data. I think each thread will end faster this way. There is also a webpage that will use the same database to show information for the clients (The database is local to the php server) . The webpage will also query the database a lot, I'm not sure which is best... for the database to be local for the UDP listener or local for the php server creating the webpage.

A local database isn't necessarily better, since the database would then share resources with your program, which I assume is sort of busy. Personally I would design the software to work with a database in any location, then test it in various configurations.

3 - Should there be a lock for the db.query()? I have it commented and it seems to work fine. This function is an insert to the DB.

Unless your database is very strange, the default locking semantics should be fine. In general, databases are designed for multiple clients.

4 - Some people have told me I should open()/close() the database connection before/after each insert... Right now I open it when I start the listener and only check to see if it's closed before inserting.

Usually you'd open and close for each operation. The overhead would be minimal because "closing" a connection doesn't actually close it-- it just sends it back to a connection pool. "Opening" just grabs it back.

That being said, in your specific situation, it may be a better idea to keep it open, because you're just doing one thing over and over, and you need it to run as fast as possible. Also, you don't need a connection pool-- you need exactly one connection (per thread) at all times.

5 - Could Async/Await help my code in this case? Maybe using Async to insert on the database will make the thread end faster?

If you use the two-thread approach that I recommend, await and async won't help anything. If you have a dedicated worker thread, it would need to await any prior async before proceeding, so you'd still be stuck processing packets one at a time.

6 - I display the data on a DataGridView, but this seems to be very slow, is there a better way to display the data on the Main Form?

If you have a very high volume of data then any forms control is going to be lackluster. It will be even worse if you are using a bound control and are refreshing the whole dataset every time. Try using an unbound DataGridView and add the data manually, one row at a time.

John Wu
  • 50,556
  • 8
  • 44
  • 80