异步聊天服务器缓冲区问题

时间:2014-02-06 00:05:50

标签: c# multithreading sockets asynchronous chat

有人可以帮我解决这个问题......我一整天都在苦苦挣扎。

所以我正在尝试学习异步套接字,这是给我带来麻烦的东西。

问题基本上就是我与加入聊天室名字的人一起更新ListBox的方式:

Chat Window

基本上我正在做的是让每个客户端在加入服务器时发送“!! addlist [nickname]”

它并不理想,因为它不检查重复等,但现在我只想知道为什么它不起作用。 每当有人添加他们以前从未见过的名字时,他们也会发送“!! addlist [nick]”

通过这种方式,每次有人加入时,都应该为每个人更新列表。 问题似乎是所有客户端同时开始通信并且它会干扰缓冲区。

我已经尝试为每个客户端使用单独的缓冲区,这不是问题。 我尝试过使用 lock(),但这似乎也没有用。

基本上发生的事情是缓冲区似乎被截断;在同一个缓冲区中有来自两个不同人的数据。

请告诉我我在缓冲区或客户端的错误:

请注意,异步套接字使用的是Send而不是BeginSend。 我已经尝试了两种方法,但它们遇到了同样的问题...所以它可能是客户端的?

public partial class Login : Form
{
    private ChatWindow cw;
    private Socket serverSocket;
    private List<Socket> socketList;
    private byte[] buffer;
    private bool isHost;
    private bool isClosing;

    public void startListening()
    {
        try
        {
            this.isHost = true;                                                         //We're hosting this server
            cw.callingForm = this;                                                      //Give ChatForm the login form (this) [that acts as the server]
            cw.Show();                                                                  //Show ChatForm
            cw.isHost = true;                                                           //Tell ChatForm it is the host (for display purposes)
            this.Hide();                                                                //And hide the login form
            serverSocket.Bind(new IPEndPoint(IPAddress.Any, int.Parse(portBox.Text)));  //Bind to our local address
            serverSocket.Listen(1);                                                     //And start listening
            serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);          //When someone connects, begin the async callback
            cw.connectTo("127.0.0.1", int.Parse(portBox.Text), nicknameBox.Text);       //And have ChatForm connect to the server
        }
        catch (Exception) { /*MessageBox.Show("Error:\n\n" + e.ToString());*/ }           //Let us know if we ran into any errors
    }

    public void AcceptCallback(IAsyncResult AR)
    {
        try
        {
            Socket s = serverSocket.EndAccept(AR);                                                              //When someone connects, accept the new socket
            socketList.Add(s);                                                                                  //Add it to our list of clients
            s.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), s);  //Begin the async receive method using our buffer
            serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);                                  //And start accepting new connections
        }
        catch (Exception) {}
    }

    public void ReceiveCallback(IAsyncResult AR)                //When a message from a client is received
    {
        try
        {
            if (isClosing)
                return;

            Socket s = (Socket)AR.AsyncState;                   //Get the socket from our IAsyncResult

            int received = s.EndReceive(AR);                    //Read the number of bytes received (*need to add locking code here*)
            byte[] dbuf = new byte[received];                   //Create a temporary buffer to store just what was received so we don't have extra data

            Array.Copy(buffer, dbuf, received);                 //Copy the received data from our buffer to our temporary buffer

            foreach (Socket client in socketList)               //For each client that is connected
            {
                try
                {
                    if (client != (Socket)AR.AsyncState)        //If this isn't the same client that just sent a message (*client handles displaying these*)
                        client.Send(dbuf);                      //Send the message to the client
                }
                catch (Exception) {  }
            }                                                  //Start receiving new data again
            s.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), s);
        }
        catch (Exception) { /*cw.output("\n\nError:\n\n" + e.ToString());*/ }
    }

    public void SendCallback(IAsyncResult AR)
    {
        try
        {
            Socket s = (Socket)AR.AsyncState;
            s.EndSend(AR);
        }
        catch (Exception) { /*cw.output("\n\nError:\n\n" + e.ToString());*/ }
    }

