在之前的文章中,讨论了如何从客户端向服务器传输文件。现在,将探讨如何从服务器向客户端传输文件。通过结合这两种传输方式,可以编写代码实现客户端之间的文件传输。中提供了完整的套接字编程系列教程,可以在那里找到更多关于套接字和使用C#进行分布式架构编程的文章。
之前有用户反映英语表达不够清晰,现在正在努力改进。相信现在阅读文章应该不会有太多困难。
要实现服务器向客户端传输文件,必须有两个应用程序:服务器应用程序和客户端应用程序。在代码中,分别提到了这两个部分。在下面的部分中,将描述服务器的操作,即服务器应用程序正在工作,需要查看服务器代码;对于客户端操作,需要查看客户端代码。这两个套接字编程应用程序的握手应该遵循以下步骤:
首先需要运行服务器应用程序,这个服务器应用程序将打开一个具有预定义IP地址和端口号的端点,并保持监听模式以接受来自客户端的新套接字连接请求。这就像某人在某个固定位置等待回复某人的请求。下面这段代码正是在做这样的事情:
IPEndPoint ipEnd = new IPEndPoint(IPAddress.Any, 5656);
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
sock.Bind(ipEnd);
sock.Listen(100);
第1行创建了一个端口号为5656的ipEnd点,IP是本机IP地址。端口号可以是任何值,除了众所周知的端口号(端口号应该大于1024)。接下来的两行创建了一个套接字对象,并将其绑定到之前创建的IPEndPoint。最后一行将新创建的套接字对象发送到监听模式以接受来自客户端的新连接请求。
所以需要先运行服务器应用程序,然后运行客户端应用程序。
现在轮到客户端请求服务器了。
IPAddress[] ipAddress = Dns.GetHostAddresses("localhost");
IPEndPoint ipEnd = new IPEndPoint(ipAddress[0], 5656);
Socket clientSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
clientSock.Connect(ipEnd);
这些代码来自客户端应用程序,这里前两行用于获取本地主机IP地址,并使用它创建新的IPEndPoint。请确保IP地址和端口号与服务器地址相同。在本机上运行应用程序,所以使用localhost。接下来的两行代码创建了一个套接字对象,并尝试使用IPEndPoint连接。因此,这个套接字对象将尝试连接到处于监听模式的服务器套接字。
再次轮到服务器响应客户端的请求,这是通过服务器应用程序中的以下代码行完成的:
Socket clientSock = sock.Accept();
这个"sock"是之前创建的服务器套接字对象,它处于监听模式。这个sock对象将接受客户端请求,并生成一个新的套接字对象,名为"clientSock"。服务器端的所有后续工作将由"clientSock"对象处理,以处理这个特定的客户端请求。
这些代码与套接字编程没有直接关系。这是用于读取和向客户端发送文件的。
string fileName = "test.txt";
string filePath = @"C:\FT\";
byte[] fileNameByte = Encoding.ASCII.GetBytes(fileName);
byte[] fileData = File.ReadAllBytes(filePath + fileName);
byte[] clientData = new byte[4 + fileNameByte.Length + fileData.Length];
byte[] fileNameLen = BitConverter.GetBytes(fileNameByte.Length);
fileNameLen.CopyTo(clientData, 0);
fileNameByte.CopyTo(clientData, 4);
fileData.CopyTo(clientData, 4 + fileNameByte.Length);
这些代码行从本地驱动器读取某个特定文件,并将文件数据读取到字节数组"clientData"中。文件数据需要以原始字节格式存储在数组中,以便将这些数据发送到客户端。文件名大小字符串位于文件数据的开头。这是客户端和服务器之间的预定义,必须这样做,否则客户端将无法获取服务器发送的文件名。
在案例中,使用前四个字节来表示文件名长度,从第5个字节开始存储文件名。所以所有文件数据将存储在文件名之后。
现在文件数据在字节数组中,需要将其发送到客户端。使用以下代码,借助客户端套接字(clientSock)对象,完成了这件事,该对象是在客户端请求接受期间创建的。
clientSock.Send(clientData);
基本上,服务器应用程序的任务在这里就结束了,对于小文件传输来说。剩余的代码用于一些装饰和套接字关闭相关的事情。
现在轮到客户端,它将执行以下任务:
byte[] clientData = new byte[1024 * 5000];
string receivedPath = "C:/";
int receivedBytesLen = clientSock.Receive(clientData);
这里前两行只是创建一个字节数组来存储服务器数据,路径用于决定数据保存的位置。在代码中,将数据保存在C:驱动器。最后一行开始从服务器接收数据。每当客户端套接字开始接收服务器数据时,它就会返回数据的长度,这个长度被捕获在一个整数变量中。
这部分代码是使用以下代码行检索文件名长度,并使用此文件名,这是服务器在文件数据开始时发送的。这将需要检索文件名。
int fileNameLen = BitConverter.ToInt32(clientData, 0);
string fileName = Encoding.ASCII.GetString(clientData, 4, fileNameLen);
现在接收到的数据将使用以下代码行保存在客户端,借助二进制流写入器。
BinaryWriter bWrite = new BinaryWriter(File.Open(receivedPath + fileName, FileMode.Append));
bWrite.Write(clientData, 4 + fileNameLen, receivedBytesLen - 4 - fileNameLen);
文件数据开始检索是在文件大小和文件名字节之后。这在第二行中进行了管理。通过这种方式,一个小型文件可以从服务器发送到客户端。
现在服务器和客户端都将执行相同的活动;那就是使用套接字的关闭方法释放服务器和客户端套接字。客户端还需要关闭二进制流写入器。
bWrite.Close();
clientSock.Close();
通过遵循这些步骤,可以从服务器向客户端发送文件。同样的方式,也可以发送大文件从服务器到客户端。TCP缓冲区一次不能处理大量数据。所以如果尝试发送大文件,它会引发溢出错误。为了避免这个错误,需要将大文件分割成小块,并逐个发送。