【问题标题】:Can the server use the same socket to send the response to the client? how?服务器可以使用相同的套接字将响应发送给客户端吗?如何?
【发布时间】:2009-01-21 22:54:48
【问题描述】:

我正在使用 Berkeley 套接字(包括:Internet 域和 Unix 域),我想知道服务器是否可以使用相同的套接字来读取请求并将响应写入客户端。或者客户端是否应该创建另一个套接字来等待重播,服务器在处理收到的消息后连接到它。

顺便说一下,我说的是面向连接的套接字(流套接字、TCP、...)。

这是简化的服务器代码(为了简单起见,我在这里省略了对系统调用的错误检查):

int main() {

    int server_socket, connected_socket;
    struct sockaddr_in server_addr;
    char buf[1024];
    char aux[256];
    int bytes_read;

    server_socket = socket(AF_INET, SOCK_STREAM, 0);    

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(1234);
    bind(server_socket, &server_addr, sizeof(server_addr))

    listen(server_socket, 5)

    connected_sodket = accept(server_socket, 0, 0);
    do {
        bzero(buf, sizeof(buf));
        bytes_read = read(connected_socket, buf, sizeof(buf));        
    } while (bytes_read > 0);          

    /* Here I want to use connected_socket to write the reply, can I? */

    close(connected_socket);       

    close(server_socket);

    return (EXIT_SUCCESS);
}

这是简化的客户端代码(为了简单起见,我在这里省略了对系统调用的错误检查):

int main() {

    int client_socket;
    struct sockaddr_in server_addr;

    client_socket = socket(AF_INET, SOCK_STREAM, 0);

    hp = gethostbyname("myhost");
    server_addr.sin_family = AF_INET;
    memcpy(&server_addr.sin_addr, hp->h_addr_list[0], hp->h_length);
    server_addr.sin_port = htons(1234);

    connect(client_socket, &server_addr, sizeof(server_addr));

    write(client_socket, MSG, sizeof(MSG));

    /* Here I want to wait for a response from the server using client_socket, can I? */

    close(client_socket);

    return (EXIT_SUCCESS);
}

我可以在服务器中使用connected_socket 并在客户端使用client_socket 来传回响应消息吗?还是应该使用“接受”时在服务器中获得的客户端地址连接到客户端中的套接字?

我尝试在显示评论的客户端/服务器中使用读/写,但这样两个程序都被阻止,这似乎是一个死锁。

提前致谢! 问候。

