发送的数据量超出了发送缓冲区的容量限制

8
我正在使用C#套接字实现自己的基本RTSP服务器(用于学习目的)。我目前正在编写服务器逻辑,以在与客户端协商媒体端口时执行握手。客户端应用程序不是由我编写的,我无法更新软件,它是一个简单的RTSP客户端,可在Cisco路由器上协商媒体端口,然后将音频传输到连接到该客户端的客户端。
我要发送回的有效负载为1024字节和64字节的头部。从检查我的客户端应用程序可以看出,只有400字节被传回给客户端。响应有效负载在C#中定义为:
// Build the RTSP Response string
var body =      "v=0\n" +
                "o=- 575000 575000 IN IP4 " + m_serverIP + "\n" +
                "s=" + stream + "\n" +
                "i=<No author> <No copyright>\n" + 
                "c= IN IP4 0.0.0.0\n"+
                "t=0 0\n" +
                "a=SdpplinVersion:1610641560\n" +
                "a=StreamCount:integer;1\n" +
                "a=control:*\n" +
                "a=Flags:integer;1\n" +
                "a=HasParam:integer;0\n" +
                "a=LatencyMode:integer;0\n" + 
                "a=LiveStream:integer;1\n" + 
                "a=mr:intgner;0\n" +
                "a=nr:integer;0\n" +
                "a=sr:integer;0\n" + 
                "a=URL:string;\"Streams/" + stream + "\"\n" +
                "a=range:npt=0-\n" + 
                "m=audio 0 RTP/AVP 8" + // 49170 is the RTP transport port and 8 is A-Law audio
                "b=AS:90\n" +
                "b=TIAS:64000\n" +
                "b=RR:1280\n" +
                "b=RS:640\n" + 
                "a=maxprate:50.000000\n" +
                "a=control:streamid=1\n" +
                "a=range:npt=0-\n" +
                "a=length:npt=0\n" +
                "a=rtpmap:8 pcma/8000/1\n" +
                "a=fmtp:8" +
                "a=mimetype:string;\"audio/pcma\"\n" +
                "a=ASMRuleBook:string;\"marker=0, Avera**MSG 00053 TRUNCATED**\n" +
                "**MSG 0053 CONTINUATION #01**geBandwidth=64000, Priority=9, timestampdelivery=true;\"\n" +
                "a=3GPP-Adaptation-Support:1\n" +
                "a=Helix-Adaptation-Support:1\n" +
                "a=AvgBitRate:integer;64000\n" +
                "a=AvgPacketSize:integer;160\n" + 
                "a=BitsPerSample:integer;16\n" +
                "a=LiveStream:integer;1\n" + 
                "a=MaxBitRate:integer;64000\n" +
                "a=MaxPacketSize:integer;160\n" +
                "a=Preroll:integer;2000\n" +
                "a=StartTime:integer;0\n" +
                "a=OpaqueData:buffer;\"AAB2dwAGAAEAAB9AAAAfQAABABAAAA==\""; 

var header =    "RTSP/1.0 200 OK\n" +
                "Content-Length: " + body.Length + "\n" +
                "x-real-usestrackid:1\n" +
                "Content-Type: application/sdp\n" +
                "Vary: User-Agent, ClientID\n" +
                "Content-Base: " + requestURI + "\n" +
                "vsrc:" + m_serverIP + "/viewsource/template.html\n" +
                "Set-Cookie: " + Auth.getCookie() + "\n" +
                "Date: " + System.DateTime.Now + "\n" +
                "CSeq: 0\n";

var response = header + body;

var byteArray = System.Text.Encoding.UTF8.GetBytes(response);
respondToClient(byteArray, socket);

响应客户端的方法respondToClient实现如下:

