套接字连接在客户端(c#到PHP)的第二次调用时关闭


Socket connection closes on second call from client (C# to PHP)

我在本地主机上用c#写了一个客户端,用PHP写了一个服务器端,应该通过套接字进行数据通信。

我使用以下c#代码从PHP服务器读取:
string getResponse(string request)
{
    string response = "";
    byte[] bytes = new byte[10024];
    byte[] msg = Encoding.ASCII.GetBytes(request);
    // Send the data through the socket.
    int bytesSent = Sender.Send(msg);

    // Receive the response from the remote device.
    int bytesRec = Sender.Receive(bytes);
    response = Encoding.ASCII.GetString(bytes, 0, bytesRec);

    return response;
}

和Sender,定义和初始化如下

Socket Sender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAddress = IPAddress.Parse("127.0.0.1");               
IPEndPoint remoteEP = new IPEndPoint(ipAddress,8888);           
Sender.Connect(remoteEP);

代码在getResponse的第一次调用中工作正常,但在第二次调用中,它生成以下错误Sender.Receive叫:

SocketException未处理已建立的连接被主机中的软件中止

我注意到在PHP 5中,我们可以使用stream_sockets当我使用stream_socket而不是socket_create时,它就像一个魅力....真奇怪,这是怎么回事?!有什么区别?

<?php
    # server.php
    $server = stream_socket_server("tcp://127.0.0.1:1337", $errno, $errorMessage);
    if ($server === false) {
        throw new UnexpectedValueException("Could not bind to socket: $errorMessage");
    }
    for (;;) {
        $client = @stream_socket_accept($server);
    if ($client) {
        stream_copy_to_stream($client, $client);
        fclose($client);
    }
}

有时你必须为一次发送做多次接收。

尝试接收:

_socket.Receive(Buffer, 0, Buffer.Length, SocketFlags.None);
while (_socket.Available != 0)
{
   _socket.Receive(Buffer, readByte, Buffer.Length - readByte, SocketFlags.None);
}

对于您的c#客户端,使用System.Net.WebClient类而不是较低级别的Socket类可能会更好。例如,WebClient。DownloadString方法几乎完成了代码要做的事情,并且已经经过了彻底的测试。

从这个问题:

这是一个锅炉板错误信息,它来自Windows。底层错误代码是wsaeconaborted。这并不意味着"连接中断"。你必须对你的主机小心一点。这是短语的一部分。在绝大多数Windows应用程序中,确实是桌面应用程序所连接的主机终止了连接。通常是其他地方的服务器。

然而,当您实现自己的服务器时,角色颠倒了。现在,您需要将错误消息读取为"被连接另一端的应用程序终止"。在实现服务器时,这当然并不罕见,使用服务器的客户端程序不太可能出于任何原因终止连接。这可能意味着防火墙或代理终止了连接,但这不太可能,因为它们通常首先不允许建立连接。

在我看来,发生的事情是客户端(c#)试图连接到服务器(PHP)。客户端发送一个请求,然后从PHP接收一个响应;然后,在第二次尝试通过同一个套接字发送请求时,再次尝试发送/接收数据。

请记住,PHP是一种修饰HTTP协议的语言处理器,因此它基本上解释PHP代码,然后通过HTTP层(Apache?)向请求者返回纯HTTP。

我不是HTTP方面的专家,也不是PHP方面的专家,但我认为这是因为PHP正在接收你的数据,发送你的响应,然后关闭连接。这是有意义的,因为通常我们不会在HTTP中保持套接字打开-我们只是触发一个请求,得到一个响应,就是这样。

你正在考虑做的是所谓的长轮询,这是HTTP连接的套接字保持打开更长的持续时间,以便服务器可以继续向客户端发送数据。我对这个话题不太了解,但是我的Google-Fu让我找到了SignalR,它可能会给你指明正确的方向。

如果你不需要长时间的轮询(也就是说,你对创建一个长时间保持打开的套接字不感兴趣),你所需要做的就是在你写服务器时重新连接你的套接字,如果它还没有打开。

当然,这可能是完全错误的。但根据这些现象和我对HTTP的了解,这似乎是有道理的。

TLDR:您正在尝试写一个已经关闭的连接,因为HTTP(因此PHP)只期望每个响应一个请求。您需要将连接重新连接到服务器,或者您也可以实现长轮询。

看起来您只初始化了一次套接字和连接。我不太确定,但我认为当网络响应结束时,它将关闭连接。这意味着你的第二次读取尝试将会失败。您可以尝试将套接字连接代码放在get响应方法中。所以每次你想要一个响应的时候,你都在建立连接。

问题出在你的PHP代码上,而不是你发布的c#。由于某种原因,它正在关闭连接,或者它认为有错误,因此正在中止。

你已经说PHP代码是循环的,所以我猜它是三个中的一个:

  1. 你正在重新协商头每个循环;你只需要协商头每个新的连接;一旦连接,只需读取数据
  2. 你继续关闭套接字
  3. 你的套接字(PHP)在阻塞模式

你的php代码应该是:

    打开套接字服务器,设置为监听模式。(也应该在非阻塞状态。通过socket_recv()处理-稍后会详细说明)
  1. 循环无限
  2. 使用命令行,按Ctrl-C/Cmd-C中断。

然后在循环内:

  1. 寻找新的socket连接(socket_accept())。如果你得到一个,socket_read()头,握手一次,并记住一个数组中的套接字。
  2. 然后运行在步骤1中存储的所有打开的套接字,并将(socket_recv())读入缓冲区。
    • 如果socket_recv()为"false"(使用===比较),则丢弃套接字,它已断开。
    • 如果套接字有数据,你需要揭开它的掩码并处理。

使用"非阻塞"模式的提示,当你发送数据时,也发送一个"命令结束"字符。这有助于PHP知道它得到了一个完整的指令,而不是其中的一部分。

如果PHP是套接字服务器,只有在socket_recv() === false或结束程序时才关闭套接字。否则,它们将保持活动状态并循环。

另一个指针是确保您重用IP地址。socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);

(注意:如果PHP是套接字客户端,则适用不同的规则,这仅适用于将PHP用作套接字服务器。)