Archive

Posts Tagged ‘C’

REMOTE PROCEDURE CALLs: C vs. Erlang (part 1)

December 21, 2009 Leave a comment

In the last weeks I have been geeking a while with Remote Procedure Calls.

RPCs were invented at Sun Microsystems in the 80s, and can be seen as “normal” procedure calls on a distributed system. The concept behind RPCs is the following: you don’t have to declare the function you want to use in your client, you can declare that function on a server and than if you invoke it, a so-called “middleware” is going to forward the request from the client to the server, where the function is executed and the result is returned back to the client.

Since I’m still confident that Erlang is much more better than C for distributed systems I’ll try to code in both languages a server that keeps a counter in it, each time a client calls via RPC a function it will increment that value.

In C I could do it coding three files. Let’s start with the first: a file (counter.x) with the specifications of the service provided by the functions that can be called remotely. The specifications must be converted to stubs and header files for the middleware using the tool rpcgen (we will se it later).

When you write the specifications, you must identify the functions that are going to be called remotely. The functions must specify the return type and arguments. For every function (called RPC), you must assign an ID and must include it in a program that must be uniquely referred to by an ID and a version.

Our counter.x may look like this:

program COUNTERPROG {
  version COUNTERVERS {
    int COUNTER() = 1;
  } = 1;
} = 0x20000001;

where the remote procedure COUNTER has ID 1 and is included in the version 1 of the program COUNTERPROG. The ID of the program is 0x20000001.

After that you must write the procedure code as if you would write it locally, but with slight changes. The procedure must use a conventional name PROGRAM_VERSION_svc (in our case counter_1_svc). The procedure uses pointers to return the result and receive the arguments.
One more argument is required to identify the execution context.

Thus in our case we can create a file named counter_proc.c of the following form:

#include <stdio.h>
#include "counter.h"
int *counter_1_svc(void *msg, struct svc_req *req)
{
  static int result = 0;
  result++;
  return (&result);
}

The header file counter.h is one of the files generated by rpcgen. As you can see we create a STATIC variable result that lives in the space allocated for static variables so that it can survive when the execution of the fuction ends. Whenever the function is called we increment that value and return the address of that static variable.

Now we have to write to client! This client, before performing the remote call to the server, must connect to it. Then, after the call it can disconnect. The connection is done using the function clnt_create(server address, program id, program version, transport protocol). The disconnection is done using the function clnt_destroy().

Let’s create a file called rcounter.c of the following form:

#include <stdio.h>
#include <string.h>
#include "counter.h"
int main(int argc, char **argv)
{
  CLIENT *clnt;
  int *result;
  char *server;
  // try to get the server address from command line
  if (argc != 2) {
    fprintf(stderr, "Usage is the following: %s server\n", argv[0]);
    return -1;
  }
  server = argv[1];
  // create the client
  clnt = clnt_create(server, COUNTERPROG, COUNTERVERS, "udp"); //udp may be set to tcp
  if (clnt == NULL) {
    clnt_pcreateerror(server);
    return -1;
  }
  // call the function
  result = counter_1(NULL, clnt);
  if (result == NULL) {
    clnt_perror(clnt, server);
    return -1;
  }
  // print the value obtained from the call
  printf("The value of the counter is %d\n", *result);
  // destroy the client
  clnt_destroy(clnt);
  return 0;
}

Now let’s compile all the files:

bellerofonte@pegaso:~$ rpcgen counter.x

This generates: counter.h counter_svc.c counter_clnt.c

bellerofonte@pegaso:~$ gcc counter_svc.c counter_proc.c -o counter_svr -lnsl
bellerofonte@pegaso:~$ gcc counter_clnt.c rcounter.c -o counter_clt -lnsl

and execute them:

bellerofonte@pegaso:~$ ./counter_svr &
[1] 4898
bellerofonte@pegaso:~$ ./counter_clt localhost
Counter is 1
bellerofonte@pegaso:~$ ./counter_clt localhost
Counter is 2
bellerofonte@pegaso:~$ kill -9 4898

As you can see at every call the value returned is the previous one plus 1 (starting from zero).

I killed the server in the end because I started it in background mode…if you want you can run the two files in different shells to avoid this.

Ok, this post starts being long…so for the Erlang code I will open a new one 🙂

Categories: C, English, Erlang Tags: , , ,

A client for our echo server in C

