Showing posts with label pthreaded. Show all posts
Showing posts with label pthreaded. Show all posts

Wednesday, July 3, 2013

Simple Chat Program in C (Part 3)


Read Part 2 : The Client

Sample Execution:

 $ gcc server.c -o server -lpthread $ gcc client.c -o client -lpthread 

Now we run the server program and several client programs

Each client should first connect to the server by typing in:

login [alias]
Here [alias] is optional, if it is not mentioned, the name will be set as Anonymous.

The name can be changed by the command:

alias [new_name]
After this, you alias will be changed to the new name you have given.

To send a message to everyone connected at the moment:

send [message]

To send a message to a specific alias:

whisp [user_alias] [message]
If the target alias is not active at that moment, message will not be sent anywhere

You can disconnect from the server by typing:

logout
You can again reconnect by using the login command

You can terminate the client by typing:

exit

This is pretty much of it. Basically this is good for your C lab assignments regarding TCP / socket based chat clients. Here is a screen shot from my computer:

Good luck!


Simple Chat Program in C (Part 2)


Read Part 1 : The server

The client program

The client program basically communicates with other client program, to run multiple clients, just open a terminal for each client. The client is command driven in this code, that is you have to enter command strings. Just read the code and you will see what it is expecting from you. The code is pretty simple.

 /* ** Author: Zobayer Hasan ** Date: 26th February, 2010 ** Copyright: NO COPYRIGHT ISSUES. However, do not copy it. */  #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h>  #include <netdb.h> #include <unistd.h> #include <pthread.h>  #include <arpa/inet.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h>  #define SERVERIP "127.0.0.1" #define SERVERPORT 8080  #define BUFFSIZE 1024 #define ALIASLEN 32 #define OPTLEN 16 #define LINEBUFF 2048  struct PACKET {     char option[OPTLEN]; // instruction     char alias[ALIASLEN]; // client's alias     char buff[BUFFSIZE]; // payload };  struct USER {         int sockfd; // user's socket descriptor         char alias[ALIASLEN]; // user's name };  struct THREADINFO {     pthread_t thread_ID; // thread's pointer     int sockfd; // socket file descriptor };  int isconnected, sockfd; char option[LINEBUFF]; struct USER me;  int connect_with_server(); void setalias(struct USER *me); void logout(struct USER *me); void login(struct USER *me); void *receiver(void *param); void sendtoall(struct USER *me, char *msg); void sendtoalias(struct USER *me, char * target, char *msg);  int main(int argc, char **argv) {     int sockfd, aliaslen;          memset(&me, 0, sizeof(struct USER));          while(gets(option)) {         if(!strncmp(option, "exit", 4)) {             logout(&me);             break;         }         if(!strncmp(option, "help", 4)) {             FILE *fin = fopen("help.txt", "r");             if(fin != NULL) {                 while(fgets(option, LINEBUFF-1, fin)) puts(option);                 fclose(fin);             }             else {                 fprintf(stderr, "Help file not found...\n");             }         }         else if(!strncmp(option, "login", 5)) {             char *ptr = strtok(option, " ");             ptr = strtok(0, " ");             memset(me.alias, 0, sizeof(char) * ALIASLEN);             if(ptr != NULL) {                 aliaslen =  strlen(ptr);                 if(aliaslen > ALIASLEN) ptr[ALIASLEN] = 0;                 strcpy(me.alias, ptr);             }             else {                 strcpy(me.alias, "Anonymous");             }             login(&me);         }         else if(!strncmp(option, "alias", 5)) {             char *ptr = strtok(option, " ");             ptr = strtok(0, " ");             memset(me.alias, 0, sizeof(char) * ALIASLEN);             if(ptr != NULL) {                 aliaslen =  strlen(ptr);                 if(aliaslen > ALIASLEN) ptr[ALIASLEN] = 0;                 strcpy(me.alias, ptr);                 setalias(&me);             }         }         else if(!strncmp(option, "whisp", 5)) {             char *ptr = strtok(option, " ");             char temp[ALIASLEN];             ptr = strtok(0, " ");             memset(temp, 0, sizeof(char) * ALIASLEN);             if(ptr != NULL) {                 aliaslen =  strlen(ptr);                 if(aliaslen > ALIASLEN) ptr[ALIASLEN] = 0;                 strcpy(temp, ptr);                 while(*ptr) ptr++; ptr++;                 while(*ptr <= ' ') ptr++;                 sendtoalias(&me, temp, ptr);             }         }         else if(!strncmp(option, "send", 4)) {             sendtoall(&me, &option[5]);         }         else if(!strncmp(option, "logout", 6)) {             logout(&me);         }         else fprintf(stderr, "Unknown option...\n");     }     return 0; }  void login(struct USER *me) {     int recvd;     if(isconnected) {         fprintf(stderr, "You are already connected to server at %s:%d\n", SERVERIP, SERVERPORT);         return;     }     sockfd = connect_with_server();     if(sockfd >= 0) {         isconnected = 1;         me->sockfd = sockfd;         if(strcmp(me->alias, "Anonymous")) setalias(me);         printf("Logged in as %s\n", me->alias);         printf("Receiver started [%d]...\n", sockfd);         struct THREADINFO threadinfo;         pthread_create(&threadinfo.thread_ID, NULL, receiver, (void *)&threadinfo);      }     else {         fprintf(stderr, "Connection rejected...\n");     } }  int connect_with_server() {     int newfd, err_ret;     struct sockaddr_in serv_addr;     struct hostent *to;      /* generate address */     if((to = gethostbyname(SERVERIP))==NULL) {         err_ret = errno;         fprintf(stderr, "gethostbyname() error...\n");         return err_ret;     }      /* open a socket */     if((newfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {         err_ret = errno;         fprintf(stderr, "socket() error...\n");         return err_ret;     }      /* set initial values */     serv_addr.sin_family = AF_INET;     serv_addr.sin_port = htons(SERVERPORT);     serv_addr.sin_addr = *((struct in_addr *)to->h_addr);     memset(&(serv_addr.sin_zero), 0, 8);      /* try to connect with server */     if(connect(newfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1) {         err_ret = errno;         fprintf(stderr, "connect() error...\n");         return err_ret;     }     else {         printf("Connected to server at %s:%d\n", SERVERIP, SERVERPORT);         return newfd;     } }  void logout(struct USER *me) {     int sent;     struct PACKET packet;          if(!isconnected) {         fprintf(stderr, "You are not connected...\n");         return;     }          memset(&packet, 0, sizeof(struct PACKET));     strcpy(packet.option, "exit");     strcpy(packet.alias, me->alias);          /* send request to close this connetion */     sent = send(sockfd, (void *)&packet, sizeof(struct PACKET), 0);     isconnected = 0; }  void setalias(struct USER *me) {     int sent;     struct PACKET packet;          if(!isconnected) {         fprintf(stderr, "You are not connected...\n");         return;     }          memset(&packet, 0, sizeof(struct PACKET));     strcpy(packet.option, "alias");     strcpy(packet.alias, me->alias);          /* send request to close this connetion */     sent = send(sockfd, (void *)&packet, sizeof(struct PACKET), 0); }  void *receiver(void *param) {     int recvd;     struct PACKET packet;          printf("Waiting here [%d]...\n", sockfd);     while(isconnected) {                  recvd = recv(sockfd, (void *)&packet, sizeof(struct PACKET), 0);         if(!recvd) {             fprintf(stderr, "Connection lost from server...\n");             isconnected = 0;             close(sockfd);             break;         }         if(recvd > 0) {             printf("[%s]: %s\n", packet.alias, packet.buff);         }         memset(&packet, 0, sizeof(struct PACKET));     }     return NULL; }  void sendtoall(struct USER *me, char *msg) {     int sent;     struct PACKET packet;          if(!isconnected) {         fprintf(stderr, "You are not connected...\n");         return;     }          msg[BUFFSIZE] = 0;          memset(&packet, 0, sizeof(struct PACKET));     strcpy(packet.option, "send");     strcpy(packet.alias, me->alias);     strcpy(packet.buff, msg);          /* send request to close this connetion */     sent = send(sockfd, (void *)&packet, sizeof(struct PACKET), 0); }  void sendtoalias(struct USER *me, char *target, char *msg) {     int sent, targetlen;     struct PACKET packet;          if(target == NULL) {         return;     }          if(msg == NULL) {         return;     }          if(!isconnected) {         fprintf(stderr, "You are not connected...\n");         return;     }     msg[BUFFSIZE] = 0;     targetlen = strlen(target);          memset(&packet, 0, sizeof(struct PACKET));     strcpy(packet.option, "whisp");     strcpy(packet.alias, me->alias);     strcpy(packet.buff, target);     strcpy(&packet.buff[targetlen], " ");     strcpy(&packet.buff[targetlen+1], msg);          /* send request to close this connetion */     sent = send(sockfd, (void *)&packet, sizeof(struct PACKET), 0); } 
