我正在尝试使用C#TcpClient通过TCP发送多个文件,对于低于64kB的文件,它工作得很好,但是当我有更多时,它会抛出一个主机 - 计算机断开连接的执行。
这是我的代码:
服务器端(发送)。 1)SocketServer类
public abstract class SocketServer
{
private Socket serverSocket;
public SocketServer(IPEndPoint localEndPoint)
{
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(localEndPoint);
serverSocket.Listen(0);
serverSocket.BeginAccept(BeginAcceptCallback, null);
}
private void BeginAcceptCallback(IAsyncResult ar)
{
Socket clientSocket = serverSocket.EndAccept(ar);
Console.WriteLine("Client connected");
ClientConnection clientConnection = new ClientConnection(this, clientSocket);
Thread clientThread = new Thread(new ThreadStart(clientConnection.Process));
clientThread.Start();
serverSocket.BeginAccept(BeginAcceptCallback, null);
}
internal abstract void OnReceiveMessage(ClientConnection client, byte header, byte[] data);
}
这是ClientConnection:
public class ClientConnection
{
private SocketServer server;
private Socket clientSocket;
private byte[] buffer;
private readonly int BUFFER_SIZE = 8192;
public ClientConnection(SocketServer server, Socket clientSocket)
{
this.server = server;
this.clientSocket = clientSocket;
buffer = new byte[BUFFER_SIZE];
}
public void Process()
{
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.Peek, BeginReceiveCallback, null);
}
private void BeginReceiveCallback(IAsyncResult ar)
{
int bytesReceived = clientSocket.EndReceive(ar);
if (bytesReceived >= 4)
{
clientSocket.Receive(buffer, 0, 4, SocketFlags.None);
// message size
int size = BitConverter.ToInt32(buffer, 0);
// read message
int read = clientSocket.Receive(buffer, 0, size, SocketFlags.None);
// if data still fragmented, wait for it
while (read < size)
{
read += clientSocket.Receive(buffer, read, size - read, SocketFlags.None);
}
ProcessReceivedData(size);
}
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.Peek, BeginReceiveCallback, null);
}
private void ProcessReceivedData(int size)
{
using (PacketReader pr = new PacketReader(buffer))
{
// message header = 1 byte
byte header = pr.ReadByte();
// next message data
byte[] data = pr.ReadBytes(size - 1);
server.OnReceiveMessage(this, header, data);
}
}
public void Send(byte[] data)
{
// first of all, send message length
clientSocket.Send(BitConverter.GetBytes(data.Length), 0, 4, SocketFlags.None);
// and then message
clientSocket.Send(data, 0, data.Length, SocketFlags.None);
}
}
FileServer类的实现:
public class FileServer : SocketServer
{
string BinaryPath;
public FileServer(IPEndPoint localEndPoint) : base(localEndPoint)
{
BinaryPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
}
internal override void OnReceiveMessage(ClientConnection client, byte hdr, byte[] data)
{
// Convert header byte to ENUM
Headers header = (Headers)hdr;
switch(header)
{
case Headers.Queue:
Queue(client, data);
break;
default:
Console.WriteLine("Wrong header received {0}", header);
break;
}
}
private void Queue(ClientConnection client, byte[] data)
{
// this message contains fileName
string fileName = Encoding.ASCII.GetString(data, 1, data.Length - 1);
// combine path with assembly location
fileName = Path.Combine(BinaryPath, fileName);
if (File.Exists(fileName))
{
FileInfo fileInfo = new FileInfo(fileName);
long fileLength = fileInfo.Length;
// pass the message that is now start a file transfer, contains:
// 1 byte = header
// 16 bytes = file length
using (PacketWriter pw = new PacketWriter())
{
pw.Write((byte)Headers.Start);
pw.Write(fileLength);
client.Send(pw.GetBytes());
}
//
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
int read = 0, offset = 0;
byte[] fileChunk = new byte[8191];
while (offset < fileLength)
{
// pass message with file chunks, conatins
// 1 byte = header
// 8195 bytes = file chunk
using (PacketWriter pw = new PacketWriter())
{
fs.Position = offset;
read = fs.Read(fileChunk, 0, fileChunk.Length);
pw.Write((byte)Headers.Chunk);
pw.Write(fileChunk, 0, read);
client.Send(pw.GetBytes());
}
offset += read;
}
}
}
}
}
辅助类:
public class PacketWriter : BinaryWriter
{
private MemoryStream memoryStream;
public PacketWriter() : base()
{
memoryStream = new MemoryStream();
OutStream = memoryStream;
}
public byte[] GetBytes()
{
byte[] data = memoryStream.ToArray();
return data;
}
}
public class PacketReader : BinaryReader
{
public PacketReader(byte[] data) : base(new MemoryStream(data))
{
binaryFormatter = new BinaryFormatter();
}
}
客户端具有几乎相同的代码。除了收到:
internal override void OnReceiveMessage(ServerConnection client, byte hdr, byte[] data)
{
Headers header = (Headers)hdr;
switch(header)
{
case Headers.Start:
Start(data); // save length of file and prepare for receiving it
break;
case Headers.Chunk:
Chunk(data);
break;
default:
Console.WriteLine("Wrong header received {0}", header);
break;
}
}
private void Chunk(byte[] data)
{
// Process reveived data, write it into a file
}
答案 0 :(得分:0)
64kB发送后TCP套接字会自动关闭吗?
不,TCP套接字在64 KB发送数据后也不会自动关闭,也不会在任何发送数据后自动关闭。它们保持打开状态,直到任一端关闭连接或发生某种类型的网络错误。
不幸的是,你的问题非常模糊,实际上没有任何背景。但我会在客户端代码中指出一个可能存在严重错误的错误:您不会将客户端读取的数据限制为您对当前文件的预期数据。如果服务器正在发送多个文件,则完全有可能并且可能在到达当前文件的数据末尾时,包含最终字节序列的读取也将包含初始序列下一个文件的字节数。
因此,表达式totalBytesRead == fileLenght
将永远不会是true
,并且您的代码将尝试读取服务器发送的所有数据,就好像它是第一个文件的所有部分一样。
检查此方案的一种简单方法是查看客户端写入的文件的大小。如果出现上述问题,它将比预期的要大得多。
您可以通过最多只接收实际预期剩余的字节数来修复代码:
while ((bytesRead = binaryReader.Read(
buffer, 0, Math.Min(fileLenght - totalBytesRead, buffer.Length))) > 0)
{
fs.Write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == fileLenght)
{
break;
}
}
在您处理此问题时,您可以修复名为fileLenght
的变量的拼写。我知道Intellisense可以很容易地输入变量名称,无论它是否正确拼写,但是如果你有一天必须通过源代码查找代码本身与&#34;长度&#34;价值观,你会错过这个变量。
修改强>
在您最初使用问题发布的代码中指出了问题后,我现在发现自己在最近编辑问题后会查看完全不同的代码。该示例仍然不是a good, minimal, complete code example,因此建议仍然必须是有限的。但是我可以在接收处理中看到至少两个主要问题:
Receive()
和BeginReceive()
。一般来说,这可能不太可能太糟糕了。在你的情况下真正糟糕的是,在初始异步接收的完成处理程序中,你在使用同步Receive()
来(试图)接收其余的时阻塞线程对于数据,然后更糟糕的是,在FileServer
情况下,当您将文件数据传输到客户端时,您将继续阻止该线程。buffer
数组一次,长度为8192,然后继续尝试填充整个计数 - 正在发送的字节字节流。假设客户端将发送短消息,这在服务器端可能很好。但在客户端,size
的值很容易大于buffer
数组中分配的8192个字节。BeginReceiveCallback()
,因为您传递的ArgumentOutOfRangeException
方法的Receive()
值超过{{1}的长度数组。我无法证明,因为代码示例不完整。你自己必须跟进这个理论。
只要我查看您发布的代码,我扫描时会出现一些其他违规行为:
size
方法会丢弃最初发送的字节数。你调用buffer
,它返回收到的字节数,但是后面的代码假定流中的BeginReceiveCallback()
值仍在等待读取,当它很容易被发送到第一个字节块。实际上,您的比较EndReceive()
似乎是为了避免处理收到的数据,直到您拥有该长度值。出于另一个原因,这是错误的:size
。但更糟糕的是,如果TCP真的想要,它被允许一次滴出一个字节(它不会,但如果你正确地编写代码,它就不重要了)。如果你只获得至少4个字节,你只处理收到的数据,并且如果你没有得到4个字节,你只是忽略到目前为止发送的任何内容,理论上你可以忽略 all 已发送的数据。你有可能完全忽略任何数据的风险是可怕的。但上面的#2保证它,而#3只会加重伤害。
bytesReceived >= 4
值时,您正在读取32位整数,但当发送 bytesReceived > 4
值时(即文件本身),您正在编写一个64位整数。您将在接收端获得正确的数字,因为size
使用little-endian,这意味着任何小于2 ^ 31的64位整数的前四个字节与四个字节相同相同数字的32位整数表示。但是在那之后你仍然会有四个额外的零字节;如果被视为文件数据,可能会损坏您的文件。