Question

I am really interested in event-driven programming in C especially with sockets so I am going to dedicate some time doing my researches.

Let's assume that I want to build a program with much File and Network I/O like a client/server app, basically, the first question is what is the philosophy behind this model. While in normal programming I would spawn new processes, how come a single process can actually serve many other requests. For example, there are some web-servers which can handle connections without creating threads or other processes, just one main process.

I know this is complicated but it's always nice to know how different solutions work.

Was it helpful?

Solution

You definitely must read the following: http://www.kegel.com/c10k.html. That page is the perfect overview of event-driven and asynchronous techniques.

However, a quick & dirty answer: event-driven is neither non-blocking, nor asynchronous.

Event-driven means, that the process will monitor its file descriptors (and sockets), and act only when some event occurs on some descriptor (events are: data received, error, became writeable, ...).

BSD sockets have the "select()" function. When called, the OS will monitor the descriptors, and return to the process as soon as some event on one of the descriptors occurs.

However, the website above has much better descriptions (and details about the different APIs).

OTHER TIPS

"what is the philosophy behind this model"

Event driven means there is no "monitoring", but that the event itself initiates the action.

Usually this is initiated by an interrupt, which is a signal to the system from an external device, or (in the case of a software interrupt) an asynchronous process.

https://en.wikipedia.org/wiki/Interrupt

Further reading seems to be here:

https://docs.oracle.com/cd/E19455-01/806-1017/6jab5di2m/index.html#sockets-40 - "Interrupt-Driven Socket I/O"

Also http://cs.baylor.edu/~donahoo/practical/CSockets/textcode.html has some examples of Interrupt-Driven Sockets, as well as other socket programming examples.

Event driven programming is based on an event loop. The loop simply waits for a new event, dispatches code to handle the event, then loops back to wait for the next event. In the case of sockets, you're talking about "asynchronous network programming". This involves select() or some other option like Kqueue() to wait for the events in the event loop. Sockets would need to be set to non blocking, so that when you read() or write() your code won't wait for the I/O to complete.

Asynchronous network programming can be very complex, and tricky to get right. Check out a couple of introductions here and here. I strongly suggest using a library such as libevent or liboop to get this right.

That kind of TCP servers/clients can be implemented by using select(2) call and non-blocking sockets.

It is more tricky to use non-blocking sockets than blocking sockets.

Example:

connect call usually return -1 immediately and set errno EINPROGRESS when non-blocking socket are used. In this case you should use select to wait when connection is opened or failed. connect may also return 0. This can happen if you create connection to the local host. This way you can serve other sockets, while one socket is opening a TCP connection.

It's actually very platform specific as to how that works.

If your running on a linux system it's really not to difficult though, you simply need to spawn a copy of your process using 'fork' something like the following would do the trick:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet.h>
#include <signal.h>
#include <unistd.h>

int main()
{
  int server_sockfd, client_sockfd;
  int server_len, client_len;
  struct sockaddr_in server_address;
  struct sockaddr_in client_address;

  server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

  server_address.sin_family = AF_INET;
  server_address.sin_addr.s_addr = htonl(INADDR_ANY);
  server_Address.sin_port = htons(1234);
  server_len = sizeof(server_address);
  bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

  listen(server_sockfd, 5);

  signal(SIGCHLD, SIG_IGN);

  while(1)
  {
    char ch;
    printf("Server Waiting\n");
    client_len = sizeof(client_address);
    client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len)

    // Here's where we do the forking, if you've forked already then this will be the child running, if not then your still the parent task.

    if(fork() == 0)
    {
      // Do what ever the child needs to do with the connected client
      read(client_sockfd, &ch, 1);
      sleep(5); // just for show :-)
      ch++;
      write(client_sockfd, &ch, 1);
      close(client_sockfd);
      exit(0);
    }
    else
    {
      // Parent code here, close and loop for next connection
      close(client_sockfd);
    }
  }
}

You may have to fiddle with that code a little I'm not near a Linux box at the moment to do a test compile, and I've pretty much typed it from memory.

Using fork however is the standard way to do this in C under a Linux / Unix based system.

Under windows it's a very different story, and one which I can't quite remember all the code needed (I'm way to used to coding in C# these days) but setting up the socket is pretty much the same except you need to use the 'Winsock' API for better compatibility.

You can (I believe anyway) still use standard berkley sockets under windows but it's full of pitfalls and holes, for windows winsock this is a good place to start:

http://tangentsoft.net/wskfaq/

As far as I'm aware too, if your using Winsock it has stuff in to help with the spawning and multi client, myself personally however, I usually just spin off a separate thread and copy the socket connection to that, then go back into the loop listening to my server.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top