【问题标题】:Usage of shared memory between 2 processes in c socket client program leads to segmentation faultc socket客户端程序中2个进程之间共享内存的使用导致分段错误
【发布时间】:2023-03-19 01:23:01
【问题描述】:

我正在创建一个具有 2 个进程的客户端程序:一个父进程定期向服务器传输数据以使其知道客户端仍处于连接状态,以及一个接收用户输入并将其发送到服务器的子进程,如果该输入恰好是“#EXIT”,那么子进程将让父进程知道程序需要通过共享内存中的变量终止,然后退出。问题是我在程序打印任何东西之前就遇到了分段错误。

奇怪的是,我第一次运行它时,它给出了预期的输出,并在我输入“#EXIT”时终止,但我随后运行它时,它立即出现了段错误。一开始我以为是因为最后没有调用shm_unlink,但是我加了那个,结果还是一样。然后我以为是因为我没有在子进程上调用exit,但我添加了它,结果还是一样。

客户端代码:

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    if (argc-1 != 2){
        perror("Usage: ./351ChatClient [address] [port]");
        return -1;
    }

    int sock = 0, valread;
    struct sockaddr_in serv_addr;
    char buffer[1024] = {0};

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("\n Socket creation error \n");
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[2]));

    // Convert IPv4 and IPv6 addresses from text to binary form 
    if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0)
    {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        printf("\nConnection Failed \n");
        return -1;
    }

    int* shared_memory;

    int shm_fd = shm_open("Transmitting", O_CREAT | O_EXCL | O_RDWR, S_IRWXU | S_IRWXG);

    ftruncate(shm_fd, sizeof(int));

    shared_memory = (int *) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

    shared_memory[0] = 0;

    int pid = fork();

    if (pid == 0){
        char data[256];
        while (strcmp(data, "#EXIT") != 0){
            fgets(data, sizeof(data), stdin);
            data[strlen(data)-1] = '\0';
            send(sock , data, strlen(data) , 0 );
            valread = read( sock , buffer, 1024);
            printf("%s\n",buffer );
        }
        shared_memory[0] = 1;
        exit(EXIT_SUCCESS);
     }
    else if (pid > 0){
        while(shared_memory[0] == 0){
            // transmit data every couple seconds to let server know client is still connected
        }
        shm_unlink("Transmitting");
        printf("Stopped transmitting\n");
    }
    return 0;
}

服务器代码:

#include <stdio.h>
#include <sys/socket.h> //For Sockets
#include <stdlib.h>
#include <netinet/in.h> //For the AF_INET (Address Family)
#include <string.h>
#include <stdbool.h>
#include <unistd.h>

#define MAXSIZE 256

void add_user(char name[], char password[]){
    if (strlen(name) > MAXSIZE || strlen(password) > MAXSIZE){
        // send error message to client
        return;
    }

    FILE* fp = fopen("users.txt", "a");
    char s[strlen(name)+strlen(password)+2];
    strcpy(s, name);
    strcat(s, "\n");
    strcat(s, password);
    strcat(s, "\n");
    fputs(s, fp);
    fclose(fp);
}

void connect_user(char name[], char password[]){

    FILE* fp = fopen("users.txt", "r");

    char line[MAXSIZE];
    char cur_name[MAXSIZE];

    bool match = false;

    int line_no = 1;
    while (fgets(line, sizeof(line), fp)){

        line[strlen(line)-1] = '\0'; // remove trailing newline

        if (line_no % 2 != 0){

            strcpy(cur_name, line);

            if (strcmp(cur_name, name) == 0){
                match = true;
            }

        }
        else if(match){

            if (strcmp(line, password) == 0){
                char s[strlen(cur_name)+strlen(" is entering the queue\n")];
                strcpy(s, cur_name);
                strcat(s, " is entering the queue\n");
                printf("%s", s);
                // add user to the queue
                return;
            }
            else{
                // send invalid password message to client
                return;
            }
        }
        line_no = line_no + 1;
    }
    add_user(name, password); // if user not found, add it to the DB 
}

