简体   繁体   English

C# .NET 套接字 tcp begin/endreceive 客户端:endreceive 读取所有字节,但在 beginreceive 中,缓冲区大小自行更改

[英]C# .NET socket tcp begin/endreceive client : endreceive reads all bytes, but in beginreceive, the buffer size changes on its own

so i have a server and client using the class Socket (on tcp).所以我有一个使用 Socket 类(在 tcp 上)的服务器和客户端。

I've been struggling with this problem for a few weeks, I've searched a lot but I don't understand how that problem is possible.我一直在为这个问题苦苦挣扎了几个星期,我搜索了很多,但我不明白这个问题是怎么可能的。 I've been watching debugger a lot, watching values of my variables but can't see where the problem is.我一直在观察调试器,观察变量的值,但看不出问题出在哪里。

For example, the client sends 100 bytes, and the server receives them, proceeds them and send back 150 bytes.例如,客户端发送 100 个字节,服务器接收它们,处理它们并返回 150 个字节。 But, on client side, the buffer changes size each time the client receives a message (i don't know why, maybe because of beginreceive).但是,在客户端,每次客户端收到消息时缓冲区都会改变大小(我不知道为什么,可能是因为 beginreceive)。

So, when the server sends these 150 bytes, the client beginreceive, but I see that the buffer size is 100 (same size as the previous message the client has sent).因此,当服务器发送这 150 个字节时,客户端开始接收,但我看到缓冲区大小为 100(与客户端发送的前一条消息的大小相同)。

And the client does "endreceive", and read 150 bytes (the right amount of bytes sent by server).客户端执行“接收”,并读取 150 个字节(服务器发送的正确字节数)。

When i want to get the string, i use an encoding function, the buffer containing message, and the number of bytes received.当我想获取字符串时,我使用编码函数、包含消息的缓冲区以及接收到的字节数。

So, i end up having a buffer of size of 100 bytes (wrong size) and the number of bytes read is 150 bytes (right amount).所以,我最终有一个大小为 100 字节(错误大小)的缓冲区,读取的字节数为 150 字节(正确数量)。

I've modified my code many times, tried to define "new byte[bufferSize]", but the problem stays the same.我已经多次修改我的代码,试图定义“新字节[bufferSize]”,但问题保持不变。

Sometimes, the client even receive message that the server has never sent!有时,客户端甚至会收到服务器从未发送过的消息! That's weird.这很奇怪。 The client seems sometimes to receive bytes from the "server buffer" (i haven't found how to check it)客户端有时似乎从“服务器缓冲区”接收字节(我还没有找到如何检查它)

       public void InitializeReceive(Socket soc)
        {
             //bufferSize is fixed to 256
            MyBuffer = new byte[bufferSize];

            soc.BeginReceive(MyBuffer , 0, MyBuffer .Length,
                SocketFlags.None, new AsyncCallback(Receiving), soc);

        }
        public void Receiving(IAsyncResult iar)
        {

            Socket tmp = (Socket)iar.AsyncState;

            int received= tmp.EndReceive(iar);

            if (received> 0)
            {
                 //problem is here. MyBuffer has a size of 100 bytes, received is 150 bytes
                string msg = Encoding.Unicode.GetString(MyBuffer, 0, received);



                //next message
                InitializeReceive(tmp);
            }

        }

i've tried checking heap stack, stuff like this too, but i don't really know how to diagnostic it.我试过检查堆堆栈,类似这样的东西,但我真的不知道如何诊断它。

The first messages between client and server are okay.客户端和服务器之间的第一条消息没问题。 But it becomes like this after a few exchanges, and the buffer that i fixed isn't full at all.但是几次交换后就变成这样了,我修复的缓冲区根本就没有满。 The size of first messages were around 36 bytes, over 100 bytes, and so on.第一条消息的大小约为 36 字节,超过 100 字节,依此类推。 It doesn't seem to be a problem of size here这里好像不是大小问题

I don't even understand how it is possible.我什至不明白这怎么可能。 I hope someone has an explanation thank you !希望有大神给个解释谢谢!

EDIT :编辑 :

So, as asked, I tried to make a minimal reproducible code.所以,正如所问,我试图制作一个最小的可重现代码。

I've tested it myself and yes, the problem still stays.我自己测试过,是的,问题仍然存在。 I get the exception "System.ArgumentOutOfRangeException " in the client side.我在客户端收到异常“System.ArgumentOutOfRangeException”。