private static void respondToClient(byte[] byteChunk, Socket socket)
{
    socket.BeginSend(byteChunk, 0, byteChunk.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
    socket.BeginReceive(m_dataBuffer, 0, m_dataBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socket);
}

使用SendCallback来确定数据包何时已发送到客户端:

private static void SendCallback(IAsyncResult res)
{
    try
    {
        var socket = (Socket)res.AsyncState;
        socket.EndSend(res);
    }
    catch (Exception e)
    {
        Console.Write(e.Message);
    }
}

为什么我的客户端应用程序只接收了400字节,如果是这样,如何确保整个数据包被发送(我正在使用TCP)?这是C#设置的硬限制吗?我咨询了MSDN,并没有找到任何支持这个理论的信息。
如果需要有关服务器实现的更多详细信息,请询问。
编辑:这里有一些值得思考的东西,我不知道为什么会发生这种情况,所以如果有人能启发我,那就太好了……@Gregory A Beamer在评论中建议EndSend可能会过早触发,这是可能的吗?根据我的理解,我认为异步回调只会在所有字节从服务器发送到监听客户端套接字后触发一次。
为了让我知道客户端没有完全接收数据包,以下信息说明客户端应用程序仅接收了400字节:
由S->C的wireshark跟踪支持
更新:在之前与@usr交谈之后,我进行了进一步的测试,现在可以确认客户端应用程序只会从服务器接收到400个字节的响应。
Jul  1 13:28:29: //-1//RTSP:/rtsp_read_svr_resp: Socket = 0
Jul  1 13:28:29: //-1//RTSP:/rtsp_read_svr_resp: NBYTES = 1384
Jul  1 13:28:29: //-1//RTSP:/rtsp_process_single_svr_resp:
Jul  1 13:28:29: rtsp_process_single_svr_resp: 400 bytes of data:
RTSP/1.0 200 OK
Content-Length: 1012
x-real-usestrackid:1
Content-Type: application/sdp
Vary: User-Agent, ClientID
Content-Base: rtsp://10.96.134.50/streams/stream01.dcw
vsrc:10.96.134.50/viewsource/template.html
Set-Cookie: cbid=aabtvqjcldwjwjbatynprfpfltxaspyopoccbtewiddxuzhsesflnkzvkwibtikwfhuhhzzz;path=/;expires=09/10/2015 14:28:38
Date: 01/07/2015 14:28:38
CSeq: 0  
v=0
o=- 575000 575000 IN IP4

^ 当我拨号到服务器时,客户端应用程序记录的日志跟踪如上所述。我们可以看到它从客户端接收了1384个字节,但其处理缓冲区只能存储400个字节。我查看了一个现有应用程序的日志,该应用程序处理对同一设备的流式传输,在wireshark跟踪中,它不是一次性发送头部信息,而是返回几个块,并带有信息[TCP segment of a reassembled PDU]。我不确定这是什么意思(对网络方面很陌生),但我认为它正在分解服务器有效载荷并将其分成几个块发送,然后在客户端重新组装以处理请求。

我该如何在我的服务器应用程序上复制此行为?我尝试在响应方法中使用以下内容,希望它会将数据包返回并在客户端重新组装,但是我仍然遇到了以上讨论的相同错误:

private static void respondToClient(byte[] byteChunk, Socket socket)
{
    // Divide the byte chunk into 300 byte chunks
    if (byteChunk.Length >= 400)
    {
        Console.WriteLine("Total bytes: " + byteChunk.Length);

        var totalBytes = byteChunk.Length;
        var chunkSize = 400;
        var tmp = new byte[chunkSize];

        var packets  = totalBytes / chunkSize;
        var overflow = totalBytes % chunkSize;

        Console.WriteLine("Sending " + totalBytes + " in " + packets + " packets, with an overflow packets of " + overflow + " bytes.");

        for (var i = 0; i < packets * chunkSize; i+=chunkSize)
        {
            Console.WriteLine("Sending chunk " + i + " to " + (i + chunkSize));
            tmp = byteChunk.Skip(i).Take(chunkSize).ToArray();
            socket.BeginSend(tmp, 0, tmp.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
        }
        if (overflow > 0)
        {
            Console.WriteLine("Sending overflow chunk: " + overflow + " at index " + (totalBytes - overflow) + " to " + (totalBytes ));
            tmp = byteChunk.Skip(byteChunk.Length - overflow).Take(overflow).ToArray();
            socket.BeginSend(tmp, 0, tmp.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
        }
    }
    else
    {
        socket.BeginSend(byteChunk, 0, byteChunk.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
        socket.BeginReceive(m_dataBuffer, 0, m_dataBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socket);
    }
}

发送方法限制为 System.Int32。 - Pranay Rana
你是否遇到了异常?我还注意到你的 a=fmtp:8 行末尾没有换行符。 - Frank Bryce
左场概念...客户端和服务器之间有双工不匹配的可能吗? - David W
@DavidW 感谢您的评论 - 什么是复工不匹配?如上所述,网络方面对我来说很新! - Halfpint
谢谢你的想法,David。我会更深入地研究它,因为它可能会为问题提供一些启示!然而,用多个或一个客户端连接运行相同的测试多次,结果收集到的客户端数据始终是400字节,这似乎很奇怪。感谢你的建议 :) - Halfpint
显示剩余8条评论
3个回答

2
TCP是一种无边界的字节流,应用程序无法感知任何数据包。读/接收调用以一种非确定性的方式返回任意数量的字节(至少一个)。在接收方,必须处理读取可能是部分的事实。使用Receive的返回值来确定接收到的字节数会更好。
如果您同步读取并使用BinaryReader,则所有这些都更容易,因为它会为您执行循环逻辑。
“BeginSend”过早触发的可能性不大。

抱歉,我是指 EndSend,但我认为答案仍然是否定的(正如我所想的)。我对这个应用程序唯一的问题是我无法控制客户端,它是一个SIP设备,客户端软件无法修改(这让我的工作变得更加困难)。在服务器应用程序上有没有办法确保所有字节都传输到侦听套接字? - Halfpint
好的。首先要注意客户端已经损坏了。您应该能够通过禁用 Nagling 并执行一次 BeginSend 调用来发送一个单个 TCP 数据包。使用 Wireshark 确认 TCP 包以预期的 1024+64 字节数据离开。这是一种hack方式,顺便问一下,您是如何确定客户端仅接收到部分数据的呢? - usr
我已经更新了我的问题,并附上了Wireshark跟踪和客户端应用程序的日志。 - Halfpint
我不知道如何进一步调试。Wireshark跟踪没有显示任何类似于400字节的数据包。 - usr
它被用于电话应用程序,是一台Cisco路由器,通过RTSP建立媒体端口,然后将音频传输到连接的客户端。我想在这种情况下拥有一个小的接收缓冲区可能是有意义的?这就是我所得到的所有实现细节。 - Halfpint
显示剩余7条评论

2
可能的答案是客户端在请求您的服务器时设置了特定的块大小(请参见12.7-https://www.ietf.org/rfc/rfc2326.txt)。这可能适用,也可能不适用,但如果您能够转储客户端发送给您的请求并查看其中的内容,您可能会看到某个地方陈述了大约400左右的块大小。

编辑:请查看Alex下面的答案以获取最终答案。


1
不幸的是,我花了很多时间查找RFC文档,但我确实同意客户端必须有某种软件限制。只是让人烦恼的是,客户端应用程序是一个没有任何文档的黑匣子! - Halfpint

2
这段代码中的bug非常愚蠢,虽然可能有争议,但我必须将此问题的成功归功于回答中的@Keyz182。在私下讨论后,他注意到我使用\n添加新行元素。
问题是我需要将这些回车符更改为\r\n,并在标头和正文之间添加一个回车符,以指示客户端标头结束和有效载荷的正文信息开始(撞了一下头)。
事实证明,客户端上没有400字节缓冲区(所以我仍然感到困惑,为什么它会谈论400字节的处理缓冲区)。谢谢大家的帮助!

1
可能是分配给读取单行的缓冲区大小的问题。由于它没有看到预期的CRLF,它会一直读取直到缓冲区满,然后才意识到这是无效的行,在此时它会突然关闭连接。 - Keyz182
1
@Keyz182,现在你这么说,这确实很有道理——我总是忽略那些小细节......再次感谢你的帮助。 - Halfpint

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