0

I'm creating a client-server program that uses sockets to connect, once a client has connected to the server a new process is created for the client (required for project). I have a linked list in the server that stores client structs holding info about them.

The issue that this has created is that when 2 clients connect for example, each client can only view there version of the linked list which holds just there details. So i'm trying to create a shared memory segment that holds the linked list, but i'm new to shared memory and am struggling to get it to work.

node_t *client_list = NULL;

int main() {
    int shmid;
    key_t key;
    key = 1234;

    shmid = shmget(key, SHMSZ, IPC_CREAT | 0666);

    client_list = (node_t *) shmat(shmid, (void *)0, 0);

    while(1) {
        pid_t current_process;
        if ((current_process = fork()) == 0) {
            node_add(client_list, &client);
            node_print(client_list);
            pthread_create(&thread[client_count], NULL, network(client.ID), (void *) &client);

    }
    return 0;
}

(I have left socket connection code etc off to make it more readable). I am probably doing something very wrong in this current version but my understanding was I am creating a shared memory segment for 'node_t *client_list' which is a linked list, and then after a new process is created from connection a client node added to it. Any help would be greatly appreciated.

SkinnyBetas
  • 461
  • 1
  • 5
  • 27
  • can you explain this? "required for project". What makes it required? Like, an assignment where there's just an arbitrary rule about how to do it? Or some other implementation detail? `select` is a really efficient way to handle multiple sockets, and really alleviates the race condition question. It's very performant too, usually, because you spend most of your time waiting for a client not processing. If you are going to use shared memory etc, fine, but you're still going to need some sort of coordination mechanism to make sure the shared memory is consistent. – erik258 Oct 27 '19 at 01:44
  • Apologies for the ambiguity in that, it's an assignment that requires process creation for new clients. I guess basically the main thing i'm trying to tackle is having both connected clients acknowledged on this list. But because a new process is created it seems both clients have a different version of the list just containing themself – SkinnyBetas Oct 27 '19 at 01:48
  • wait ... you're `fork`ing _and `pthread_create`ing? strange. but you're absolutely right about the problem - a `fork` is going to duplicate the process memory space. Shared memory is a solution for sharing data across the `fork`; memory is already shared between threads but I'm not really sure what the pthread_create is supposed to be doing. You'll also need a read/write lock to make sure one process isn't writing the memory while another is reading it or you'll get inconsistent results. – erik258 Oct 27 '19 at 01:56
  • I was having an issue with a deadlock with receiving and sending with multiple clients, creating a thread for receiving client data helped it, or maybe it was just a novice fix. Is my initialisation for the shared memory incorrect? It just doesnt seem to be applying to the list. – SkinnyBetas Oct 27 '19 at 02:01
  • It's hard to say without seeing more of the program ... depends on what's been omitted. You might want to get comfortable with your IPC first, then layer it into your server code. Have you looked at https://stackoverflow.com/questions/5656530/how-to-use-shared-memory-with-linux-in-c ? – erik258 Oct 27 '19 at 23:37

1 Answers1

1

was kind of curious around this question so I wanted to put together a proof of concept of the forking / shared memory as well as the semaphore to guard against reading an incomplete write.

There's a few takeaways for me:

  • mmap seems an easiser and more modern implementation of shared memory than the sysv shared memory segments.
  • semaphore or other coordination is required to make sure the read is finished and the write is not.
  • data in mmap is shared, but if that data is a pointer, it has to point to an mmap segment or it won't work in each fork ... I am pretty sure. So make sure to put all your shared data in shared memory, not just its address.

A note on using shared memory: I created 2 different mmap-shared memory, one for the semaphore and one for the data itself. But I could have instead created a struct and put both variables in that, which probably makes more sense because mmaped memory has to be page-aligned and therefore I think every new memory map requires at least 1 full page of memory. There's definitely some thinking you need to do about how to expose the state you want between the processes.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <semaphore.h>

#define BUFSIZE 20480

int main(int argc, char**argv){
  int protection = PROT_READ | PROT_WRITE;
  int visibility = MAP_SHARED | MAP_ANONYMOUS;
  // we need shared memory for our semaphore
  sem_t* sem = mmap(NULL, sizeof(sem_t), protection, visibility, -1, 0);
  if(sem == MAP_FAILED) {
    perror("Couldn't create memory map");
    exit(errno);
  }
  if( sem_init(sem, 1, 0) != 0 ){
    perror("couldn't init shared memory");
    exit(errno);
  }
  // now shared memory for the data we care about, which is just a string up to BUFSIZE len including termination
  void* shmem = mmap(NULL, BUFSIZE, protection, visibility, -1, 0);
  if(shmem == MAP_FAILED) {
    perror("Couldn't create memory map");
    exit(errno);
  }
  // let's make sure there's a '\0' at the end for the string functions by copying an empty string in.  
  strncpy(shmem, "" , BUFSIZE-1);
  pid_t cpid = fork();
  if(cpid < 0){
    perror("Couldn't fork");
    exit(errno);
  }
  else if( cpid == 0 ){ // child!  
    char* msg = malloc(sizeof(char)*BUFSIZE);
    *msg = '\0';
    while( strlen(msg) == 0 ){
      printf("What would you like the message to be? ");
      fflush(stdout);
      fgets(msg, BUFSIZE, stdin);
    }
    printf("%lu character string input\n", strlen(msg));
    strncpy(shmem, msg, BUFSIZE-1);
    sem_post(sem);
  }
  else {
    int sig;
    if( sem_wait(sem) ){
      perror("couldn't wait for semaphore in parent");
      exit(errno);
    }
    printf("%lu character string input\n", strlen(shmem));
  }
}

erik258
  • 14,701
  • 2
  • 25
  • 31