Server code :服务器代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
//
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace BugServeurSocket1
{
    public partial class Form1 : Form
    {
        private Socket MonServeur, MonClient;


        int bufferSize = 256;
        private byte[] MonBuffer;

        int portServeur = 8000;

        string tAdversaire = "1";

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            MonBuffer = new byte[bufferSize];

            #region mise en place serveur
  
            IPAddress IPServeur = CorrectAddress(Dns.GetHostName());

            MonServeur = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            MonServeur.Bind(new IPEndPoint(IPServeur, portServeur));

            MonServeur.Listen(10);

            MonServeur.BeginAccept(new AsyncCallback(SurDemandeConnexion), MonServeur);
            #endregion
        }

        //réseau
        #region fct perso
        #region verif adr ipv4
        private IPAddress CorrectAddress(string nPC)
        {
            IPAddress ipReponse = null;

            if (nPC.Length > 0)
            {
                IPAddress[] IPserveur = Dns.GetHostEntry(nPC).AddressList;

                for (int i = 0; i < IPserveur.Length; i++)
                {
                    Ping pingServeur = new Ping();
                    //envoyer ping
                    PingReply pingResponse = pingServeur.Send(IPserveur[i]);

                    if (pingResponse.Status == IPStatus.Success)

                        if (IPserveur[i].AddressFamily == AddressFamily.InterNetwork)
                        {

                            ipReponse = IPserveur[i];
                            break;
                        }
                }//for
            }

            return ipReponse;
        }
        #endregion
        #region reception connexion client
        public void SurDemandeConnexion(IAsyncResult iar)
        {
            Socket tmp = (Socket)iar.AsyncState;

            MonServeur.BeginAccept(new AsyncCallback(SurDemandeConnexion), MonServeur);//accepter nouvelles connexions

            MonClient = tmp.EndAccept(iar);

            //MonBuffer = new byte[256];//test
            MonBuffer = Encoding.Unicode.GetBytes("Connexion acceptée");
            MonClient.Send(MonBuffer, 0, MonBuffer.Length, SocketFlags.None);

            //ver7
            InitializeReceive(MonClient);
        }
        #endregion
        public void InitializeReceive(Socket soc)
        {
            MonBuffer = new byte[256];//empty buffer//test

            soc.BeginReceive(MonBuffer, 0, MonBuffer.Length,
                SocketFlags.None, new AsyncCallback(ReceiveMsg), soc);
        }
        public void ReceiveMsg(IAsyncResult iar)
        {
            Socket tmp = (Socket)iar.AsyncState;

            int received = tmp.EndReceive(iar);

            if (received > 0)
            {
                string msg = Encoding.Unicode.GetString(MonBuffer, 0, received);

                string[] msgSplit = msg.Split(':');

                //pseudo
                string msgKey = msgSplit[0];

                #region recherche adversaire
                if (msgKey == "JEU_CHERCHER_ADVERSAIRE")
                {
                        MonBuffer = Encoding.Unicode.GetBytes("TESTJEU_CHERCHER_ADVERSAIRE:" + tAdversaire + ":"
+ tAdversaire + "=adversaire_RECHERCHE;joueur_RECHERCHANT");

                       MonClient.Send(MonBuffer, 0, MonBuffer.Length, SocketFlags.None);
                }
                #endregion

                //next msg
                InitializeReceive(tmp);
            }
        }
        #endregion
    }
}

Client code:客户端代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO; 
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
//
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;

namespace BugClientSocket1
{
    public partial class Form1 : Form
    {
        private Socket MonClient;

        //buffer
        int bufferSize = 256;
        private byte[] MonBuffer;

        int portServeur = 8000;

        string pseudoID = "1:";
        string tAdversaire = "1";

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            tPseudo.Text = pseudoID;

            MonBuffer = new byte[bufferSize];
        }

        private IPAddress CorrectAddress(string nPC)
        {
            IPAddress ipReponse = null;

            try
            {
                if (nPC.Length > 0)
                {
                    IPAddress[] IPserveur = Dns.GetHostEntry(nPC).AddressList; 
                    for (int i = 0; i < IPserveur.Length; i++)
                    {
                        Ping pingServeur = new Ping();
                        PingReply pingResponse = pingServeur.Send(IPserveur[i]);

                        if (pingResponse.Status == IPStatus.Success)
                            if (IPserveur[i].AddressFamily == AddressFamily.InterNetwork)
                            {
                                ipReponse = IPserveur[i];
                                break;
                            }
                    }//for
                }

                return ipReponse;
            }
            catch (SocketException e)
            {
                return null;
            }
        }

