C++ 服务器 Linux 机器上的分段错误 - 适用于 Mac

Posted

技术标签:

【中文标题】C++ 服务器 Linux 机器上的分段错误 - 适用于 Mac【英文标题】:Segmentation Fault on C++ Server Linux Machines - Works on Mac 【发布时间】:2015-09-13 22:18:12 【问题描述】:

我正在处理这个家庭作业 作业,并且在这一点上花费了相当多的时间。我已经能够让我的 Mac 完美地运行客户端和服务器,但要求说它必须在 Linux 机器上运行,这是我发现它的主要问题所在。在客户端连接到服务器后,我立即收到分段错误,并且它发生在两条 cout 行之间,这对我来说毫无意义。此时我已经花了大约 4 个小时来研究这个问题,但没有运气。任何人都可以给我任何有用的指示吗?提前致谢!

服务器.cpp

//header files

//input - output declarations included in all C programs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <stdexcept>
#include <iostream>

//contains definitions of a number of data types used in system calls
#include <sys/types.h>

//definitions of structures needed for sockets
#include <sys/socket.h>

//in.h contains constants and structures needed for internet domain addresses
#include <netinet/in.h>

using namespace std;

int* clientNumbers  = new int[50];

void initializeArray()

    for(int i = 0; i < 50; i++)
    
        clientNumbers[i] = 0;
    


//This function is called when a system call fails. It displays a message about the error on stderr and then aborts the program.
void error(const char *msg)

    perror(msg);
    exit(1);

int numberClients = 0;
bool checkNumber(int number)

    for(int i = 0; i < 50; i++)
    
        if(number == clientNumbers[i])
        
            return false;
        
    
    clientNumbers[numberClients] = number;
    numberClients++;
    return true;



int main(int argc, char *argv[])

    pid_t childPID;

    initializeArray();

    //sockfd and newsockfd are file descriptors,These two variables store the values returned by the socket system call and the accept system call.
    //portno stores the port number on which the server accepts connections.
    int sockfd, newsockfd, portno;

    //clilen stores the size of the address of the client. This is required for the accept system call.
    socklen_t clilen;

    //serv_addr will contain the address of the server, and cli_addr will contain the address of the client which connects to the server.
    struct sockaddr_in serv_addr, cli_addr;
    int n;

    //create socket
    //it take three arguments - address domain, type of socket, protocol (zero allows the OS to choose thye appropriate protocols based on type of socket)
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");

    string portNumber;
    cout << "Please Enter Port Number:";
    getline(cin, portNumber);


    //stoi() function can be used to convert port number from a string of digits to an integer, if your input is in the form of a string.
    try
        portno = stoi(portNumber);
    catch(const std::invalid_argument e)
    
        error("ERROR No Port Number Entered.");
    


    if(portno > 65535 || portno < 1)
    
        error("Invalid Port Number. Exiting.");
    

    //contains a code for the address family
    serv_addr.sin_family = AF_INET;

    //contains the IP address of the host
    serv_addr.sin_addr.s_addr = INADDR_ANY;

    //contain the port number
    serv_addr.sin_port = htons(portno);

    //bind() system call binds a socket to an address, in this case the address of the current host and port number on which the server will run.
    //three arguments, the socket file descriptor, the address to which is bound, and the size of the address to which it is bound.
    if (::bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR on binding");

    //listen system call allows the process to listen on the socket for connections.
    //The first argument is the socket file descriptor, and second is number of connections that can be waiting while the process is handling a particular connection.
    listen(sockfd,5);

    while(1)
    
        cout << "Server waiting...\n";

        //This is where the program experiences a Segmentation Fault

        cout << "Just Before Clilen";
        clilen = (socklen_t) sizeof(cli_addr);

        //accept() system call causes the process to block until a client connects to the server.
        newsockfd = accept(sockfd,
                            (struct sockaddr *) &cli_addr,
                            &clilen);

        cout << "WE MADE IT THIS FAR!";

        if (newsockfd < 0)
            error("ERROR on accept");

        string clientNumberString;
        int clientNumber = 0;
        n = read(newsockfd,&clientNumberString,15);
        if (n < 0) error("ERROR reading from socket\n");
        clientNumber = stoi(clientNumberString);

        if(checkNumber(clientNumber)) 

            //reads from the socket into a buffer for a maximum of 255 characters
            //read call uses new file descriptor, the one returned by accept()

            if(fork() == 0)
            
                cout << "Established Connection With: " << clientNumber << "\n";
                while(1)
                
                    string lowerCase;
                    //Read in String from Client
                    n = read(newsockfd, &lowerCase, 255);
                    if(n == 0)break;

                    //transform(lowerCase.begin(), lowerCase.end(), lowerCase.begin(), ::toupper);

                    string upperCase = "";

                    for(int i = 0; i < lowerCase.length(); i++)
                    
                        upperCase += toupper(lowerCase.at(i));
                    

                    n = write(newsockfd, &upperCase, upperCase.length() + 1);
                
                cout << "Disconnecting from Client\n";
                //close connections using file descriptors
                close(newsockfd);
            

        else
        
            n = write(newsockfd, 0, 1);
            cout << "CONNECTION REFUSED: Client Number already Taken.\n";
            break;
        

    

        //
        close(sockfd);


    return 0;

运行输出示例:

 ./server
 Please Enter Port Number:6666
 Server waiting...
 Segmentation fault (core dumped)

来自 GDB 的回溯:

Program received signal SIGSEGV, Segmentation fault.
__GI_____strtol_l_internal (
    nptr=0x2388058 <error: Cannot access memory at address 0x2388058>, 
    endptr=0x7fffffffde00, base=10, group=<optimized out>, 
    loc=0x7ffff78ba060 <_nl_global_locale>) at ../stdlib/strtol_l.c:298
298 ../stdlib/strtol_l.c: No such file or directory.
(gdb) backtrace

#0  __GI_____strtol_l_internal (
    nptr=0x2388058 <error: Cannot access memory at address 0x2388058>, 
    endptr=0x7fffffffde00, base=10, group=<optimized out>, 
    loc=0x7ffff78ba060 <_nl_global_locale>) at ../stdlib/strtol_l.c:298
