发送的数据多于发送缓冲区中可存储的数据

时间:2015-06-30 16:30:35

标签: c# sockets rtsp

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

客户端应用程序不是由我编写的,我无权更新软件,它是一个简单的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个字节:

enter image description here

来自S-> C

的wireshark轨迹支持

enter image description here

UPDATE 在早些时候与@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);
    }
}

3 个答案:

答案 0 :(得分:2)

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

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

  

BeginSend可能会过早发射,这是一种可能性

没有

答案 1 :(得分:2)

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

编辑:检查Alex的答案,以获得最终答案。

答案 2 :(得分:2)

这可能看起来有争议,但代码中的错误非常愚蠢,我必须将这个问题的成功归功于答案中的@ Keyz182。私下讨论后,他注意到我正在使用\n添加新的线元素。

问题是我需要将这些返回更改为\r\n并在标题和正文之间添加一个以指示标题结束的客户端以及有效负载的正文信息开始(对着砖墙撞击) )。

事实证明,客户端上没有400字节的缓冲区(所以我仍然觉得它为什么谈论400字节的处理缓冲区)。感谢大家的帮助!