Search code examples
c.net-coreipcmkfifo

inter-process communication between linux and dotnet using mkfifo


We have an application written in c that sends events/notifications to an application written in c#. Both applications run on the same linux computer.

The C application:

The C application is Asterisk and we modified the source code (it is open source) so that it can send events to our dotnet console application. The way we currently send events is simply by appending text to a file. For example this is how we send an event that a new peer (ip-phone) connected:

// place this on chan_sip.c
// Example: 1-LN-48T6-E3C5-OFWT|10.0.0.103:5868|189.217.18.244|10216|Z 3.9.32144 r32121
if(!ast_sockaddr_isnull(&peer->addr))
{
    // lock 
    ast_mutex_lock(&some_lock);

        // write to file
        FILE *pFile;
        pFile=fopen("/var/log/asterisk/peer-subscriptions.txt", "a");
        if(pFile==NULL) { perror("Error opening file."); }
        else {
                fprintf(pFile,"%s|%s|%s|%s|%s\n",
                /* 1-LN-48T6-E3C5-OFWT */   peer->name,
                /* 10.0.0.103:5868     */   pvt->initviasentby,
                /* 189.217.18.244      */   ast_sockaddr_stringify_addr(&peer->addr),
                /* 10216               */   ast_strdupa(ast_sockaddr_stringify_port(&peer->addr)),
                /* Z 3.9.32144         */   peer->useragent
                    // Other:
                    // peer->fullcontact, // sip:[email protected]:10216;rinstance=8b4135488f735cbf;transport=UDP
                    // pvt->via      //  SIP/2.0/UDP 54.81.92.135:20001;branch=z9hG4bK58525e18;rport
                    );
        }
        fclose(pFile);

    // unlock
    ast_mutex_lock(&some_lock);
 }

The C# application The c# application is a console application that opens that file for reading events nothing fancy.

So basically the C application is writing to a text file and the c# application is reading from that text file.

Question

Over time the file gets to large and I do not want to get in the trouble of truncating it and creating another lock meanwhile it truncates etc... making use of mkfifo seems to be exactly what I want. Since I am relatively new to linux I want to make sure I understand how it works before using it. I know the basics of C (I am no expert) and will like to use a more efficient approach. Do you guys recommend using mkfifo, namedpipes or tcp?

Example 1:

mkfifo works amazing with few lines but when I try to read a lot of lines it fails. Take this example:

mkfifo foo.pipe # create a file of type pipe

On terminal one write to that file

echo "hello world" >> foo.pipe   # writes hello world AND blocks until someone READS from it

On a separate terminal I do:

cat foo.pipe  # it will output hello world. This will block too until someone WRITES to that file

Example 2:

mkfifo foo.pipe # create a file of type pipe. If it exists already do not create again

On terminal 1 read from that file

tail -f foo.pipe # similar to cat foo.pipe but it keeps reading

On Terminal 2 write to that file but a lot of data

echo ~/.bashrc >> foo.pipe  # write the content of file ~/.bashrc to that file

This does not work and only a few lines of that file are displayed on the console. How can I make proper use of mkfifo in order to read all text? Should I use a different approach and use tcp instead?


Solution

  • As @resiliware stated it is probably best to use a unix socket.

    This example shows how to communicate between C and C# using a unix socket:

    Client (written in C running on ubuntu)

    #include<stdio.h>
    #include<string.h>  //strlen
    #include<sys/socket.h>
    #include<unistd.h>
    
    int send_data(void)
    {
        int sock;
        int conn;
    
        struct sockaddr saddr = {AF_UNIX, "/tmp/foo.sock"};
        socklen_t saddrlen = sizeof(struct sockaddr) + 6;
    
        sock = socket(AF_UNIX, SOCK_STREAM, 0);
        conn = connect(sock, &saddr, saddrlen);
    
        char BUFF[1024];
    
        char *message;
        message = "hello world";
        if( send(sock , message , strlen(message) , 0) < 0)
        {
            printf("Send failed \n");
            close(sock);
            return 3;
        }
    
        // I am not sure if I should close both or only the socket. 
        close(conn);
        close(sock);
    
        return 0;
    }
    
    int main(int argc , char *argv[])
    {
        // send 5000 messages
        for(int i=0; i<4000; i++)
        {
            send_data();
            // sleep 1 millisecond
            usleep(1000);
        }
        
        return 0;
    }
    

    Server (written in C# running on same ubuntu machine)

    using System;
    using System.Net.Sockets;
    using System.Threading;
    
    class Program
    {
        // unix Endpoint that we will use
        const string path = "/tmp/foo.sock";
    
        // Thread signal.  
        public static ManualResetEvent _semaphore = new ManualResetEvent(false);
    
        // maximum length of the pending connections queue.
        const int _max_length_pending_connections_queue = 100;
    
        // Counts the number of messages received
        static int _counter = 0;
    
        public static void StartListening()
        {
            if (System.IO.File.Exists(path))
                System.IO.File.Delete(path);
    
            // create unix socket
            var listener = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
    
            try
            {
                // listener.Bind(localEndPoint);
                listener.Bind(new UnixDomainSocketEndPoint(path));
    
                listener.Listen(_max_length_pending_connections_queue);
    
                Console.WriteLine("Waiting for a connection...");
    
                // keep listening for connections
                while (true)
                {
                    // Set the event to nonsignaled state.  
                    _semaphore.Reset();
    
                    // Start an asynchronous socket to listen for connections.  
    
                    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
    
                    // Wait until a connection is made before continuing.  
                    _semaphore.WaitOne();
                }
    
            }
            catch (Exception e)
            {
                Console.WriteLine("Something bad happened:");
                Console.WriteLine(e.ToString());
                Console.WriteLine("\nPress ENTER to continue...");
                Console.Read();
            }
        }
    
        // On new connection
        public static void AcceptCallback(IAsyncResult ar)
        {
            // Signal the main thread to continue.  
            _semaphore.Set();
    
            var cntr = Interlocked.Increment(ref _counter);
    
            // Get the socket that handles the client request.  
            Socket listener = (Socket)ar.AsyncState;
            Socket socket = listener.EndAccept(ar);
    
            var data = new byte[1024];
            var i = socket.Receive(data);
    
            // print message every 100 times
            //if (cntr % 100 == 0)
            Console.WriteLine($"[{cntr}] Received data: {System.Text.Encoding.UTF8.GetString(data, 0, i)}");
    
            // close socket we are only receiving events
            socket.Close();
    
        }
    
        static void Main(string[] args)
        {
            StartListening();        
        }
    }
    

    Client (If you will like the code of the client to be written on C# instead of C)

            using (var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified))
            {
                socket.Connect(new UnixDomainSocketEndPoint(path));
    
                // send hello world
                var dataToSend = System.Text.Encoding.UTF8.GetBytes("Hello-world!");
    
                socket.Send(dataToSend);
            }