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

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

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

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

聊天窗口

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

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

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

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

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

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

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

public partial class Login : Form { private ChatWindow cw; private Socket serverSocket; private List 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; } } 

这是我的延迟addNickfunction似乎修复了一些东西?

  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接收和发送吗?

我有一种感觉就是答案^

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

没有收到所有的名字

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

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

 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 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(); 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并找出打破它的原因。 此外,我相信由于客户端添加列表function在读取循环中,仍有可能丢包。 我相信这就是为什么在名称填充的服务器回调中使用Thread.Sleep(10)的“糟糕的黑客”是一个问题。

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

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

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

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

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

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

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