以下是客户端:

    public void getData()
    {
        try
        {
            byte[] buf = new byte[1024];
            string message = "";
            while(isConnected)
            {
                Array.Clear(buf, 0, buf.Length);
                message = "";
                clientSocket.Receive(buf, buf.Length, SocketFlags.None);
                message = Encoding.ASCII.GetString(buf);
                if (message.StartsWith("!!addlist"))
                {
                    message = message.Replace("!!addlist", "");
                    string userNick = message.Trim();
                    if (!namesBox.Items.Contains(userNick))
                    {
                        addNick(userNick.Trim());
                    }
                    continue;
                }
                else if (message.StartsWith("!!removelist"))
                {
                    message = message.Replace("!!removelist", "");
                    string userNick = message.Trim();
                    removeNick(userNick);
                    output("Someone left the room: " + userNick);
                    continue;
                }
                else if (!namesBox.Items.Contains(message.Substring(0, message.IndexOf(":"))))
                {
                    addNick(message.Substring(0, message.IndexOf(":")).Trim()); //So they at least get added when they send a message
                }
                output(message);
            }
        }
        catch (Exception)
        {
            output("\n\nConnection to the server lost.");
            isConnected = false;
        }
    }

这是我的延迟addNick功能似乎可以解决一些问题?

    public void addNick(string n)
    {
        if (n.Contains(" ")) //No Spaces... such a headache
            return;
        if (n.Contains(":"))
            return;
        bool shouldAdd = true;
        n = n.Trim();
        for (int x = namesBox.Items.Count - 1; x >= 0; --x)
            if (namesBox.Items[x].ToString().Contains(n))
                shouldAdd = false;
        if (shouldAdd)
        {
            namesBox.Items.Add(n);
            output("Someone new joined the room: " + n);
            sendRaw("!!addlist " + nickName);
        }
    }

我认为问题是有些数据包被跳过了?

也许在接收之后客户端中的代码太多而在再次被调用之前?

我应该为每条消息创建一个单独的线程,以便接收不断运行吗? (哑)

我的客户端是否应该使用Async接收和发送?

我有一种感觉就是答案^

通过我所做的所有检查,我设法清理了重复的名称问题...但我经常收到带有空格的消息和来自其他客户端的部分消息。

Not Receiving All The Names

1 个答案:

答案 0 :(得分:1)

好的,经过长时间的捣乱,我相对稳定了。

对于初学者,我添加了以下状态对象:

public class StateObject
{
    public Socket workSocket = null;
    public const int BufferSize = 1024;
    public byte[] buffer = new byte[BufferSize];
    public StringBuilder sb = new StringBuilder();
    public bool newConnection = true;
}

这样可以轻松跟踪每个连接,并为每个连接提供自己的缓冲区。

我做的第二件事是在每条消息中寻找新的一行。 我不是在原始代码中寻找这个,我相信这是大多数问题的根源。

我还负责处理服务器的用户名管理;我应该从一开始就做的事情。

以下是当前的服务器代码:

这段代码绝不是完美的,我不断发现新的错误,我试图打破它。我将继续搞乱它一段时间,但此刻,它似乎工作正常。

public partial class Login : Form
{
    private ChatWindow cw;
    private Socket serverSocket;
    private List<Socket> socketList;
    private byte[] buffer;
    private bool isHost;
    private bool isClosing;
    private ListBox usernames;

    public Login()
    {
        InitializeComponent();
    }

    private void Login_Load(object sender, EventArgs e)
    {
        ipLabel.Text = getLocalIP();
        cw = new ChatWindow();
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socketList = new List<Socket>();
        buffer = new byte[1024];
        isClosing = false;
        usernames = new ListBox();
    }

    public string getLocalIP()
    {
        return Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString();
    }

    private void joinButton_Click(object sender, EventArgs e)
    {
        try
        {
            int tryPort = 0;
            this.isHost = false;
            cw.callingForm = this;
            if (ipBox.Text == "" || portBox.Text == "" || nicknameBox.Text == "" || !int.TryParse(portBox.Text.ToString(), out tryPort))
            {
                MessageBox.Show("You must enter an IP Address, Port, and Nickname to connect to a server.", "Missing Info");
                return;
            }
            this.Hide();
            cw.Show();
            cw.connectTo(ipBox.Text, int.Parse(portBox.Text), nicknameBox.Text);
        }
        catch(Exception otheree) {
            MessageBox.Show("Error:\n\n" + otheree.ToString(),"Error connecting...");
            cw.Hide();
            this.Show();
        }
    }