Compile it like this:
 gcc client.c -o client -lpthread 

So basically this is it. It's just a program to show and example of pthread and socket together, it will be pointless to find how poorly written this program is compared to real life chat clients, those are off the limits of this post. Also, read the main functions of the client first to understand what you need to give it as input to make it work.

The next part of the post describes the commands which are usable from the client program.

Continue to Part 3 : Sample Execution


Friday, June 21, 2013

Simple Chat Program in C (Part 1)


A multi-threaded chat client in C using socket and pthread library

I wrote this code years ago, putting it here because some people may want to get some help in writing server client programs in C using socket programming.

The server and client programs have a few features that you might want to take a look at. I am not going to explain the functionalities here, you can get that fairly easily by just going through the codes, they are bit long, but quite self explanatory. Ask in the comment area if you have any confusion.

The server program

The server basically waits for clients to connect and start passing message among them. Whenever a client connects to this server, it creates a thread to handle the new client, this way, it does not block other clients from connecting. Also the server can take a few commands such as whether you want to exit or not. Here is the source code of the server:

 /* ** Author: Zobayer Hasan ** Date: 26th February, 2010 ** Copyright: NO COPYRIGHT ISSUES. However, do not copy it. */  #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h>  #include <netdb.h> #include <unistd.h> #include <pthread.h>  #include <arpa/inet.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h>  #define IP "127.0.0.1" #define PORT 8080 #define BACKLOG 10 #define CLIENTS 10  #define BUFFSIZE 1024 #define ALIASLEN 32 #define OPTLEN 16  struct PACKET {     char option[OPTLEN]; // instruction     char alias[ALIASLEN]; // client's alias     char buff[BUFFSIZE]; // payload };  struct THREADINFO {     pthread_t thread_ID; // thread's pointer     int sockfd; // socket file descriptor     char alias[ALIASLEN]; // client's alias };  struct LLNODE {     struct THREADINFO threadinfo;     struct LLNODE *next; };  struct LLIST {     struct LLNODE *head, *tail;     int size; };  int compare(struct THREADINFO *a, struct THREADINFO *b) {     return a->sockfd - b->sockfd; }  void list_init(struct LLIST *ll) {     ll->head = ll->tail = NULL;     ll->size = 0; }  int list_insert(struct LLIST *ll, struct THREADINFO *thr_info) {     if(ll->size == CLIENTS) return -1;     if(ll->head == NULL) {         ll->head = (struct LLNODE *)malloc(sizeof(struct LLNODE));         ll->head->threadinfo = *thr_info;         ll->head->next = NULL;         ll->tail = ll->head;     }     else {         ll->tail->next = (struct LLNODE *)malloc(sizeof(struct LLNODE));         ll->tail->next->threadinfo = *thr_info;         ll->tail->next->next = NULL;         ll->tail = ll->tail->next;     }     ll->size++;     return 0; }  int list_delete(struct LLIST *ll, struct THREADINFO *thr_info) {     struct LLNODE *curr, *temp;     if(ll->head == NULL) return -1;     if(compare(thr_info, &ll->head->threadinfo) == 0) {         temp = ll->head;         ll->head = ll->head->next;         if(ll->head == NULL) ll->tail = ll->head;         free(temp);         ll->size--;         return 0;     }     for(curr = ll->head; curr->next != NULL; curr = curr->next) {         if(compare(thr_info, &curr->next->threadinfo) == 0) {             temp = curr->next;             if(temp == ll->tail) ll->tail = curr;             curr->next = curr->next->next;             free(temp);             ll->size--;             return 0;         }     }     return -1; }  void list_dump(struct LLIST *ll) {     struct LLNODE *curr;     struct THREADINFO *thr_info;     printf("Connection count: %d\n", ll->size);     for(curr = ll->head; curr != NULL; curr = curr->next) {         thr_info = &curr->threadinfo;         printf("[%d] %s\n", thr_info->sockfd, thr_info->alias);     } }  int sockfd, newfd; struct THREADINFO thread_info[CLIENTS]; struct LLIST client_list; pthread_mutex_t clientlist_mutex;  void *io_handler(void *param); void *client_handler(void *fd);  int main(int argc, char **argv) {     int err_ret, sin_size;     struct sockaddr_in serv_addr, client_addr;     pthread_t interrupt;      /* initialize linked list */     list_init(&client_list);      /* initiate mutex */     pthread_mutex_init(&clientlist_mutex, NULL);      /* open a socket */     if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {         err_ret = errno;         fprintf(stderr, "socket() failed...\n");         return err_ret;     }      /* set initial values */     serv_addr.sin_family = AF_INET;     serv_addr.sin_port = htons(PORT);     serv_addr.sin_addr.s_addr = inet_addr(IP);     memset(&(serv_addr.sin_zero), 0, 8);      /* bind address with socket */     if(bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1) {         err_ret = errno;         fprintf(stderr, "bind() failed...\n");         return err_ret;     }      /* start listening for connection */     if(listen(sockfd, BACKLOG) == -1) {         err_ret = errno;         fprintf(stderr, "listen() failed...\n");         return err_ret;     }      /* initiate interrupt handler for IO controlling */     printf("Starting admin interface...\n");     if(pthread_create(&interrupt, NULL, io_handler, NULL) != 0) {         err_ret = errno;         fprintf(stderr, "pthread_create() failed...\n");         return err_ret;     }      /* keep accepting connections */     printf("Starting socket listener...\n");     while(1) {         sin_size = sizeof(struct sockaddr_in);         if((newfd = accept(sockfd, (struct sockaddr *)&client_addr, (socklen_t*)&sin_size)) == -1) {             err_ret = errno;             fprintf(stderr, "accept() failed...\n");             return err_ret;         }         else {             if(client_list.size == CLIENTS) {                 fprintf(stderr, "Connection full, request rejected...\n");                 continue;             }             printf("Connection requested received...\n");             struct THREADINFO threadinfo;             threadinfo.sockfd = newfd;             strcpy(threadinfo.alias, "Anonymous");             pthread_mutex_lock(&clientlist_mutex);             list_insert(&client_list, &threadinfo);             pthread_mutex_unlock(&clientlist_mutex);             pthread_create(&threadinfo.thread_ID, NULL, client_handler, (void *)&threadinfo);         }     }      return 0; }  void *io_handler(void *param) {     char option[OPTLEN];     while(scanf("%s", option)==1) {         if(!strcmp(option, "exit")) {             /* clean up */             printf("Terminating server...\n");             pthread_mutex_destroy(&clientlist_mutex);             close(sockfd);             exit(0);         }         else if(!strcmp(option, "list")) {             pthread_mutex_lock(&clientlist_mutex);             list_dump(&client_list);             pthread_mutex_unlock(&clientlist_mutex);         }         else {             fprintf(stderr, "Unknown command: %s...\n", option);         }     }     return NULL; }  void *client_handler(void *fd) {     struct THREADINFO threadinfo = *(struct THREADINFO *)fd;     struct PACKET packet;     struct LLNODE *curr;     int bytes, sent;     while(1) {         bytes = recv(threadinfo.sockfd, (void *)&packet, sizeof(struct PACKET), 0);         if(!bytes) {             fprintf(stderr, "Connection lost from [%d] %s...\n", threadinfo.sockfd, threadinfo.alias);             pthread_mutex_lock(&clientlist_mutex);             list_delete(&client_list, &threadinfo);             pthread_mutex_unlock(&clientlist_mutex);             break;         }         printf("[%d] %s %s %s\n", threadinfo.sockfd, packet.option, packet.alias, packet.buff);         if(!strcmp(packet.option, "alias")) {             printf("Set alias to %s\n", packet.alias);             pthread_mutex_lock(&clientlist_mutex);             for(curr = client_list.head; curr != NULL; curr = curr->next) {                 if(compare(&curr->threadinfo, &threadinfo) == 0) {                     strcpy(curr->threadinfo.alias, packet.alias);                     strcpy(threadinfo.alias, packet.alias);                     break;                 }             }             pthread_mutex_unlock(&clientlist_mutex);         }         else if(!strcmp(packet.option, "whisp")) {             int i;             char target[ALIASLEN];             for(i = 0; packet.buff[i] != ' '; i++); packet.buff[i++] = 0;             strcpy(target, packet.buff);             pthread_mutex_lock(&clientlist_mutex);             for(curr = client_list.head; curr != NULL; curr = curr->next) {                 if(strcmp(target, curr->threadinfo.alias) == 0) {                     struct PACKET spacket;                     memset(&spacket, 0, sizeof(struct PACKET));                     if(!compare(&curr->threadinfo, &threadinfo)) continue;                     strcpy(spacket.option, "msg");                     strcpy(spacket.alias, packet.alias);                     strcpy(spacket.buff, &packet.buff[i]);                     sent = send(curr->threadinfo.sockfd, (void *)&spacket, sizeof(struct PACKET), 0);                 }             }             pthread_mutex_unlock(&clientlist_mutex);         }         else if(!strcmp(packet.option, "send")) {             pthread_mutex_lock(&clientlist_mutex);             for(curr = client_list.head; curr != NULL; curr = curr->next) {                 struct PACKET spacket;                 memset(&spacket, 0, sizeof(struct PACKET));                 if(!compare(&curr->threadinfo, &threadinfo)) continue;                 strcpy(spacket.option, "msg");                 strcpy(spacket.alias, packet.alias);                 strcpy(spacket.buff, packet.buff);                 sent = send(curr->threadinfo.sockfd, (void *)&spacket, sizeof(struct PACKET), 0);             }             pthread_mutex_unlock(&clientlist_mutex);         }         else if(!strcmp(packet.option, "exit")) {             printf("[%d] %s has disconnected...\n", threadinfo.sockfd, threadinfo.alias);             pthread_mutex_lock(&clientlist_mutex);             list_delete(&client_list, &threadinfo);             pthread_mutex_unlock(&clientlist_mutex);             break;         }         else {             fprintf(stderr, "Garbage data from [%d] %s...\n", threadinfo.sockfd, threadinfo.alias);         }     }      /* clean up */     close(threadinfo.sockfd);      return NULL; } 
