send()崩溃了我的程序。

6

我正在运行一个服务器和一个客户端。我正在我的计算机上测试我的程序。

这是在服务器中向客户端发送数据的函数:

int sendToClient(int fd, string msg) {

    cout << "sending to client " << fd << " " << msg <<endl;
    int len = msg.size()+1;
    cout << "10\n";
    /* send msg size */
    if (send(fd,&len,sizeof(int),0)==-1) {
        cout << "error sendToClient\n";
        return -1;
    }
    cout << "11\n";
    /* send msg */
    int nbytes = send(fd,msg.c_str(),len,0); //CRASHES HERE
    cout << "15\n";
    return nbytes;
}

当客户端退出时,它向服务器发送"BYE",服务器会用上述函数进行回复。我将客户端连接到服务器(在一台计算机上完成,有2个终端),当客户端退出时,服务器崩溃了 - 它从未打印出15。你有任何想法为什么吗?有没有任何想法如何测试原因?
谢谢。
编辑:这是我如何关闭客户端的方式:
void closeClient(int notifyServer = 0) {

/** notify server before closing */
if (notifyServer) {
    int len = SERVER_PROTOCOL[bye].size()+1;
    char* buf = new char[len];
    strcpy(buf,SERVER_PROTOCOL[bye].c_str());   //c_str - NEED TO FREE????
    sendToServer(buf,len);
    delete[] buf;
}
close(_sockfd);
}

顺便提一下,如果我跳过这段代码,也就是只留下close(_sockfd)而不通知服务器一切都好的意思——服务器不会崩溃。

编辑2:这是strace.out的结尾:

5211  recv(5, "BYE\0", 4, 0)            = 4
5211  write(1, "received from client 5 \n", 24) = 24
5211  write(1, "command: BYE msg: \n", 19) = 19
5211  write(1, "BYEBYE\n", 7)           = 7
5211  write(1, "response = ALALA!!!\n", 20) = 20
5211  write(1, "sending to client 5 ALALA!!!\n", 29) = 29
5211  write(1, "10\n", 3)               = 3
5211  send(5, "\t\0\0\0", 4, 0)         = 4
5211  write(1, "11\n", 3)               = 3
5211  send(5, "ALALA!!!\0", 9, 0)       = -1 EPIPE (Broken pipe)
5211  --- SIGPIPE (Broken pipe) @ 0 (0) ---
5211  +++ killed by SIGPIPE +++

破管道会导致我的程序崩溃吗?为什么不只是通过send()返回-1呢?

我建议将 msg.c_str() 复制到一个 char 数组中,然后再传递该数组。 - phoxis
请看我回答中关于sigpipe的评论。 - George Kastrinis
@rob 你尝试过不关闭客户端看看错误是否仍然发生了吗? - George Kastrinis
也尝试过不关闭。正如我所提到的,如果去掉if(notifyServer)块,它就可以正常工作。 - Asher Saban
6个回答

15

你可能想在标志中指定MSG_NOSIGNAL

int nbytes = send(fd,msg.c_str(), msg.size(), MSG_NOSIGNAL);

6
您之所以收到SIGPIPE信号是因为Unix系统的一个“特性”,当试图在远程对等方关闭的套接字上发送数据时,系统会触发SIGPIPE信号。由于您未处理该信号,因此将调用默认的信号处理程序,导致程序中止/崩溃。
要获得您想要的行为(即让send()返回错误而不是引发信号),请将以下代码添加到程序的启动例程中(例如main()函数的顶部):
#include <signal.h>

int main(int argc, char ** argv)
{
   [...]
   signal(SIGPIPE, SIG_IGN); 

5
可能是客户端在服务器完成发送之前退出,从而破坏它们之间的套接字。这样就导致了发送崩溃。
链接:link 此套接字已连接,但连接现在已断开。在这种情况下,send 首先生成一个 SIGPIPE 信号;如果忽略或阻止该信号,或者其处理程序返回,则 send 失败并显示 EPIPE。

但如果在发送过程中出现错误,它只应该返回-1给rv,而不是使程序崩溃。对吧? - Asher Saban
“send” 不会因为套接字的另一端已经关闭而崩溃;它会返回一个错误。 - RichieHindle
1
@Richie 链接 这个套接字已经连接,但是连接现在已经断开。在这种情况下,send 首先会生成一个 SIGPIPE 信号;如果该信号被忽略或阻塞,或者其处理程序返回,则 send 失败并出现 EPIPE。 - George Kastrinis
确保当服务器尝试向套接字发送数据时,客户端仍然存在该套接字。 - George Kastrinis
我认为这没有特定的方法。这取决于代码的逻辑。当客户端可能已经关闭连接时,服务器不应尝试向客户端写入数据;或者如果需要写入,服务器应该处理可能发生的任何错误(例如使用信号处理程序)。 - George Kastrinis

1
我觉得下面这行代码很奇怪,因为你定义了int len = msg.size()+1;
int nbytes = send(fd,msg.c_str(),len,0); //CRASHES HERE

如果你定义了 int len = msg.size(); 会发生什么?

@rob:你不想要那个。它是用来分隔C字符串的,而不是字符串的一部分。send知道这一点,你会超出缓冲区的范围。 - Lightness Races in Orbit
+1 是正确的。对于 "foo",size() 返回 3,c_str() 返回一个长度为 4 的字符缓冲区。如果他想发送空字符,他可以自由地这样做。 - Dark Falcon

1
如果客户端在服务器第二次send之前退出,并且连接没有正确处理,您的服务器将保持挂起状态,这可能会引发崩溃。
只是猜测,因为我们不知道服务器和客户端实际上做了什么。

0

如果你正在使用Linux系统,尝试在strace中运行服务器。这将会把大量有用的数据写入到日志文件中。

strace -f -o strace.out ./server

然后查看日志文件末尾。也许很明显程序做了什么并在何时崩溃,也可能不是。如果是后者:将最后几行发布在这里。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接