    private void hostButton_Click(object sender, EventArgs e)
    {
        int tryPort = 0;
        if (portBox.Text == "" || nicknameBox.Text == "" || !int.TryParse(portBox.Text.ToString(), out tryPort)) {
            MessageBox.Show("You must enter a Port and Nickname to host a server.", "Missing Info");
            return;
        }
        startListening();
    }

    public void startListening()
    {
        try
        {
            this.isHost = true;                                                         //We're hosting this server
            cw.callingForm = this;                                                      //Give ChatForm the login form (this) [that acts as the server]
            cw.Show();                                                                  //Show ChatForm
            cw.isHost = true;                                                           //Tell ChatForm it is the host (for display purposes)
            this.Hide();                                                                //And hide the login form
            serverSocket.Bind(new IPEndPoint(IPAddress.Any, int.Parse(portBox.Text)));  //Bind to our local address
            serverSocket.Listen(1);                                                     //And start listening
            serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);          //When someone connects, begin the async callback
            cw.connectTo("127.0.0.1", int.Parse(portBox.Text), nicknameBox.Text);       //And have ChatForm connect to the server
        }
        catch (Exception) {}
    }

    public void AcceptCallback(IAsyncResult AR)
    {
        try
        {
            StateObject state = new StateObject();
            state.workSocket = serverSocket.EndAccept(AR);
            socketList.Add(state.workSocket);
            state.workSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
            serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        }
        catch (Exception) {}
    }

    public void ReceiveCallback(IAsyncResult AR)
    {
        try
        {
            if (isClosing)
                return;

            StateObject state = (StateObject)AR.AsyncState;
            Socket s = state.workSocket;
            String content = "";
            int received = s.EndReceive(AR);

            if(received > 0)
                state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, received));

            content = state.sb.ToString();

            if (content.IndexOf(Environment.NewLine) > -1) //If we've received the end of the message
            {

                if (content.StartsWith("!!addlist") && state.newConnection)
                {
                    state.newConnection = false;
                    content = content.Replace("!!addlist", "");
                    string userNick = content.Trim();
                    if (isHost && userNick.StartsWith("!"))
                        userNick = userNick.Replace("!", "");
                    userNick = userNick.Trim();
                    if (userNick.StartsWith("!") || userNick == string.Empty || usernames.Items.IndexOf(userNick) > -1)
                    {
                        //Invalid Username :c get dropped
                        s.Send(Encoding.ASCII.GetBytes("Invalid Username/In Use - Sorry :("));
                        s.Shutdown(SocketShutdown.Both);
                        s.Disconnect(false);
                        s.Close();
                        socketList.Remove(s);
                        return;
                    }
                    usernames.Items.Add(userNick);
                    foreach (string name in usernames.Items)
                    {
                        if (name.IndexOf(userNick) < 0)
                        {
                            s.Send(Encoding.ASCII.GetBytes("!!addlist " + name + "\r\n"));
                            Thread.Sleep(10); //such a hack... ugh it annoys me that this works
                        }
                    }
                    foreach (Socket client in socketList)
                    {
                        try
                        {
                            if (client != s)
                                client.Send(Encoding.ASCII.GetBytes("!!addlist " + userNick + "\r\n"));
                        }
                        catch (Exception) { }
                    }
                }
                else if (content.StartsWith("!!removelist") && !state.newConnection)
                {
                    content = content.Replace("!!removelist", "");
                    string userNick = content.Trim();
                    usernames.Items.Remove(userNick);
                    foreach (Socket client in socketList)
                    {
                        try
                        {
                            if (client != s)
                                client.Send(Encoding.ASCII.GetBytes("!!removelist " + userNick + "\r\n"));
                        }
                        catch (Exception) { }
                    }
                }
                else if (state.newConnection) //if they don't give their name and try to send data, just drop.
                {
                    s.Shutdown(SocketShutdown.Both);
                    s.Disconnect(false);
                    s.Close();
                    socketList.Remove(s);
                    return;
                }
                else
                {
                    foreach (Socket client in socketList)
                    {
                        try
                        {
                            if (client != s)
                                client.Send(System.Text.Encoding.ASCII.GetBytes(content));
                        }
                        catch (Exception) { }
                    }
                }
            }
            Array.Clear(state.buffer, 0, StateObject.BufferSize);
            state.sb.Clear();
            s.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
        }
        catch (Exception) {
            socketList.Remove(((StateObject)AR.AsyncState).workSocket);
        }
    }
    public void SendCallback(IAsyncResult AR)
    {
        try
        {
            StateObject state = (StateObject)AR.AsyncState;
            state.workSocket.EndSend(AR);
        }
        catch (Exception) {}
    }
    private void Login_FormClosed(object sender, FormClosedEventArgs e)
    {
        try
        {
            this.isClosing = true;
            if (this.isHost)
            {
                foreach (Socket c in socketList)
                {
                    if (c.Connected)
                    {
                        c.Close();
                    }
                }
                serverSocket.Shutdown(SocketShutdown.Both);
                serverSocket.Close();
                serverSocket = null;
                serverSocket.Dispose();
            }
            socketList.Clear();
        }
        catch (Exception) { }
        finally
        {
            Application.Exit();
        }
    }
}
public class StateObject
{
    public Socket workSocket = null;
    public const int BufferSize = 1024;
    public byte[] buffer = new byte[BufferSize];
    public StringBuilder sb = new StringBuilder();
    public bool newConnection = true;
}