Compile it like this:
 gcc server.c -o server -lpthread 

Continue to Part 2 : The Client


Wednesday, September 29, 2010

Threaded Merge Sort



What?


In my previous post about Merge Sort, the algorithm for merge sort was discussed. Although the pictures shows something which appears to be apparently parallel, but actually they are not. In fact, as the procedure introduced there was recursive, it works in a post-order fashion, meaning, first it solves the left half, then the right half, and then it works with the current range. The following picture shows the actual work-flow of the standard recursive merge sort algorithm:


The red lines and numbers shows the actual call sequence of the algorithm merge sort, for reference, the basic algorithm, is shown here again:

MERGE-SORT(A, p, r):
if p < r
then q := (r + p) / 2
MERGE-SORT(A, p, q)
MERGE-SORT(A, q+1, r)
MERGE(A, p, q, r)

So, from the above example, we can see that no two calls are concurrent, i.e. parallel. Each call is one of the three calls made in MERGE-SORT(A, p, r) procedure stated above, also shown below (according to the picture, p=0, r=6):

Step-00: MERGE-SORT(A, 0, 6)
Step-01: MERGE-SORT(A, 0, 3)
Step-02: MERGE-SORT(A, 0, 1)
Step-03: MERGE-SORT(A, 0, 0)
Step-04: MERGE-SORT(A, 1, 1)
Step-05: MERGE(A, 0, 0, 1)
Step-06: MERGE-SORT(A, 2, 3)
Step-07: MERGE-SORT(A, 2, 2)
Step-08: MERGE-SORT(A, 3, 3)
Step-09: MERGE(A, 2, 2, 3)
Step-10: MERGE(A, 0, 1, 3)
Step-11: MERGE-SORT(A, 4, 6)
Step-12: MERGE-SORT(A, 4, 5)
Step-13: MERGE-SORT(A, 4, 4)
Step-14: MERGE-SORT(A, 5, 5)
Step-15: MERGE(A, 4, 4, 5)
Step-16: MERGE-SORT(A, 6, 6)
Step-17: MERGE(A, 4, 5, 6)
Step-18: MERGE(A, 0, 3, 6)

Also note that, there are no overlapping calls at a same level of the tree, which is going to be a key factor.

How threading helps:


After a few observations, we can easily find out some interesting properties about merge sort algorithm. If we closely look at the merge sort tree structure, it becomes very clear that, int the recursive call tree, parent nodes depend on children, but siblings are independent. This property helps us to deduce a threaded version of merge sort algorithm where, we can really solve the left and right sub-portion of a range in a parallel fashion by making the calls threaded.

As the algorithm here is recursive, it might look confusing at the first glance, but it is not. Because, nodes share data only with their ancestors and in a call, we can wait until the threads finish their works. So, no concurrency problem appears here. Also, we don't need to worry about mutual exclusion of the shared data, because, there is no such situation in this algorithm, already stated above.

So, the threaded version of the algorithm will look like this:

THREADED-MERGE-SORT(A, p, r):
if p < r
then q := (r + p) / 2
start_thread(left_thread, THREADED-MERGE-SORT(A, p, q))
start_thread(right_thread, THREADED-MERGE-SORT(A, q+1, r))
wait_for(left_thread)
wait_for(right_thread)
MERGE(A, p, q, r)

According to the above algorithm, the flow of program changes which is shown in the picture below:


Now because of being able to run multiple THREADED-MERGE-SORT() at a time, and the job scheduling features of modern computers, the effective number of iteration is dramatically reduced, resulting a much much elegant solution for the heavy duty areas like manipulating large data on drives.

