我有一个服务器应用程序,它只需要一半的时间就可以获取数据。 Why/how 会发生这种情况吗?我该如何解决?
I have a server application that gets data exactly half the time. Why/how does this happen and how do I fix it?
所以我的服务器和聊天客户端是由 2 个不同的 C# TCP tutorials.You 可能识别出 1 个,如果不是两个,我已经对它们进行了自己的修改以适应我自己的风格。当我尝试这两种方法时,它们都工作得很好,损失为 0,但我的版本恰好有 50% 的损失率。
例如:
1. 客户端连接:收到数据
2.客户端发送文本:无数据
3.客户端发送文本:收到数据
4.客户端发送文本:无数据
服务器代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
namespace WindowsFormsApplication2
{
class Server
{
private TcpListener tcpListener;
private Thread listenThread;
public Hashtable clientsList = new Hashtable();
private System.Windows.Forms.TextBox output;
private delegate void ObjectDelegate(String text);
private ObjectDelegate del;
public Server(System.Windows.Forms.TextBox setOut)
{
this.tcpListener = new TcpListener(IPAddress.Any, 8888);
this.listenThread = new Thread(new ThreadStart(ListenForClients));
this.listenThread.IsBackground = true;
this.listenThread.Start();
output = setOut;
del = new ObjectDelegate(outputTextToServer);
}
private void ListenForClients()
{
this.tcpListener.Start();
while (true)
{
//blocks until a client has connected to the server
TcpClient client = this.tcpListener.AcceptTcpClient();
//create a thread to handle communication
//with connected client
addClient(client);
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
clientThread.IsBackground = true;
clientThread.Start(client);
}
}
private void HandleClientComm(object client)
{
TcpClient tcpClient = (TcpClient)client;
NetworkStream clientStream = tcpClient.GetStream();
byte[] message = new byte[4096];
int bytesRead;
while (true)
{
bytesRead = 0;
try
{
//blocks until a client sends a message
bytesRead = clientStream.Read(message, 0, 4096);
}
catch
{
//a socket error has occured
break;
}
if (bytesRead == 0)
{
//the client has disconnected from the server
break;
}
//message has successfully been received
String text = getData(clientStream);
del.Invoke(text); //Used for Cross Threading & sending text to server output
//if filter(text)
sendMessage(tcpClient);
//System.Diagnostics.Debug.WriteLine(text); //Spit it out in the console
}
tcpClient.Close();
}
private void outputTextToServer(String text)
{
if (output.InvokeRequired)
{
// we then create the delegate again
// if you've made it global then you won't need to do this
ObjectDelegate method = new ObjectDelegate(outputTextToServer);
// we then simply invoke it and return
output.Invoke(method, text);
return;
}
output.AppendText(Environment.NewLine + " >> " + text);
}
private String getData(NetworkStream stream)
{
int newData;
byte[] message = new byte[4096];
ASCIIEncoding encoder = new ASCIIEncoding();
newData = stream.Read(message, 0, 4096);
String text = encoder.GetString(message, 0, newData); //Translate it into text
text = text.Substring(0, text.IndexOf("$")); //Here comes the money
return text;
}
private void addClient(object client)
{
TcpClient tcpClient = (TcpClient)client;
NetworkStream clientStream = tcpClient.GetStream();
String dataFromClient = getData(clientStream);
if (clientsList.Contains(dataFromClient))
{
Console.WriteLine(dataFromClient + " Tried to join chat room, but " + dataFromClient + " is already in use");
//broadcast("A doppleganger of " + dataFromClient + " has attempted to join!", dataFromClient, false);
}
else
{
clientsList.Add(dataFromClient, tcpClient);
//broadcast(dataFromClient + " Joined ", dataFromClient, false);
del.Invoke(dataFromClient + " Joined chat room ");
//handleClinet client = new handleClinet();
//client.startClient(clientSocket, dataFromClient, clientsList);
}
}
private Boolean connectionAlive(NetworkStream stream)
{
byte[] message = new byte[4096];
int bytesRead = 0;
try
{
//blocks until a client sends a message
bytesRead = stream.Read(message, 0, 4096);
}
catch
{
//a socket error has occured
return false;
}
if (bytesRead == 0)
{
//the client has disconnected from the server
//clientsList.Remove
return false;
}
return true;
}
private void sendMessage(TcpClient client)
{
NetworkStream clientStream = client.GetStream();
ASCIIEncoding encoder = new ASCIIEncoding();
byte[] buffer = encoder.GetBytes("Hello Client!");
clientStream.Write(buffer, 0, buffer.Length);
clientStream.Flush();
}
}
}
这是我的客户端代码
using System;
using System.Windows.Forms;
using System.Text;
using System.Net.Sockets;
using System.Threading;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public delegate void newDelegate();
public newDelegate myDelegate;
System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient();
NetworkStream serverStream = default(NetworkStream);
string readData = null;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
newMsg();
}
private void newMsg()
{
byte[] outStream = System.Text.Encoding.ASCII.GetBytes(textBox2.Text + "$");
serverStream.Write(outStream, 0, outStream.Length);
serverStream.Flush();
textBox2.Text = "";
}
private void button2_Click(object sender, EventArgs e)
{
readData = "Connecting to Chat Server ...";
msg();
clientSocket.Connect(txtIP.Text, int.Parse(txtPort.Text));
serverStream = clientSocket.GetStream();
byte[] outStream = System.Text.Encoding.ASCII.GetBytes(txtName.Text + "$");
serverStream.Write(outStream, 0, outStream.Length);
serverStream.Flush();
myDelegate = new newDelegate(disconnect);
Thread ctThread = new Thread(getMessage);
ctThread.IsBackground = true;
ctThread.Start();
button2.Enabled = false;
}
private void getMessage()
{
while (true)
{
serverStream = clientSocket.GetStream();
int buffSize = 0;
byte[] inStream = new byte[clientSocket.ReceiveBufferSize];
buffSize = clientSocket.ReceiveBufferSize;
try
{
serverStream.Read(inStream, 0, buffSize);
string returndata = System.Text.Encoding.ASCII.GetString(inStream);
readData = "" + returndata;
msg();
}
catch
{
Invoke(myDelegate);
return;
}
}
}
private void disconnect()
{
button2.Enabled = true;
}
private void msg()
{
if (this.InvokeRequired)
this.Invoke(new MethodInvoker(msg));
else
textBox1.AppendText(Environment.NewLine + " >> " + readData);
//textBox1.Text = textBox1.Text + Environment.NewLine + " >> " + readData;
}
private void textBox2_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
newMsg();
}
}
private void cmdHost_Click(object sender, EventArgs e)
{
Server serv = new Server(txtLog);
}
}
}
这段代码显然是一个正在进行的工作,对于混乱提前表示歉意。也欢迎对代码提出任何其他建议。
好的,开始有点长了。
您的代码中存在多个错误。从服务器代码开始:
- 正如 Damien 指出的那样,您正在尝试阅读每个 "message" 两次 - 首先是
HandleClientComm
,然后是 getData
。流不再有原始数据,因此您只是完全丢弃其中一个读取(因此可疑的 50% "packet" 损失)
- 稍后,在
getData
中,您丢弃了流中第一个 $
之后的所有数据。虽然这显然是一种处理消息框架的尝试(因为 TCP 是一种基于流的协议,而不是基于消息的协议),但这是一种愚蠢的做法——您正在丢弃数据。这在您的测试中没有显示的原因是 1) Windows 对待本地 TCP 与远程 TCP 非常不同,2) 您实际上需要能够足够快地发送两条消息以使它们 "blend" 一起在流中。这意味着要么在大约 200 毫秒内发送两条消息(默认 TCP 缓冲),要么在读取时阻塞。
- 您保持
Flush
网络流。这实际上并没有做任何事情,即使它做了,你也不想那样做。
connectionAlive
从共享套接字读取 - 这总是一个坏主意。永远不要超过一个 reader - 多个 reader 不适用于基于流的协议。您似乎没有在示例代码中使用它,但请注意不要尝试这样做。
- 注释掉的
clientList.Remove
当然是共享字段的跨线程访问了。如果你想这样做,你必须确保并发访问是安全的 - 通过使用 ConcurrentDictionary
而不是 HashSet
,或者通过 lock
ing 围绕每个写 and 读取 clientList
.
- 您希望在一个
Read
中收到完整的消息。这对于一个简单的聊天客户端来说可能没问题,但无论如何它都是糟糕的 TCP - 你需要阅读直到找到你的消息终止符。如果我发送的消息足够大,您的代码将在 text.IndexOf("$")
. 上死掉
还有很多 "style" 问题,尽管这不是代码审查,所以让我只列出一些:使用古老的技术、服务器的同步套接字、将多线程代码与 GUI 混合随意。这主要是关于可维护性和性能,而不是正确性。
现在,客户端有点简单了:
- 同样,不要
Flush
网络流。
- 如无必要,请不要使用后台线程。只需确保正确终止连接等即可。
- 断开实际上应该断开。这并不难,只需关闭
TcpClient
.
readData = "" + returndata
应该做什么?太傻了。
- 您忽略了
Read
的 return 值。这意味着您不知道您读取了多少字节的数据 - 这意味着您的 returnData
字符串实际上包含消息,后跟几千个 [=28=]
字符。您在输出中看不到它们的唯一原因是因为大多数 Windows 使用 [=28=]
作为字符串终止符 ("it made sense at the time")。 .NET 没有。
- 同样,
Read
需要一次完整的消息。与服务器不同,这不会使客户端崩溃,但您的代码的行为会有所不同(例如,一个额外的 \r\n >>
,即使它不是单独的消息。
服务器的样式问题也适用于此。
作为旁注,我最近制作了一个简化的网络示例,它使用更现代的技术处理简单的聊天客户端-服务器系统——使用基于 await
的异步 I/O 而不是例如,多线程。它不是可用于生产的代码,但它应该非常清楚地展示想法和意图(我还建议查看第一个示例,"HTTP-like TCP communication")。您可以在此处找到完整的源代码 - Networking Part 2.
所以我的服务器和聊天客户端是由 2 个不同的 C# TCP tutorials.You 可能识别出 1 个,如果不是两个,我已经对它们进行了自己的修改以适应我自己的风格。当我尝试这两种方法时,它们都工作得很好,损失为 0,但我的版本恰好有 50% 的损失率。 例如: 1. 客户端连接:收到数据 2.客户端发送文本:无数据 3.客户端发送文本:收到数据 4.客户端发送文本:无数据 服务器代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
namespace WindowsFormsApplication2
{
class Server
{
private TcpListener tcpListener;
private Thread listenThread;
public Hashtable clientsList = new Hashtable();
private System.Windows.Forms.TextBox output;
private delegate void ObjectDelegate(String text);
private ObjectDelegate del;
public Server(System.Windows.Forms.TextBox setOut)
{
this.tcpListener = new TcpListener(IPAddress.Any, 8888);
this.listenThread = new Thread(new ThreadStart(ListenForClients));
this.listenThread.IsBackground = true;
this.listenThread.Start();
output = setOut;
del = new ObjectDelegate(outputTextToServer);
}
private void ListenForClients()
{
this.tcpListener.Start();
while (true)
{
//blocks until a client has connected to the server
TcpClient client = this.tcpListener.AcceptTcpClient();
//create a thread to handle communication
//with connected client
addClient(client);
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
clientThread.IsBackground = true;
clientThread.Start(client);
}
}
private void HandleClientComm(object client)
{
TcpClient tcpClient = (TcpClient)client;
NetworkStream clientStream = tcpClient.GetStream();
byte[] message = new byte[4096];
int bytesRead;
while (true)
{
bytesRead = 0;
try
{
//blocks until a client sends a message
bytesRead = clientStream.Read(message, 0, 4096);
}
catch
{
//a socket error has occured
break;
}
if (bytesRead == 0)
{
//the client has disconnected from the server
break;
}
//message has successfully been received
String text = getData(clientStream);
del.Invoke(text); //Used for Cross Threading & sending text to server output
//if filter(text)
sendMessage(tcpClient);
//System.Diagnostics.Debug.WriteLine(text); //Spit it out in the console
}
tcpClient.Close();
}
private void outputTextToServer(String text)
{
if (output.InvokeRequired)
{
// we then create the delegate again
// if you've made it global then you won't need to do this
ObjectDelegate method = new ObjectDelegate(outputTextToServer);
// we then simply invoke it and return
output.Invoke(method, text);
return;
}
output.AppendText(Environment.NewLine + " >> " + text);
}
private String getData(NetworkStream stream)
{
int newData;
byte[] message = new byte[4096];
ASCIIEncoding encoder = new ASCIIEncoding();
newData = stream.Read(message, 0, 4096);
String text = encoder.GetString(message, 0, newData); //Translate it into text
text = text.Substring(0, text.IndexOf("$")); //Here comes the money
return text;
}
private void addClient(object client)
{
TcpClient tcpClient = (TcpClient)client;
NetworkStream clientStream = tcpClient.GetStream();
String dataFromClient = getData(clientStream);
if (clientsList.Contains(dataFromClient))
{
Console.WriteLine(dataFromClient + " Tried to join chat room, but " + dataFromClient + " is already in use");
//broadcast("A doppleganger of " + dataFromClient + " has attempted to join!", dataFromClient, false);
}
else
{
clientsList.Add(dataFromClient, tcpClient);
//broadcast(dataFromClient + " Joined ", dataFromClient, false);
del.Invoke(dataFromClient + " Joined chat room ");
//handleClinet client = new handleClinet();
//client.startClient(clientSocket, dataFromClient, clientsList);
}
}
private Boolean connectionAlive(NetworkStream stream)
{
byte[] message = new byte[4096];
int bytesRead = 0;
try
{
//blocks until a client sends a message
bytesRead = stream.Read(message, 0, 4096);
}
catch
{
//a socket error has occured
return false;
}
if (bytesRead == 0)
{
//the client has disconnected from the server
//clientsList.Remove
return false;
}
return true;
}
private void sendMessage(TcpClient client)
{
NetworkStream clientStream = client.GetStream();
ASCIIEncoding encoder = new ASCIIEncoding();
byte[] buffer = encoder.GetBytes("Hello Client!");
clientStream.Write(buffer, 0, buffer.Length);
clientStream.Flush();
}
}
}
这是我的客户端代码
using System;
using System.Windows.Forms;
using System.Text;
using System.Net.Sockets;
using System.Threading;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public delegate void newDelegate();
public newDelegate myDelegate;
System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient();
NetworkStream serverStream = default(NetworkStream);
string readData = null;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
newMsg();
}
private void newMsg()
{
byte[] outStream = System.Text.Encoding.ASCII.GetBytes(textBox2.Text + "$");
serverStream.Write(outStream, 0, outStream.Length);
serverStream.Flush();
textBox2.Text = "";
}
private void button2_Click(object sender, EventArgs e)
{
readData = "Connecting to Chat Server ...";
msg();
clientSocket.Connect(txtIP.Text, int.Parse(txtPort.Text));
serverStream = clientSocket.GetStream();
byte[] outStream = System.Text.Encoding.ASCII.GetBytes(txtName.Text + "$");
serverStream.Write(outStream, 0, outStream.Length);
serverStream.Flush();
myDelegate = new newDelegate(disconnect);
Thread ctThread = new Thread(getMessage);
ctThread.IsBackground = true;
ctThread.Start();
button2.Enabled = false;
}
private void getMessage()
{
while (true)
{
serverStream = clientSocket.GetStream();
int buffSize = 0;
byte[] inStream = new byte[clientSocket.ReceiveBufferSize];
buffSize = clientSocket.ReceiveBufferSize;
try
{
serverStream.Read(inStream, 0, buffSize);
string returndata = System.Text.Encoding.ASCII.GetString(inStream);
readData = "" + returndata;
msg();
}
catch
{
Invoke(myDelegate);
return;
}
}
}
private void disconnect()
{
button2.Enabled = true;
}
private void msg()
{
if (this.InvokeRequired)
this.Invoke(new MethodInvoker(msg));
else
textBox1.AppendText(Environment.NewLine + " >> " + readData);
//textBox1.Text = textBox1.Text + Environment.NewLine + " >> " + readData;
}
private void textBox2_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
newMsg();
}
}
private void cmdHost_Click(object sender, EventArgs e)
{
Server serv = new Server(txtLog);
}
}
}
这段代码显然是一个正在进行的工作,对于混乱提前表示歉意。也欢迎对代码提出任何其他建议。
好的,开始有点长了。
您的代码中存在多个错误。从服务器代码开始:
- 正如 Damien 指出的那样,您正在尝试阅读每个 "message" 两次 - 首先是
HandleClientComm
,然后是getData
。流不再有原始数据,因此您只是完全丢弃其中一个读取(因此可疑的 50% "packet" 损失) - 稍后,在
getData
中,您丢弃了流中第一个$
之后的所有数据。虽然这显然是一种处理消息框架的尝试(因为 TCP 是一种基于流的协议,而不是基于消息的协议),但这是一种愚蠢的做法——您正在丢弃数据。这在您的测试中没有显示的原因是 1) Windows 对待本地 TCP 与远程 TCP 非常不同,2) 您实际上需要能够足够快地发送两条消息以使它们 "blend" 一起在流中。这意味着要么在大约 200 毫秒内发送两条消息(默认 TCP 缓冲),要么在读取时阻塞。 - 您保持
Flush
网络流。这实际上并没有做任何事情,即使它做了,你也不想那样做。 connectionAlive
从共享套接字读取 - 这总是一个坏主意。永远不要超过一个 reader - 多个 reader 不适用于基于流的协议。您似乎没有在示例代码中使用它,但请注意不要尝试这样做。- 注释掉的
clientList.Remove
当然是共享字段的跨线程访问了。如果你想这样做,你必须确保并发访问是安全的 - 通过使用ConcurrentDictionary
而不是HashSet
,或者通过lock
ing 围绕每个写 and 读取clientList
. - 您希望在一个
Read
中收到完整的消息。这对于一个简单的聊天客户端来说可能没问题,但无论如何它都是糟糕的 TCP - 你需要阅读直到找到你的消息终止符。如果我发送的消息足够大,您的代码将在text.IndexOf("$")
. 上死掉
还有很多 "style" 问题,尽管这不是代码审查,所以让我只列出一些:使用古老的技术、服务器的同步套接字、将多线程代码与 GUI 混合随意。这主要是关于可维护性和性能,而不是正确性。
现在,客户端有点简单了:
- 同样,不要
Flush
网络流。 - 如无必要,请不要使用后台线程。只需确保正确终止连接等即可。
- 断开实际上应该断开。这并不难,只需关闭
TcpClient
. readData = "" + returndata
应该做什么?太傻了。- 您忽略了
Read
的 return 值。这意味着您不知道您读取了多少字节的数据 - 这意味着您的returnData
字符串实际上包含消息,后跟几千个[=28=]
字符。您在输出中看不到它们的唯一原因是因为大多数 Windows 使用[=28=]
作为字符串终止符 ("it made sense at the time")。 .NET 没有。 - 同样,
Read
需要一次完整的消息。与服务器不同,这不会使客户端崩溃,但您的代码的行为会有所不同(例如,一个额外的\r\n >>
,即使它不是单独的消息。
服务器的样式问题也适用于此。
作为旁注,我最近制作了一个简化的网络示例,它使用更现代的技术处理简单的聊天客户端-服务器系统——使用基于 await
的异步 I/O 而不是例如,多线程。它不是可用于生产的代码,但它应该非常清楚地展示想法和意图(我还建议查看第一个示例,"HTTP-like TCP communication")。您可以在此处找到完整的源代码 - Networking Part 2.