        private void BConnecter_Click(object sender, EventArgs e)
        {
            MonClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            MonClient.Blocking = false;


            //tserveur.txt -> replace with your PC name
            IPAddress IPserveur = CorrectAddress(tServeur.Text);

            if (IPserveur != null)
                MonClient.BeginConnect(new IPEndPoint(IPserveur, portServeur), new AsyncCallback(OnConnection), MonClient);

        }
        #region Se connecter au serveur
        public void OnConnection(IAsyncResult iar)
        {
            Socket tmp = (Socket)iar.AsyncState;

            if (tmp.Connected)
            {
                byte[] msg = Encoding.Unicode.GetBytes("PSEUDO:" + tPseudo.Text);

                //send pseudo
                MonClient.Send(msg, 0, msg.Length, SocketFlags.None);

                //next msg
                InitializeReceive(tmp);

            }
            else
            {
                MessageBox.Show("Serveur inaccessible");
            }

        }
        #endregion
        #region Debut reception msg + suite
        //cfr beginaccept-> beginreceive ici
        public void InitializeReceive(Socket soc)
        {
            MonBuffer = new byte[bufferSize];

            soc.BeginReceive(MonBuffer, 0, MonBuffer.Length,
                SocketFlags.None, new AsyncCallback(ReceiveMsg), soc);

        }
        public void ReceiveMsg(IAsyncResult iar)
        {
            Socket tmp = (Socket)iar.AsyncState;

            int received = tmp.EndReceive(iar);

            if (received > 0)
            {
                string msg = Encoding.Unicode.GetString(MonBuffer, 0, received);

                //next msg
                InitializeReceive(tmp);
            }

        }
        #endregion

        private void BChercher_Click(object sender, EventArgs e)
        {
            if (MonClient != null && tPseudoAdversaire.Text != "")
            {
                //MonBuffer = new byte[bufferSize];
                MonBuffer = Encoding.Unicode.GetBytes("JEU_CHERCHER_ADVERSAIRE:" + tAdversaire + ":"
                    + pseudoID + "=adversaire_RECHERCHE;joueur_RECHERCHANT");

                //send pseudo
                MonClient.Send(MonBuffer, 0, MonBuffer.Length, SocketFlags.None);

            }

        }
    }
}


I did let text on purpose (such as JEU_CHERCHER...), to get the exact same problem.我确实故意让文本(例如 JEU_CHERCHER ...)得到完全相同的问题。

My application is a winform in visual studio 2019我的应用程序是Visual Studio 2019 中winform

To reproduce the bug, here the steps:要重现该错误,请执行以下步骤:

  1. Put breakpoints in method "beginreceive" and "end receive", and variable "received";在方法“beginreceive”和“end receive”和变量“received”中放置断点; to see the number of byte and string.查看字节和字符串的数量。 Also check size of MonBuffer when arriving to these break points到达这些断点时还要检查 MonBuffer 的大小
  2. clic on "connexion" button to connect to server单击“连接”按钮连接到服务器
  3. Server receive "pseudo" and accept client服务器接收“伪”并接受客户端
  4. Server send message saying "ok you are connected"服务器发送消息说“好的,您已连接”
  5. Clic on "chercher" (search) to send a new message to server单击“chercher”(搜索)向服务器发送新消息
  6. Server got message, proceed it, then answer it with a new message服务器收到消息,继续处理,然后用一条新消息回答它
  7. Client receive message, and here the bug : buffer size is smaller than number of received byte客户端接收消息,这里是错误缓冲区大小小于接收字节数
  8. Exception occurs发生异常

If you don't understand something (because i didn't translate all in english), please tell me.如果您有什么不明白的地方(因为我没有全部翻译成英文),请告诉我。 Thank you谢谢

To sum it up, the solution is:总结一下,解决方法是:

  • Avoid global variable as buffer when possible.尽可能避免将全局变量用作缓冲区。 But here, I need global variable as buffer for receiving data但是在这里,我需要全局变量作为接收数据的缓冲区
  • Never use the same buffer for receive and send message (bytes)永远不要使用相同的缓冲区来接收和发送消息(字节)
  • Use local variable to send at least, and here, I use the global variable buffer to receive至少使用局部变量发送,这里我使用全局变量缓冲区来接收

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM