TCP Socket在C#中接收数据错误
Posted
技术标签:
【中文标题】TCP Socket在C#中接收数据错误【英文标题】:TCP Socket recieves data wrong in C# 【发布时间】:2018-09-17 23:42:13 【问题描述】:我正在尝试将文件从一个 Socket 发送到另一个 Socket。套接字在两个不同的应用程序中运行,一个客户端应用程序和一个服务器应用程序。现在在同一台机器上进行测试时正在发生这种情况。当客户端准备好接收有关将发送给它的文件的第一个信息时,客户端首先发送到服务器,例如 2056 字节消息中的文件名和文件大小(以字节为单位)。然后它在每次准备好接收新的 2048 文件内容缓冲区时发送一条消息。
简而言之,这是每个文件的通信流程:
client -"CLIENT_READY_TO_RECEIVE_FILE_INFO"-------> server
client <-"File name+file size of file coming next"- server
client -"CLIENT_READY_TO_RECEIVE_CONTENT_BUFFER" -> server
client <-"2048 byte buffer of file content"-------- server
...the same flow is repeated until all files are sent to the client.
我的问题是客户端收到错误的文件信息消息,尽管我已经仔细检查了服务器是否正确发送了它。它们都使用 ASCII 编码。这是我的代码:
客户端代码(接收文件):
private void fetchFilesThreadMethod()
String localHostName = Dns.GetHostName();
IPAddress[] localIPs = Dns.GetHostAddresses(localHostName);
IPAddress localIP = localIPs[2];
int port = 1305;
IPEndPoint localEP = new IPEndPoint(localIP,port);
fetchFilesSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
fetchFilesSocket.Bind(localEP);
fetchFilesSocket.Listen(1);
fetchFilesSocket = fetchFilesSocket.Accept();
while (true)
byte[] receivedFileInfo = new byte[2056];
byte[] receivedFileCont = new byte[2048];
byte[] comMessage = new byte[100];
byte[] comMessageBytes;
String fileInfoStr = String.Empty;
String fileNameStr = String.Empty; ;
String fileExtStr = String.Empty;
String comMessageStr = String.Empty;
String fileSizeStr = String.Empty;
ulong fileSize = 0;
ulong lastBytesSize = 0;
comMessageStr = "CLIENT_READY_TO_RECEIVE_FILE_INFO";
comMessageBytes = Encoding.ASCII.GetBytes(comMessageStr);
for (int i = 0; i < comMessageBytes.Length; i++)
comMessage[i] = comMessageBytes[i];
Console.WriteLine("Bytes available to be flushed by client: " + fetchFilesSocket.Available);
if (fetchFilesSocket.Available > 0)
Console.WriteLine(fetchFilesSocket.Available + " bytes flushed by client.");
byte[] flusher = new byte[fetchFilesSocket.Available];
fetchFilesSocket.Receive(flusher, 0, fetchFilesSocket.Available, SocketFlags.None);
fetchFilesSocket.Send(comMessage, 0, 100, SocketFlags.None);
Console.WriteLine("Client sent ready to receive file info.");
fetchFilesSocket.Receive(receivedFileInfo,0,2056, SocketFlags.None);
fileInfoStr = Encoding.ASCII.GetString(receivedFileInfo);
Console.WriteLine("Received file info:" + fileInfoStr);
fileNameStr = fileInfoStr.Split(new String[] "ENDN" , StringSplitOptions.None)[0];
Console.WriteLine("Received file name:" + fileNameStr);
fileExtStr = fileNameStr.Split('.').Last();
Console.WriteLine("Received file extension:" + fileExtStr);
fileSizeStr = fileInfoStr.Split(new String[] "ENDS",StringSplitOptions.None)[0];
fileSizeStr = fileSizeStr.Split(new String[] "ENDN",StringSplitOptions.None).Last();
Console.WriteLine("File size string:" + fileSizeStr);
fileSize = Convert.ToUInt64(fileSizeStr,10);
Console.WriteLine("Received file size:" + fileSize);
lastBytesSize = fileSize % 2048;
ulong byteCount = 0;
bool keepReceiving = true;
ulong buffersReceived = 0;
while (keepReceiving)
comMessageStr = "CLIENT_READY_TO_RECEIVE_CONTENT_BUFFER";
comMessageBytes = Encoding.ASCII.GetBytes(comMessageStr);
for (int i = 0; i < comMessageBytes.Length; i++)
comMessage[i] = comMessageBytes[i];
fetchFilesSocket.Send(comMessage, 0, 100, SocketFlags.None);
Console.WriteLine("Console sent ready to receive buffer message.");
if (fileSize - byteCount >= 2048)
fetchFilesSocket.Receive(receivedFileCont, 0, 2048, SocketFlags.None);
buffersReceived++;
Console.WriteLine("Buffers received:" + buffersReceived);
byteCount = byteCount + 2048;
else
fetchFilesSocket.Receive(receivedFileCont, 0, 2048, SocketFlags.None); buffersReceived++;
byteCount = byteCount + 2048;
Console.WriteLine("Buffers received:" + buffersReceived);
keepReceiving = false;
Console.WriteLine("Bytes received " + byteCount + "/" + fileSize);
//Console.WriteLine("Received bytes in current file:" + byteCount);
Console.WriteLine("File received.");
服务器代码(发送文件):
private void fetchThreadMethod(Object commandArgs)
if (fetchSocket == null)
fetchSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
int port = 1304;
IPEndPoint fetchSocketEP = new IPEndPoint(localIP,port);
fetchSocket.Bind(fetchSocketEP);
if (!fetchSocket.Connected)
try
fetchSocket.Connect(remoteIP, 1305);
if (fetchSocket.Connected)
Console.WriteLine("Server fetch socket connected.");
catch(Exception e)
Console.WriteLine("Something went wrong when connecting the server fetch socket.");
FileCollector fCollector = new FileCollector();
String userName = Environment.GetEnvironmentVariable("USERPROFILE");
String targetDirectory = userName + "\\" + commandArgs;
Console.WriteLine("Path sent to file collector:" + targetDirectory);
fCollector.startFileCollector(targetDirectory);
List<FileNode> collectedFiles = fCollector.getCollectedFiles();
String comMessageStr = String.Empty;
foreach (FileNode fNode in collectedFiles)
comMessageStr = String.Empty;
byte[] sentFileInfo = new byte[2056];
byte[] sentFileCont = new byte[2048];
byte[] comMessage = new byte[100];
String fileName = fNode.getFileName();
String formattedFileInfo = fileName;
formattedFileInfo += "ENDN";
ulong fileSize = 0;
FileStream fStream = null;
try
fStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
FileInfo fInfo = new FileInfo(fileName);
fileSize = (ulong) fInfo.Length;
if (fileSize == 0)
continue;
formattedFileInfo += fileSize.ToString() + "ENDS";
catch (Exception e)
Console.WriteLine("Could not read from file:" + fileName);
deniedAccessFiles.Add(fileName);
continue;
byte[] fileInfoBytes = Encoding.ASCII.GetBytes(formattedFileInfo);
for (int i = 0; i < fileInfoBytes.Length; i++)
sentFileInfo[i] = fileInfoBytes[i];
while (!comMessageStr.Equals("CLIENT_READY_TO_RECEIVE_FILE_INFO"))
Console.WriteLine("Server waiting for file info ready message from client.");
fetchSocket.Receive(comMessage,0,100,SocketFlags.None);
comMessageStr = Encoding.ASCII.GetString(comMessage);
comMessageStr = comMessageStr.Substring(0,33);
Console.WriteLine("Received parsed message from client:" + comMessageStr);
Console.WriteLine("Server received file info ready message from client.");
comMessageStr = String.Empty;
Console.WriteLine("formattedFileInfo:" + formattedFileInfo);
Console.WriteLine("Sent file info:" + Encoding.ASCII.GetString(sentFileInfo));
fetchSocket.Send(sentFileInfo, 0, 2056, SocketFlags.None);
int readByte = 0;
ulong byteCount = 0;
ulong buffersSent = 0;
while (readByte != -1)
if (byteCount == 2048)
while (!comMessageStr.Equals("CLIENT_READY_TO_RECEIVE_CONTENT_BUFFER"))
Console.WriteLine("Server waiting for ready for buffer message from client.");
fetchSocket.Receive(comMessage, 100, SocketFlags.None);
comMessageStr = Encoding.ASCII.GetString(comMessage);
comMessageStr = comMessageStr.Substring(0,38);
Console.WriteLine("Received parsed message from client 1:" + comMessageStr);
Console.WriteLine("Server received ready for buffer message from client.");
fetchSocket.Send(sentFileCont, 0, 2048, SocketFlags.None);
comMessageStr = String.Empty;
buffersSent++;
Console.WriteLine("Buffers sent:" + buffersSent);
byteCount = 0;
else
readByte = fStream.ReadByte();
if (readByte != -1)
sentFileCont[byteCount] = Convert.ToByte(readByte);
byteCount++;
while (!comMessageStr.Equals("CLIENT_READY_TO_RECEIVE_CONTENT_BUFFER"))
Console.WriteLine("Server waiting for ready for buffer message from client.");
fetchSocket.Receive(comMessage, 100, SocketFlags.None);
comMessageStr = Encoding.ASCII.GetString(comMessage);
comMessageStr = comMessageStr.Substring(0, 38);
Console.WriteLine("Received parsed message from client 2:" + comMessageStr);
Console.WriteLine("Server received ready for buffer message from client.");
fetchSocket.Send(sentFileCont, 0, 2048, SocketFlags.None);
buffersSent++;
Console.WriteLine("Buffers sent:" + buffersSent);
comMessageStr = String.Empty;
控制台输出:
【问题讨论】:
我能推荐你看看TCPClient
和TCPServer
吗?他们让做这类事情变得容易得多
我去看看。谢谢。
你为什么要手动分块?底层的套接字流会为您做到这一点。无论如何,2048 字节比单个数据包大。
TCP 将消息分成最大大小为 1500 字节的数据报。消息的接收端必须知道消息的结束位置,并且可能需要进行多次读取以获取所有数据。所以通常发件人会执行以下操作之一:1)ASCII:以已知字符终止数据,而不是返回消息中的字符 2)ASCII 或二进制:在消息的开头添加字节数 3)ASCII 或二进制:使用固定大小的消息。
我在上面的评论中指的是TCPListener
,而不是TCPServer
,请查看我的答案以获取示例
【参考方案1】:
我的建议是使用System.Net.Sockets
中提供的TCPListener
和TCPClient
类,它们确实简化了通过TCP/IP
发送消息。
服务器端你需要这样的东西:
class MyTcpListener
public static void Listen()
TcpListener server = null;
byte[] bytes = new byte[256];
try
// Set the TcpListener on port 13000.
const int port = 13000;
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
// TcpListener server = new TcpListener(port);
server = new TcpListener(localAddr, port);
// Start listening for client requests.
server.Start();
// Enter the listening loop.
while (true)
Console.Write("Waiting for a connection... ");
// Perform a blocking call to accept requests.
// You could also user server.AcceptSocket() here.
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("Connected!");
// Get a stream object for reading and writing
NetworkStream stream = client.GetStream();
int i;
// Loop to receive all the data sent by the client.
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
// Translate data bytes to a ASCII string.
string data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Console.WriteLine($"Received: data");
// Process the data sent by the client.
data = data.ToUpper();
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
// Send back a response.
stream.Write(msg, 0, msg.Length);
Console.WriteLine($"Sent: data");
// Shutdown and end connection
client.Close();
catch (SocketException e)
Console.WriteLine($"SocketException: e");
finally
// Stop listening for new clients.
server?.Stop();
显然,您会希望使其适应您的程序结构。我从here 中获取了这个,但稍微编辑了一下。
在你的客户端你想使用这样的东西:
class MyTCPClient
static void Connect(String server, String message)
try
// Create a TcpClient.
// Note, for this client to work you need to have a TcpServer
// connected to the same address as specified by the server, port
// combination.
int port = 13000;
TcpClient client = new TcpClient(server, port);
// Translate the passed message into ASCII and store it as a Byte array. Any encoding can be used as long as it's consistent with the server.
byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
// Get a client stream for reading and writing.
// Stream stream = client.GetStream();
NetworkStream stream = client.GetStream();
// Send the message to the connected TcpServer.
stream.Write(data, 0, data.Length);
Console.WriteLine($"Sent: message");
// Receive the TcpServer.response. This is all optional and can be removed if you aren't recieving a response.
// Buffer to store the response bytes.
data = new byte[256];
// String to store the response ASCII representation.
// Read the first batch of the TcpServer response bytes.
int bytes = stream.Read(data, 0, data.Length);
string responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
Console.WriteLine("Received: responseData");
// Close everything.
stream?.Close();
client?.Close();
catch (ArgumentNullException e)
Console.WriteLine($"ArgumentNullException: e");
catch (SocketException e)
Console.WriteLine($"SocketException: e");
Console.WriteLine("\n Press Enter to continue...");
Console.Read();
同样,这需要适应您的结构,我从here 获取。如果您不期望来自服务器的响应,则可以将响应内容全部删除。
这两个类具有极强的弹性并抽象出所有复杂的东西,数据缓冲区的大小也可以更改为您需要的任何大小。
【讨论】:
我来看看 TCPClient 和 TCPListener。 James Hughes 也提到了 TCPServer。非常感谢。 @EggBender 这是我的错字...TCPServer
在我的评论中提到了TCPListener
好的,我明白了。 TCPClient 和 TCPListener 就可以了。
如果您需要任何帮助来实现这一点,请告诉我,我整天都在附近以上是关于TCP Socket在C#中接收数据错误的主要内容,如果未能解决你的问题,请参考以下文章
如何在C# winform和JS的基础上,结合SOCKET编程,通过SOCKET接收到的数据,直接显示在网页上。