#1  0x0000000000401ca1 in __gnu_cxx::__stoa<long, int, char, int> (
    __convf=0x401350 <strtol@plt>, __name=0x401da9 "stoi", 
    __str=0x2388058 <error: Cannot access memory at address 0x2388058>, 
    __idx=0x0) at /usr/include/c++/4.8/ext/string_conversions.h:62
#2  0x0000000000401bab in std::stoi (__str=..., __idx=0x0, __base=10)
    at /usr/include/c++/4.8/bits/basic_string.h:2825
#3  0x00000000004017fd in main (argc=1, argv=0x7fffffffdfe8) at main.cpp:143

【问题讨论】:

gdb 的回溯在哪里? 因为输出没有被刷新,你实际上不知道它在哪里崩溃。 “就在克莱伦之前”;当 seg 错误发生时,很可能坐在输出缓冲区中等待写入控制台。 “我们做到了这一点!”也可能在缓冲区中。无法判断是否通过屏幕转储进行调试。在调试输出的末尾添加&lt;&lt;std::endl 可能是提高看到这些消息的几率的最简单方法。 我已经添加了 gdb 回溯。感谢您对 endl 的提示,我会进行这些调整。我是 C++ 新手! 全局变量、手动动态内存和幻数是灾难的根源。使用int clientNumbers[50] = ; 创建一个包含 50 个 0 的数组。或者更好的是使用一个类来表示您使用的客户,以及一个可以根据需要增长的向量,并使用std::find 进行此搜索 我猜你在这里破坏了你的字符串的内部结构:n = read(newsockfd,&amp;clientNumberString,15); 你需要在尝试读入它的缓冲区之前调整字符串的大小。 【参考方案1】:
    string clientNumberString;
    int clientNumber = 0;
    n = read(newsockfd,&clientNumberString,15);

你通过读取上面的数据来丢弃你的字符串。 string 不仅仅是一个字符数组,它是一个包含指针、长度和可能的其他信息的结构。您不能只是在其地址写入任意数据并期望它能够工作。

另外,您似乎对 TCP 的工作原理有一些基本的误解:

    n = read(newsockfd,&clientNumberString,15);
    if (n < 0) error("ERROR reading from socket\n");
    clientNumber = stoi(clientNumberString);

在第一行之后,n,只有n 保存了您收到的字节数。那么stoi 应该如何知道要查看多少字节呢?另外,假设数字是“12”,但read 只读取“1”,因为“2”尚未发送。你会得到错误的号码。这似乎表明您根本不了解 TCP 是字节流。

【讨论】:

【参考方案2】:

寻找可以发生段错误的东西,考虑函数 CheckNumber。

如果测试有可能超过 50 的计数,它将出现段错误。这是因为在函数结束时没有测试来确保 numberClients 保持在范围内。

此外,除非您为读写编写了重载,否则您的调用将无法正常工作。考虑”

std::string s;

读(袜子, &s, 10);

这应该在更好的编译器中给出警告,但不会起作用。 s 以这种方式作为 void * 传入时不会作为字符串类运行。

您可能必须提供一个字符数组:

字符[12];

读取(袜子, s, 10);

然后,如果你想从那里使用字符串类,你可以将该缓冲区移动到 std::string 中。

【讨论】:

您可能不会在搞砸 read 的 buf 参数时收到错误消息,因为它是 void *。只要您给void * 一个指针,它就会很高兴。回避他们的原因之一。

以上是关于C++ 服务器 Linux 机器上的分段错误 - 适用于 Mac的主要内容,如果未能解决你的问题,请参考以下文章

分段错误 Opencv linux c++

R Shiny服务器分段错误

C++ 套接字 - 客户端给出分段错误(linux)

Linux上的AVX分段错误[关闭]

大数组大小的分段错误

为啥在此 C++ 代码中出现分段错误?