A simple implementation:


Here a simple implementation is presented using pthread library in C++:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

typedef struct { int i, j; } pair;
const int MAX = 1024;
int a[MAX];
pthread_attr_t attr;

void *threaded_merge_sort(void *param) {
pair range = *(pair *)param, lrange, rrange;
pthread_t lside, rside;
int p = range.i, q, r = range.j, n1, n2, n = r-p+1, i, j, k;
int *aleft, *aright;
if(p < r) {
q = (p + r) >> 1;
lrange.i = p, lrange.j = q, rrange.i = q + 1, rrange.j = r;
pthread_create(&lside, &attr, threaded_merge_sort, (void *)&lrange);
pthread_create(&rside, &attr, threaded_merge_sort, (void *)&rrange);
pthread_join(lside, NULL);
pthread_join(rside, NULL);
n1 = q - p + 1, n2 = r - q;
aleft = (int *)malloc(sizeof(int) * n1);
aright = (int *)malloc(sizeof(int) * n2);
for(i = 0; i < n1; i++) aleft[i] = a[p+i];
for(i = 0; i < n2; i++) aright[i] = a[q+1+i];
for(k = i = j = 0; k < n; k++) {
if(i >= n1 || (j < n2 && aleft[i] > aright[j])) a[k+p] = aright[j++];
else a[k+p] = aleft[i++];
}
free(aleft);
free(aright);
}
return NULL;
}

int main() {
int n, i;
pthread_t sorter;
pair range;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
while(scanf("%d", &n)==1 && n) {
for(i = 0; i < n; i++) scanf("%d", &a[i]);
range.i = 0, range.j = n-1;
pthread_create(&sorter, &attr, threaded_merge_sort, (void *)&range);
pthread_join(sorter, NULL);
for(i = 0; i < n; i++) printf("%d%c", a[i], (i==n-1? '\n' : ' '));
}
pthread_attr_destroy(&attr);
return 0;
}

This may look a bit messy, but, actually, you may find a little difference with the original one, we just created threads instead of plain recursive calls, that's it.
To learn more about pthread, check these out:

Give it a try! :)