发送比发送缓冲区中可以存储的数据更多的数据

Send more data than what can be stored in send buffer

我正在使用 C# 套接字来实现我自己的基本 RTSP 服务器(用于学习目的)。我目前正在为服务器编写逻辑,以便在根据 DESCRIBE 请求协商媒体端口时与客户端执行握手。

客户端应用程序不是我编写的,我无权更新软件,它是一个简单的 RTSP 客户端,它协商 Cisco 路由器上的媒体端口,然后将音频传输到连接到它的客户端。

我要发回的有效载荷是 1024 字节和一个 64 字节 header,从检查我的客户端应用程序可以看出,只有 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 跟踪支持

UPDATE 在早些时候与@usr 交谈后,我做了一些进一步的测试,现在可以确认客户端应用程序只会从服务器接收到 400 字节的响应。我h

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 跟踪中,它不是在一个块中发送 header,而是发送几个块返回信息 [TCP segment of a reassembled PDU] I我不确定这意味着什么(网络新东西),但我假设它分解了服务器有效负载并将其分成几个块发送,然后在客户端重新组装以处理请求。

如何在我的服务器应用程序上复制此行为?我在我的响应方法中尝试了以下方法,希望它能通过管道返回数据包并在客户端重新组装,但是我只是继续遇到上面讨论的相同错误:

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);
    }
}

TCP 是一种无边界的字节流。应用程序无法感知任何数据包。 Read/Receive 以不确定的方式调用 return 任意数量的字节(至少一个)。在接收器中,您必须处理读取可能是部分的事实。使用 Receive 的 return 值来确定接收到的字节数。

如果您同步阅读并使用 BinaryReader,这会更容易,因为它会为您执行此循环逻辑。

BeginSend could be firing prematurely, is this a possibility

没有

一个可能的答案可能是客户端在它对您的服务器的请求中设置了特定的块大小(参见 12.7 - https://www.ietf.org/rfc/rfc2326.txt)。这可能适用也可能不适用,但如果您可以转储客户端发送给您的请求并仔细查看,您可能会看到块大小在 400 标记附近。

编辑:在下面查看 Alex 的回答以获得最终答案。

这似乎有争议,但代码中的错误非常愚蠢,我不得不将此问题的成功归功于@Keyz182 的回答。私下讨论后,他注意到我正在使用 \n 添加新行元素。

问题是我需要将这些 returns 更改为 \r\n 并在 header 和 body 之间添加一个以指示客户 header 结束,有效负载的 body 信息开始(头撞砖墙)。

原来客户端上没有400字节的buffer(所以我还是很好奇为什么说400字节的处理buffer)。 Thank-you 一切为了你的帮助!