5
\$\begingroup\$

The idea of my program is to use pthreads, semaphores, posix shared memory, and sockets to create an environment where N processes (that we will refer to as the children) can connect to a single process (that we will refer to as the parent) in order to send it data.

Requirements

Common Code

The parent and child processes are only allowed, and must have, 2 globally defined (#define) variables. It is these values that provide for a common means for these processes to establish a connection. These values will define the main semaphore and the shared memory segment.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <stdbool.h>
#include <sys/ipc.h> 
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/sem.h>
#include <fcntl.h>

#define SEM_KEY     ftok("commoncode.c", 'a')
#define SHARED_MEM  "/vector_table"

struct v_table
{
    pid_t PID;
    int Sem;
    int Port;
};

Parent Code

#include "commoncode.c"

#define MAX_LENGTH  512
#define MAX_CHILD   5

pthread_barrier_t   barrier;
struct v_table Vector[MAX_CHILD];

char *ok = "<ok>\n";
key_t key;
static int semcnt = 0;

union semun {
    int val;               /* used for SETVAL only */
};

void *client_serv(void * argum) {
    int msg_len, client_socket = *(int *)argum, semid0, semid1;
    char text[20];
    union semun arg;

    while (1) {
        semid0 = semget(key + semcnt, 0, IPC_CREAT | 0666);
        if ((volatile int)semctl(semid0, 0, GETVAL, 0) != 1) {
            arg.val = 1;
            semctl(semid0, 0, SETVAL, arg);
            msg_len = recv(client_socket, text, 20, 0);
            printf("%s\n", text);
            if(msg_len <= 0) {
                printf("Incorrect name from client");
                return (void *)1;
            }
            semcnt++;
            if (semcnt == MAX_CHILD)
                semcnt = 0;
            semid1 = semget(key + semcnt, 0, IPC_CREAT | 0666);
            arg.val = 0;
            semctl(semid1, 0, SETVAL, arg);
            if (!strcmp(text, "terminate"))
                break;
            memset(text, 0, 20);
            if (send(client_socket, ok, strlen(ok), 0) != strlen(ok)) {
                printf("Error : send() sent a different number of bytes\n");
                return (void *)1;
            }
        } 
    }

    pthread_barrier_wait (&barrier);
    if (send(client_socket, ok, strlen(ok), 0) != strlen(ok)) {
        printf("Error : send() sent a different number of bytes\n");
        return (void *)1;
    }

    return (void *)0;
}

int main(int argc, char *argv[])
{
    int i, fd;
    int servSock, clntSock[MAX_CHILD], i1=1;
    struct sockaddr_in echoServAddr;
    pthread_t threads[MAX_CHILD];
    uint8_t * mapped_ptr;
    union semun arg;

    for (i = 0; i < MAX_CHILD; i++) {
        Vector[i].PID  = 0;
        Vector[i].Sem  = 0;
        Vector[i].Port = i + 49153;
    }

    fd = shm_open(SHARED_MEM, O_CREAT | O_RDWR | O_APPEND, S_IRUSR | S_IWUSR);
    if(fd == -1) {
        perror("shm_open");
        shm_unlink (SHARED_MEM);
        return 1;
    }
    ftruncate(fd, MAX_CHILD * sizeof(struct v_table));
    mapped_ptr = mmap(NULL, MAX_CHILD * sizeof(struct v_table), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(mapped_ptr < 0) {
        perror("mmap");
        shm_unlink (SHARED_MEM);
        return 1;
    }

    key = SEM_KEY;

    for (i = 0; i < MAX_CHILD; i++) {
        Vector[i].Sem = semget(key + i, 1, IPC_CREAT | 0666);
        arg.val = 0;
        semctl(Vector[i].Sem, 0, SETVAL, arg);
    }

    memcpy(mapped_ptr, Vector, MAX_CHILD * sizeof(struct v_table));
    msync(mapped_ptr, MAX_CHILD * sizeof(struct v_table), MS_SYNC);

    printf("Vector table (from Parent)\n");
    printf("-----------------------------\n");
    printf("PID  Semaphore      Port\n");
    for (i = 0; i < MAX_CHILD; i++)
        printf("%04d %06d       %05d\n", (int)(Vector[i].PID), Vector[i].Sem, Vector[i].Port);
    if ((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        printf("Error : Socket failed\n");
        return 1;
    }
    setsockopt(servSock, SOL_SOCKET, SO_REUSEADDR, &i1, sizeof(i1));
    memset(&echoServAddr, 0, sizeof(echoServAddr));
    echoServAddr.sin_family = AF_INET;
    echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    echoServAddr.sin_port = htons(Vector[0].Port);
    if (bind(servSock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) < 0) {
        printf("bind() failed\n");
        return 1;
    }

    /* Mark the socket so it will listen for incoming connections */
    if (listen(servSock, MAX_CHILD) < 0) {
        printf("Listen() failed\n");
        return 1;
    }

    for (i = 0; i < MAX_CHILD; i++) {
        if ((clntSock[i]=accept(servSock,(struct sockaddr*)NULL,(socklen_t *)NULL)) < 0) {
            printf("accept() failed\n");
            return 1;
        }
    }

    pthread_barrier_init (&barrier, NULL, MAX_CHILD);
    for (i = 0; i < MAX_CHILD; i++) {
        pthread_create(&threads[i], NULL, client_serv,(void *)(&clntSock[i]));
    }

    while (i)
        pthread_join(threads[--i], NULL);

    for (i = 0; i < MAX_CHILD; i++) {
        close(clntSock[i]);
        semctl(Vector[i].Sem, 0, IPC_RMID);
    }
    getchar();
    shm_unlink (SHARED_MEM);
    return 0;
}

Child Code

#include "commoncode.c"

#define MAX_LENGTH 512
#define MAX_CHILD   5

struct v_table Vector[MAX_CHILD];

uint8_t * mapped_ptr;
char *ok = "<ok>\n";

int main(int argc, char *argv[])
{
    int server_socket, echoServPort, i, n, attmpt = 0, fd;
    char *ipaddrstr, *text[3];
    struct sockaddr_in echoServAddr;

    if (1 == argc) {
        printf("Error - You must include a data size: ./child N.\n");
        return 1;
    } else if (2 != argc) {
        printf("Error : Too many arguments\n");
        return 1;
    }
    for(i=0 ; i<3 ; i++) {
        text[i] = malloc(MAX_LENGTH);
                memset(text[i], 0, MAX_LENGTH);
    }

    fd = shm_open(SHARED_MEM, O_RDWR | O_APPEND, S_IRUSR | S_IWUSR);
    if(fd == -1) {
        perror("shm_open");
        shm_unlink (SHARED_MEM);
        return 1;
    }
    ftruncate(fd, MAX_CHILD * sizeof(struct v_table));
    mapped_ptr = mmap(NULL, MAX_CHILD * sizeof(struct v_table), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(mapped_ptr < 0) {
        perror("mmap");
        shm_unlink (SHARED_MEM);
    }

    memcpy((void *)Vector, (void *)mapped_ptr, MAX_CHILD * sizeof(struct v_table));
    ipaddrstr = malloc(16);
    memcpy(ipaddrstr, "127.0.0.1", strlen("127.0.0.1"));

    if ((server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        printf("socket() failed\n");
        return 1;
    }
    echoServAddr.sin_family = AF_INET;  /* Internet address family */
    echoServAddr.sin_addr.s_addr = inet_addr(ipaddrstr); /* Server IP address */

    for (i = 0; i < MAX_CHILD; i++) {
        if (Vector[i].PID == 0) {
            Vector[i].PID = getpid();
            break;
        }
        attmpt++;
    }
    printf("Vector table (from %04d)\n", getpid());
    printf("-----------------------------\n");
    printf("PID  Semaphore  Port\n");

    for (i = 0; i < MAX_CHILD; i++)
        printf("%04d %04d       %05d\n", (int)(Vector[i].PID), Vector[i].Sem, Vector[i].Port);

    if (attmpt == MAX_CHILD) {
        printf("Error - No space left in vector table\n");
        getchar();
        return 1;
    }


    for (i = 0; i < MAX_CHILD; i++) {
        echoServPort = 49153 + i;
        echoServAddr.sin_port = htons(echoServPort); /* Server port */

        if (connect(server_socket, (struct sockaddr*) &echoServAddr,sizeof(echoServAddr)) < 0) {
            printf("Error Connecting\n");
            getchar();
            return 1;
        } else {
            break;
        }
    }

    mapped_ptr = memcpy((void *)mapped_ptr, (void *)Vector, MAX_CHILD * sizeof(struct v_table));
    msync(mapped_ptr, MAX_CHILD * sizeof(struct v_table), MS_SYNC);

    sscanf(argv[1], "%d", &n);
    for (i = 0; i < n; i++) {
        sprintf(text[1], "Child %04d %d", getpid(), i);
        if (send(server_socket, text[1], strlen(text[1]), 0) != strlen(text[1])) {
            printf("Error : send() sent a different number of bytes\n");
            return 1;
        }
        recv(server_socket, text[2], MAX_LENGTH, 0);
        if(strcmp (text[2], ok)) {
            printf("Error : Wrong Secret\n");
            shutdown(server_socket, 2);
            return 1;
        }
        memset(text[1], 0, MAX_LENGTH);
        memset(text[2], 0, MAX_LENGTH);
    }

    sprintf(text[1], "terminate");
    if (send(server_socket, text[1], strlen(text[1]), 0) != strlen(text[1])) {
        printf("Error : send() sent a different number of bytes\n");
        return 1;
    }
    recv(server_socket, text[2], MAX_LENGTH, 0);
    if(strcmp (text[2], ok)) {
        printf("Error : Wrong Secret\n");
        shutdown(server_socket, 2);
        return 1;
    }
    free(ipaddrstr);

    getchar();
    shm_unlink (SHARED_MEM);
    return 0;
}
\$\endgroup\$
2
  • \$\begingroup\$ @JS1 Can you assist? \$\endgroup\$ Commented Dec 8, 2015 at 7:17
  • \$\begingroup\$ The link to the requirements is broken. Please add the requirements to the question. \$\endgroup\$
    – pacmaninbw
    Commented Feb 27, 2022 at 23:18

1 Answer 1

1
\$\begingroup\$

Inconsistent Error Reporting

There are 2 methods of error reporting in use; one uses perror(), and the other is using printf(). Unfortunately this is inconsistently reporting the errors, the errors reported by perror() are being reported to stderr which is the correct place to report errors. The errors reported using printf() are being written to stdout. It would be better to report errors using fprintf(stderr, FMT); so that all errors are reported to stderr.

Test for Possible Memory Allocation Errors

In modern high level languages such as C++, memory allocation errors throw an exception that the programmer can catch. This is not the case in the C programming language. While it is rare in modern computers because there is so much memory, memory allocation can fail, especially if the code is working in a limited memory application such as embedded control systems. In the C programming language when memory allocation fails, the functions malloc(), calloc() and realloc() return NULL. Referencing any memory address through a NULL pointer results in undefined behavior (UB).

Possible unknown behavior in this case can be a memory page error (in Unix this would be call Segmentation Violation), corrupted data in the program and in very old computers it could even cause the computer to reboot (corruption of the stack pointer).

To prevent this undefined behavior a best practice is to always follow the memory allocation statement with a test that the pointer that was returned is not NULL.

Examples of Current Code:

    for (i = 0; i < 3; i++) {
        text[i] = malloc(MAX_LENGTH);
        memset(text[i], 0, MAX_LENGTH);
    }
    ipaddrstr = malloc(16);
    memcpy(ipaddrstr, "127.0.0.1", strlen("127.0.0.1"));

This second instance of malloc() may also be the cause of a buffer overflow if the IP address used all of the digits in the address.

Example of Current Code with Test:

    for (i = 0; i < 3; i++) {
        text[i] = malloc(MAX_LENGTH);
        if (text[i] == NULL)
        {
            fprintf(stderr, "malloc of text[%d] failed\n", i);
            return EXIT_FAILURE;
        }
        memset(text[i], 0, MAX_LENGTH);
    }

In the case of allocating memory for ipaddrstr there are 2 ways to handle this, the first is to use the library function strdup() the second get the size of the IP address string:

    ipaddrstr = strdup("127.0.0.1");
    if (ipaddrstr == NULL)
    {
        fprintf(stderr, "malloc of (ipaddrstr failed\n");
        return EXIT_FAILURE;
    }
    ipaddrstr = malloc(strlen("127.0.0.1") + 1);
    if (ipaddrstr == NULL)
    {
        fprintf(stderr, "malloc of (ipaddrstr failed\n");
        return EXIT_FAILURE;
    }
    memcpy(ipaddrstr, "127.0.0.1", strlen("127.0.0.1"));

Complexity

The function main() is too complex (does too much). As programs grow in size the use of main() should be limited to calling functions that parse the command line, calling functions that set up for processing, calling functions that execute the desired function of the program, and calling functions to clean up after the main portion of the program.

There is also a programming principle called the Single Responsibility Principle that applies here. The Single Responsibility Principle states:

that every module, class, or function should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by that module, class or function.

Break the main() function in both the parent and child code into small functions that do only one thing. This will make the code easier to read, write, debug and maintain.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.