客户端代码(正在进行中):

public partial class ChatWindow : Form
{
    private Socket clientSocket;
    private Thread chatThread;
    private string ipAddress;
    private int port;
    private bool isConnected;
    private string nickName;
    public bool isHost;
    public Login callingForm;

    private static object conLock = new object();

    public ChatWindow()
    {
        InitializeComponent();
        isConnected = false;
        isHost = false;
    }

    public string getIP() {
        return Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString();
    }

    public void displayError(string err)
    {
        output(Environment.NewLine + Environment.NewLine + err + Environment.NewLine);
    }

    public void op(string s)
    {
        try
        {
            lock (conLock)
            {
                chatBox.Text += s;
            }
        }
        catch (Exception) { }
    }

    public void connectTo(string ip, int p, string n) {
        try
        {
            this.Text = "Trying to connect to " + ip + ":" + p + "...";
            this.ipAddress = ip;
            this.port = p;
            this.nickName = n;

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

            if (!isHost)
            {
                op("Connecting to " + ipAddress + ":" + port + "...");
            }
            else
            {
                output("Listening on " + getIP() + ":" + port + "...");
            }

            clientSocket.Connect(ipAddress, port);

            isConnected = true;

            if (!isHost)
            {
                this.Text = "Connected to " + ipAddress + ":" + port + " - Nickname: " + nickName;
                output("Connected!");
            }
            else
            {
                this.Text = "Hosting on " + getIP() + ":" + port + " - Nickname: " + nickName;
            }

            chatThread = new Thread(new ThreadStart(getData));
            chatThread.Start();

            nickName = nickName.Replace(" ", "");
            nickName = nickName.Replace(":", "");
            if(nickName.StartsWith("!"))
                nickName = nickName.Replace("!", "");
            namesBox.Items.Add(nickName);

            sendRaw("!!addlist " + nickName);
        }
        catch (ThreadAbortException)
        {
            //do nothing; probably closing chat window
        }
        catch (Exception e)
        {
            if (!isConnected)
            {
                this.Hide();
                callingForm.Show();
                clearText();
                MessageBox.Show("Error:\n\n" + e.ToString(), "Error connecting to remote host");
            }
        }
    }

    public void removeNick(string n)
    {
        if (namesBox.Items.Count <= 0)
            return;
        for (int x = namesBox.Items.Count - 1; x >= 0; --x)
            if (namesBox.Items[x].ToString().Contains(n))
                namesBox.Items.RemoveAt(x);
    }

    public void clearText()
    {
        try
        {
            lock (conLock)
            {
                chatBox.Text = "";
            }
        }
        catch (Exception) { }
    }

    public void addNick(string n)
    {
        if (n.Contains(" ")) //No Spaces... such a headache
            return;
        if (n.Contains(":"))
            return;
        bool shouldAdd = true;
        n = n.Trim();
        for (int x = namesBox.Items.Count - 1; x >= 0; --x)
            if (namesBox.Items[x].ToString().Contains(n))
                shouldAdd = false;
        if (shouldAdd)
        {
            namesBox.Items.Add(n);
            output("Someone new joined the room: " + n);
            //sendRaw("!!addlist " + nickName);
        }
    }

    public void addNickNoMessage(string n)
    {
        if (n.Contains(" ")) //No Spaces... such a headache
            return;
        if (n.Contains(":"))
            return;
        bool shouldAdd = true;
        n = n.Trim();
        for (int x = namesBox.Items.Count - 1; x >= 0; --x)
            if (namesBox.Items[x].ToString().Contains(n))
                shouldAdd = false;
        if (shouldAdd)
        {
            namesBox.Items.Add(n);
            //sendRaw("!!addlist " + nickName);
        }
    }

    public void getData()
    {
        try
        {
            byte[] buf = new byte[1024];
            string message = "";
            while(isConnected)
            {
                Array.Clear(buf, 0, buf.Length);
                message = "";
                int gotData = clientSocket.Receive(buf, buf.Length, SocketFlags.None);
                if (gotData == 0)
                    throw new Exception("I swear, this was working before but isn't anymore...");
                message = Encoding.ASCII.GetString(buf);
                if (message.StartsWith("!!addlist"))
                {
                    message = message.Replace("!!addlist", "");
                    string userNick = message.Trim();
                    if(!namesBox.Items.Contains(userNick))
                    {
                        addNick(userNick);
                    }
                    continue;
                }
                else if (message.StartsWith("!!removelist"))
                {
                    message = message.Replace("!!removelist", "");
                    string userNick = message.Trim();
                    removeNick(userNick);
                    output("Someone left the room: " + userNick);
                    continue;
                }
                output(message);
            }
        }
        catch (Exception)
        {
            isConnected = false;
            output(Environment.NewLine + "Connection to the server lost.");
        }
    }

    public void output(string s)
    {
        try
        {
            lock (conLock)
            {
                chatBox.Text += s + Environment.NewLine;
            }
        }
        catch (Exception) { }
    }

    private void ChatWindow_FormClosed(object sender, FormClosedEventArgs e)
    {
        try
        {
            if(isConnected)
                sendRaw("!!removelist " + nickName);
            isConnected = false;
            clientSocket.Shutdown(SocketShutdown.Receive);
            if (chatThread.IsAlive)
                chatThread.Abort();
            callingForm.Close();
        }
        catch (Exception) { }
    }

    private void sendButton_Click(object sender, EventArgs e)
    {
        if(isConnected)
            send(sendBox.Text);
    }

    private void sendBox_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Enter)
        {
            if (isConnected)
            {
                if (sendBox.Text != "")
                {
                    send(sendBox.Text);
                    sendBox.SelectAll();
                    e.SuppressKeyPress = true;
                    e.Handled = true;
                }
            }
        }
    }

    private void send(string t) {
        try
        {
            byte[] data = System.Text.Encoding.ASCII.GetBytes(nickName + ": " + t + "\r\n");
            clientSocket.Send(data);
            output(nickName + ": " + t);
        }
        catch (Exception e)
        {
            displayError(e.ToString());
        }
    }

    private void sendRaw(string t)
    {
        try
        {
            byte[] data = System.Text.Encoding.ASCII.GetBytes(t + "\r\n");
            clientSocket.Send(data);
        }
        catch (Exception e)
        {
            displayError(e.ToString());
        }
    }

    private void chatBox_TextChanged(object sender, EventArgs e)
    {
        chatBox.SelectionStart = chatBox.Text.Length;
        chatBox.ScrollToCaret();
    }

    private void sendBox_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Enter)
            e.SuppressKeyPress = true;
    }
}

待办事项:

添加调用,更多代理,执行更多QA并找出打破它的原因。 此外,我认为由于客户端添加列表功能在读取循环中,仍有可能丢包。我相信这就是为什么在名称填充的服务器回调中使用Thread.Sleep(10)的“糟糕的黑客”是一个问题。

我认为最好将命令传递给另一个线程,同时继续读取或让客户端告诉服务器它已经准备好了另一个名字。

否则,名称更新期间可能会丢失一些数据。

另一件事是,正如上面的评论中所说,在更新UI对象(聊天框和列表框)时,应该使用代理。我为这些编写了代码,但最终删除了它,因为没有明显的变化,我想保持简单。

在向聊天框输出文字时,我仍然使用对象锁定,但那里没有明显的区别。

代码应该添加,因为不使用代理可能会有问题,但我确实在无限循环的更新中抓住了聊天框而没有问题。

我尝试用telnet打破它并且成功了所以我向StateObject添加了一个newConnection属性,以确保每个客户端只能发送一次“!! addlist”。

当然,有其他方法通过创建一个连续加入和离开的客户端滥用服务器,所以最终我可能最终将!! removelist处理传递给服务器而不是将其留给客户端。