December 1, 2009 Leave a comment

Last time I posted something about sockets, ending up with a small server that accepted one client and greeted it.

Anyhow I left two things to do: fist the client was simulated by a telnet connection, and second the server wasn’t devised for a multiclient approach.

For the client side, I can briefly say that all the concepts I have introduced in last post are valid: to create a client we create a socket descriptor, connect to it and then send and receive data over it (we don’t use listen and accept here), then we close the socket.

Thus a valid example is:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

#define BUF_SIZE 	(1000)

int main(int narg, char *varg[])
{
    int socketfd;
    struct sockaddr_in server_addr;
    int port = atoi(varg[2]); //convert a string to an integer
    int recv_len, sent_len;
    char buf[BUF_SIZE];

    printf("Connecting to %s:%d\n", varg[1], port);

    // create the socket if possible
    if ((socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        perror("unable to create socket");
        return -1;
    }

    // configure the server address for the socket
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(varg[1]);

    // connect to the server via the socket created above
    if (connect(socketfd,
                   (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
        perror("unable to connect");
        return -1;
    }

    // receive data from server
    recv_len = recv(socketfd, buf, BUF_SIZE, 0);
    if (recv_len == 0) {
        printf("server disconnected!\n");
        close(socketfd);
        return 0;
    }
    if (recv_len < 0) {
        perror("error receiving data from server");
        return -1;
    }

    printf("received from server %d bytes:%s", recv_len, buf);

    close(socketfd);
    return 0;
}

To execute the following code try to run the run in one shell the code for the server I have posted last time and in another the following:

bellorofonte@pegaso:~$ ./client 127.0.0.1 4000
Connecting to 127.0.0.1:4000
received from server 14 bytes:Hello World!

You may have noticed that in the above case we specified the IP address of the server in the notation xxx.xxx.xxx.xxx, you may want to use the function struct hostent *gethostbyname(const char *name) by including the library <netdb.h>
You can type something like:

struct hostent *host = gethostbyname("localhost");

where hostent is the following structure:

struct hostent {
    char    *h_name;        // official name of host
    char    **h_aliases;    // list of aliases
    int     h_addrtype;     // host address type
    int     h_length;       // lenght of address
    char    **h_addr_list;  // list of addresses
}

For the multiclient server stay tuned!

Categories: C, English Tags: , , ,

Echo server in C

November 30, 2009 Leave a comment

In order to create an echo server we must introduce the concept of socket in C.

Sockets can be seen as an endpoint for cummunication, and in C are created by including in our code the following libraries:

<sys/types.h>
<sys/socket.h>

and by using the function int socket(int domain, int type, int protocol)

Let’s explain briefly the three variables used in the previous function:

int domain represents a communication domain, the protocol family to be used for communication (common values are AF_UNIX for local communication, AF_INET for IPv4 Internet protocols and AF_INET6 for IPv6).

int type specifies the communication semantics (commonly we use SOCK_STREAM that provides sequenced, reliable, two-way, connection-based byte streams but can be used other types as SOCK_RAW which provides raw network protocol access).

int protocol indicates a particular protocol for the socket, normally only a single protocol exists for supporting a particular socket type in a family but in some cases there can be more than one and this variable specifies which one should be used.

Function int socket(…) returns a socket descriptor if valid, otherwise it returns -1 and a errno to handle the error. To send or receive data (e.g to/from another process or machine) the socket must be connected to another socket.

Since we want to create a server first, we must introduce a way to setup a port to listen to. We do this by using the function int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen) (the same libraries as before must have included). This function assigns the address specified by variable addr to the socket assigned to socket descriptor sockfd. addrlen specifies the size in bytes of the address structure pointed to by addr.

The struct sockaddr is structure representing an IP socket address, thus a pain (IP, PORT). This structure may change based on protocol you use, but is something like:

struct sockaddr {
	short sin_family;         // e.g. AF_INET for IPv4
	unsigned short sin_port;  // e.g. htons(4320) see htons()
	struct in_addr sin_addr;  // structure containing the IP address
	char sin_zero[8];
};

where:

in_addr {
	unsigned long s_addr;
};

If successful, bind() returns 0; otherwise it returns -1 and sets errno to indicate the error.
After binding, listening operation can be started as: listen(int sockefd, in queue) where socketfd is our socket descriptor and queue is the maximum number of connections pending. If successful, listen() returns 0; otherwise it returns -1 and sets errno to indicate the error.

To accept a connection we use the function int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) which is used with connection based sockets such as streams. Argument sockfd is a socket that has been created with socket(), bound to a local address with bind(), and is listening for connections after a listen(). The argument addr is a pointer to a sockaddr structure filled in with the address of the peer socket, as known to the communications layer. The addrlen argument is a value-result argument: the caller must ini tialize it to contain the size (in bytes) of the structure pointed to by addr; on return it will contain the actual size of the peer address. If an error occurs, -1 is returned and errno is set to indicate the cause.

For exchanging data we use two functions: ssize_t send(int sockfd, const void *buf, size_t len, int flags) where buf is the message you want to send, flags is usually set to 0 and the function ssize_t recv(int sockfd, void *buf, size_t len, int flags). These functions return -1 in case of an error.

Finally to close a connection we include library and call int close(int sockfd)

Here an example of a server that waits for a connection and greets the user connected. (note that I did not check the return value of the functions):

#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>

#define MAX_CONNECTIONS    (1000)
#define MAX_LINE           (1000)

int main()
{
  int sockfd;
  sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  struct sockaddr_in my_addr;
  my_addr.sin_family = AF_INET;
  my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  my_addr.sin_port = htons(4000);

  bind(sockfd, (struct sockaddr *) &my_addr, sizeof(my_addr));
  listen(sockfd, MAX_CONNECTIONS);

  while(1)
    {
      struct sockaddr_in client_addr;
      int client_len = sizeof(client_addr);

      int client_fd = accept(sockfd, (struct sockaddr *) &client_addr,  &client_len);

      char buf[MAX_LINE];
      strcpy(buf, "Hello user!\n");
      send(client_fd, buf, strlen(buf) + 1, 0);
      close(client_fd);
    }
}

To run the server type in one shell the following:

bellerofonte@pegaso:~$ ./echoserv

and in another:

bellerofonte@pegaso:~$ telnet localhost 4000
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello user!
Connection closed by foreign host.

Note that this server accepts only one connection (I will post new code for a multi-client server soon)!

Categories: C Tags: , ,

Pipes in C

November 12, 2009 Leave a comment

Il mio pc è ancora defunto..quindi anche oggi vi parlerò di C e non di Erlang 😦

Oggi tratterò le PIPES in C. Le pipes sono utilizzate per garantire un canale di comunicazione tra processi diversi e proprio come la traduzione di PIPE dall’inglese suggerisce, possono essere viste come un “tubo” con un foro di ingresso e uno di uscita.

Le PIPEs vengo gestite tramite due descrittori di file: uno viene usato per la scrittura e l’atro per la lettura di dati. Una PIPE viene dichiarata nel seguente modo:

int fd[2];
pipe(fd);

La prima riga introduce un array di 2 elementi che verranno usati come file descriptors, mentre la seconda va a creare una PIPE caratterizzata da tali descriptors.

Vediamo una un codice in cui dopo la creazione della PIPE verrà eseguito un fork: il processo figlio scriverà sulla PIPE, mentre il processo padre leggerà da essa.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
  int fd[2], nbytes;
  pid_t pid;
  char string[] = "Hello world!\n";
  char readbuffer[80];

  pipe(fd);

  switch(pid = fork())
    {
    case -1:
      printf("Error in fork() command\n");
      close(fd[0]);
    case 0:
      close(fd[0]);
      write(fd[1], string, 13);
      _exit(0);
    default:
      close(fd[1]);
      nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
      printf("Received string: %s", readbuffer);
    }
  return(0);
}

Il codice come potete vedere inizia con l’inclusione di alcune librerie che ci saranno utili, in seguito vengono dichiarate le variabili, alcune allo scopo di utilizzare la PIPE, altre per la lettura e la scrittura di stringhe sulla PIPE stessa.

Dopo aver creato la PIPE eseguiamo il fork e ne switchiamo il risultato: in caso di processo padre andiamo a leggere il valore presente in fd[0] (SEMPRE E SOLO UTILIZZATO PER LA LETTURA), assegniamo tale valore alla variabile readbuffer e ne stampiamo il valore a video.

In caso di processo figlio invece scriviamo il valore della variabile string in fd[1] che viene SEMPRE USATO PER LA SCRITTURA e poi terminiamo il processo.

Categories: C Tags: , ,