【问题讨论】:

    标签: c deadlock sockets


    【解决方案1】:

    应该使用同一个socket!

    您的应用程序协议明确定义了客户端和服务器何时应该等待数据或相互发送消息;假设一个协议只有一个来自客户端的请求和一个来自服务器的响应,以下应该成立:


    • 客户端与服务器建立连接;
    • 客户端发送请求(使用 send());
    • 客户端知道,凭借协议,服务器将回复;因此它在同一个套接字上等待数据(recv());
    • 验证响应后,客户端可以关闭套接字。

    • 服务器接受来自客户端的连接;
    • 服务器知道第一步取决于客户端,因此它等待数据(recv());
    • 服务器验证请求;
    • 服务器现在知道,从协议,客户端正在等待数据;因此它使用 send() 发送响应;
    • 服务器知道,从协议中,没有进一步的步骤;因此它可以关闭套接字。

    【讨论】:

      【解决方案2】:

      您可以使用相同的套接字,但您的程序设置为让服务器在尝试回复之前读取客户端发送的所有内容。因此,在客户端关闭其套接字的写入端之前,服务器中的循环不会完成,因此服务器会获得 EOF(读取 0 字节),因此服务器将永远不会发回其响应。

      有几种方法可以解决这个问题。

      1. 您可以在服务器看到整个请求后中断循环,而不是读取直到 EOF。这要求客户端发送的数据以某种方式自定界,以便服务器可以知道它何时读取所有数据。
      2. 您可以使用第二个连接进行回复。可能不是最好的。
      3. 您可以使用非对称关闭套接字。让客户端执行shutdown(client_socket, SHUT_WR) 半关闭套接字。然后服务器将看到 EOF(并且循环将结束),但套接字上的另一个方向仍会为回复打开。

      【讨论】:

      • 关机(client_socket, SHUT_WR);似乎是最优雅的方式。它成功了!谢谢!
      • 从历史上看,许多 Internet 协议都是“基于行”的,因为当服务器接收到换行符(或 CRLF)时,它会处理当前的输入行并将响应发送回客户端。
      • 如选项 1 所说,我如何“打破服务器中的循环”?
      • 当服务器看到请求的结束分隔符时,它会跳出循环并发送回复,而不是等待EOF
      【解决方案3】:

      是的,这是可能的。查看this page 的简单服务器(和简单客户端)示例。请注意,服务器通常会将“接受”文件描述符传递给新进程,以便它可以继续侦听更多传入连接

      【讨论】:

        【解决方案4】:

        您不仅应该使用同一个套接字(正如 Federico 所说),实际上您必须才能让返回的数据包通过防火墙。

        防火墙知道 TCP 连接,如果防火墙内的机器启动了连接,它会自动允许返回的数据通过。相反,如果您尝试从外部创建新的 TCP 套接字,防火墙会阻止它,除非该流量得到特别允许。

        【讨论】:

          【解决方案5】:

          是的,SOCK_STREAM 套接字是双向的。您应该能够在连接的每一侧读取和写入相同的套接字。 socket(2) 的手册页对此有更多详细信息。

          【讨论】:

            【解决方案6】:

            是的,你可以。 TCP 套接字是双向的。只需使用相同的 read() 和 write() 函数。还要确保检查对 connect()、read()、write() 等的所有调用的错误情况,因为您无法控制网络上发生的情况。

            【讨论】:

              【解决方案7】:

              对不起;我没有说,但我确实这样尝试过

              评论所在服务器中的这段代码:

              write(connected_socket, "Ok, I got it", sizeof("Ok, I got it"));
              

              以及注释所在的客户端中的这段代码:

              read(client_socket, buf, sizeof(buf));
              

              两个程序都被阻止,当我杀死客户端时,服务器会显示它收到的消息(在服务器调用 read 之后我有一个 printf)。

              我尝试使用 send 和 recv(都带有 0 标志)而不是 read 和 write,但它没有改变。

              【讨论】:

              • 你的“buf”太大了。 read() 阻塞,直到它完全填满。请参阅 man read(),并阅读有关不同行为的非阻塞 IO 的更多信息。
              • 如果我将代码保留为原始帖子(服务器没有回复),服务器会立即解除阻塞,并且它正在使用与客户端一样大的缓冲区。
              • wdu 不正确 - 而“fread()”将阻塞直到缓冲区已满,而“read”则不会。在套接字上,它通常会将每个写入(或数据包)匹配到一个读取。
              • 嗯,这符合我的描述。始终读取小于或等于您发送的内容,并且您的呼叫不会阻塞。 read() 等待直到接收到足够的数据以完全填充缓冲区(或直到连接关闭或其他错误),除非套接字是非阻塞的。
              • 是的,Alnitak,我刚刚重新阅读了 read 手册页,它没有说明填充缓冲区。谢谢!
              【解决方案8】:

              在您当前的设置中,服务器会尝试读取,直到客户端关闭套接字,而客户端在服务器应答之前不会关闭套接字。因此,您有一种“僵局”。服务器不会停下来读取,希望有更多的数据到达,而客户端无法告诉服务器它已经完成了。

              您需要某种方式让服务器识别请求已完成,而连接仍处于打开状态。例如,如果您通过换行符终止请求,服务器可以检查它是否收到完整的行,然后停止读取并发送答案。

              【讨论】:

              • 好吧,你的权利,现在你提到它我看到死锁。但是,我不明白您的解决方法。
              • 说客户端发送“列表文件\n”。服务器使用第一个 read() 接收“list f”,没有找到“\n”并继续 read()。然后它接收剩余的“iles\n”,现在它的缓冲区中有“list files\n”。它看到“\n”,决定命令完成并发送响应。
              • 顺便说一句:在您的示例中,您在 while 循环的每次迭代中将缓冲区归零,一旦您到达计划发送回复的点,缓冲区就会为空......您可能还需要使用 recv() 而不是 read()。
              • 感谢您的示例和 cmets。但是,我在按照 Chris Dodd 的建议发送消息后解决了客户端调用 shutdown(client_socket, SHUT_WR) 的问题。
              【解决方案9】:

              你试过了吗?当您实际将代码放在指示的位置时,它看起来应该可以工作

              【讨论】:

                猜你喜欢
                • 2014-02-04
                • 1970-01-01
                • 2016-10-15
                • 1970-01-01
                • 2021-02-14
                • 2020-06-01
                • 1970-01-01
                • 2016-03-03
                • 2013-08-27
                相关资源
                最近更新 更多