int main(int argc, char *argv[]){
    FILE* fp = fopen("users.txt", "a"); // creates users file if it does not exist
    fclose(fp);

    int sockfd, newsockfd, portno;
    socklen_t clilen;
    char buffer[256];
    struct sockaddr_in serv_addr, cli_addr;
    int n;
    if (argc < 2) {
         fprintf(stderr, "ERROR, no port provided\n");
         exit(1);
    }
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
         perror("ERROR opening socket");
    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = atoi(argv[1]);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        perror("ERROR on binding");
    listen(sockfd, 5);

    clilen = sizeof(cli_addr);
    //Below code is modified to handle multiple clients using fork
    //------------------------------------------------------------------
    int pid;
    while (1) {
         newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
         if (newsockfd < 0)
              perror("ERROR on accept");
         //fork new process
         pid = fork();
         if (pid < 0) {
              perror("ERROR in new process creation");
         }
         if (pid == 0) {
            //child process
            close(sockfd);
            //do whatever you want
            bzero(buffer, 256);
            while(strcmp(buffer, "#EXIT") != 0){

                memset(buffer, 0, sizeof(buffer)); // clear the read buffer

                n = read(newsockfd, buffer, 255);
                if (n < 0)
                    perror("ERROR reading from socket");
                printf("Here is the message: %s\n", buffer);

                n = write(newsockfd, "I got your message", 18);
                if (n < 0)
                    perror("ERROR writing to socket");
            }
            n = write(newsockfd, "See you soon!", 18);
            if (n < 0)
                perror("ERROR writing to socket");
            printf("Closing connection with client");
            close(newsockfd);
            exit(EXIT_SUCCESS);
          } else {
             //parent process
             close(newsockfd);
          }
    }
    //-------------------------------------------------------------------
   return 0;
}

当我输入“#EXIT”时我期望收到的输出是

#EXIT
Stopped transmitting

但我收到的输出很简单

segmentation fault (core dumped)

在我有机会输入任何内容之前。

我是 c 套接字和一般 c 的新手,所以请不要犹豫指出我是否在做其他完全错误的事情。谢谢。

编辑:我注意到的另一件事是,当客户端出现段错误时,服务器会收到大量消息。不知道为什么。

编辑2:我将客户端中的所有套接字代码注释掉,重新编译并执行它,仍然发生分段错误。

编辑 3:我在定义之前修复了 while 循环检查 data,但问题仍然存在。

这是简化的客户端代码:

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    if (argc-1 != 2){
        perror("Usage: ./351ChatClient [address] [port]");
        return -1;
    }

    int* shared_memory;

    int shm_fd = shm_open("Transmitting", O_CREAT | O_EXCL | O_RDWR, S_IRWXU | S_IRWXG);

    ftruncate(shm_fd, sizeof(int));

    shared_memory = (int *) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

    shared_memory[0] = 0;

    int pid = fork();

    if (pid == 0){
        char data[256];
        while (true){
            fgets(data, sizeof(data), stdin);
            data[strlen(data)-1] = '\0';
            if(strcmp(data, "#EXIT") != 0)
                break;  
        }
        shared_memory[0] = 1;
        exit(EXIT_SUCCESS);
     }
    else if (pid > 0){
        while(shared_memory[0] == 0){
            // transmit data every couple seconds to let server know client is still connected
        }
        shm_unlink("Transmitting");
        printf("Stopped transmitting\n");
    }
    return 0;
}

编辑 4:我使用命令 valgrind --leak-check=full --track-origins=yes ./351ChatClient 127.0.0.1 8080 通过 valgrind 运行客户端,这就是我得到的:

==11098== Memcheck, a memory error detector
==11098== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==11098== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==11098== Command: ./351ChatClient 127.0.0.1 8080
==11098== 
==11098== Invalid write of size 4
==11098==    at 0x108AA4: main (in /home/jp/Courses/csci351/projects/project1/351ChatClient)
==11098==  Address 0xffffffffffffffff is not stack'd, malloc'd or (recently) free'd
==11098== 
==11098== 
==11098== Process terminating with default action of signal 11 (SIGSEGV)
==11098==  Access not within mapped region at address 0xFFFFFFFFFFFFFFFF
==11098==    at 0x108AA4: main (in /home/jp/Courses/csci351/projects/project1/351ChatClient)
==11098==  If you believe this happened as a result of a stack
==11098==  overflow in your program's main thread (unlikely but
==11098==  possible), you can try to increase the size of the
==11098==  main thread stack using the --main-stacksize= flag.
==11098==  The main thread stack size used in this run was 8388608.
==11098== 
==11098== HEAP SUMMARY:
==11098==     in use at exit: 0 bytes in 0 blocks
==11098==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==11098== 
==11098== All heap blocks were freed -- no leaks are possible
==11098== 
==11098== For counts of detected and suppressed errors, rerun with: -v
==11098== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

【问题讨论】:

  • 你也需要分享对方的代码。我不认为有什么奇怪的事情。
  • 您的问题缺少minimal reproducible example 和段错误点的回溯。特别是,首先从您的示例中删除所有套接字代码,以便您可以真正断言共享内存是问题所在。
  • @jipthechip 除了其他 2 个代码之外,还将在 EDIT 2 中讨论的简化代码。
  • while (strcmp(data, "#EXIT") != 0) 调用未定义的行为,因为 data 未初始化以来的第一次循环迭代。
  • 你是否通过 valgrind 执行了代码?

标签: c linux sockets shared-memory


【解决方案1】:

您的 分段错误 错误可能源于 free(s)s 中的 add_user(...)connect_user(...) 函数,因为 char s[...] 是堆栈上的可变大小数组,而不是分配给堆freed。你需要摆脱它们。

在服务器端和客户端,您无需等待子进程使用wait(..)waitpid(..)

此外,最好使用0s 初始化数组,例如char arr[SIZE] = {0},也适用于可变大小的数组。

AFAI 也知道,bzero 已被弃用并降低了可移植性。所以你应该改用memset(..)

n = write(newsockfd, "See you soon!", 18); 在第 128 行,剩下的 5 字符(字节)呢?垃圾文?

你最好也使用-Weverything 标志。


让我们重点关注以下一点,

shared_memory[0] = 0; ?

如果shm_open(..) 返回-1 会怎样?在这种情况下,您确定shared_memory 指向您可以写作的合法地点吗?您应该始终特别注意返回值及其结果。

int shm_fd = shm_open("Transmitting", O_CREAT | O_EXCL | O_RDWR, S_IRWXU | S_IRWXG);
    if (shm_fd == -1) {
        perror("shm_fd error");
        exit(-1);
    }

在我的计算机中,它会在固有地运行一次后产生shm_fd error: File exists

AFAI 记住,非匿名共享映射对象会保留在计算机中,除非手动或unlink(..)remove(..) 将其移除

或者,只需删除 O_EXCL 标志。

【讨论】:

  • 我摆脱了它们,但仍然没有解决问题。
  • @jipthechip 你看下面的部分答案了吗?
  • 啊,我明白现在是什么问题了。发生的事情是我映射了内存位置并且第一次没有调用unlink,所以当我在第二次执行时映射它时,它出现了段错误。但这意味着如果客户端进程意外终止,这将再次发生。如何检查该位置是否已映射,如果是则取消链接?
  • @jipthechip 当 TCP 连接在一侧关闭时 read() 在另一侧返回 0 字节。此外,你知道共享内存的两侧名称。
猜你喜欢
  • 2011-01-01
  • 1970-01-01
  • 2020-03-22
  • 1970-01-01
  • 1970-01-01
  • 2017-09-22
  • 2018-04-11
  • 1970-01-01
  • 2010-11-15
相关资源
最近更新 更多