客户端接收文件


1. 服务端的实现

对 于服务端,我们只需要实现上一章遗留的 sendFile()方法就可以了,它起初在 handleProtocol 中是注释掉的。另外,由于创建连接、 获取 流等操作与 receiveFile()是没有区别的,所以我们将它提出来作为一个公共方法 getStreamToClient()。下面是服务端的代码, 只 包含新增改过的代码,对于原有方法我只给出了签名:

class Server { 
 static void Main(string[] args) { 
 Console.WriteLine("Server is running ... "); 
 IPAddress ip = IPAddress.Parse("127.0.0.1"); 
 TcpListener listener = new TcpListener(ip, 8500); 
 listener.Start(); // 开启对控制端口 8500 的侦听
 Console.WriteLine("Start Listening ..."); 
 while (true) { 
 // 获取一个连接,同步方法,在此处中断
 TcpClient client = listener.AcceptTcpClient(); 
 RemoteClient wapper = new RemoteClient(client); 
 wapper.BeginRead(); 
 } 
 } 
} 
public class RemoteClient { 
 // 字段 略
 public RemoteClient(TcpClient client) {} 
 // 开始进行读取
 public void BeginRead() { } 
 // 再读取完成时进行回调
 private void OnReadComplete(IAsyncResult ar) { } 
 // 处理 protocol 
 private void handleProtocol(object obj) { 
 string pro = obj as string; 
 ProtocolHelper helper = new ProtocolHelper(pro); 
 FileProtocol protocol = helper.GetProtocol(); 
 if (protocol.Mode == FileRequestMode.Send) { 
 // 客户端发送文件,对服务端来说则是接收文件
 receiveFile(protocol); 
 } else if (protocol.Mode == FileRequestMode.Receive) { 
 // 客户端接收文件,对服务端来说则是发送文件
 sendFile(protocol); 
 } 
 } 
 // 发送文件
 private void sendFile(FileProtocol protocol) { 
 TcpClient localClient; 
 NetworkStream streamToClient = getStreamToClient(protocol, out localClient); 
 // 获得文件的路径
 string filePath = Environment.CurrentDirectory + "/" + protocol.FileName; 
 // 创建文件流
 FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); 
 byte[] fileBuffer = new byte[1024]; // 每次传 1KB 
 int bytesRead; 
 int totalBytes = 0; 
 // 创建获取文件发送状态的类
 SendStatus status = new SendStatus(filePath); 
 // 将文件流转写入网络流
 try { 
 do { 
 Thread.Sleep(10); // 为了更好的视觉效果,暂停 10 毫秒:-) 
 bytesRead = fs.Read(fileBuffer, 0, fileBuffer.Length); 
 streamToClient.Write(fileBuffer, 0, bytesRead); 
 totalBytes += bytesRead; // 发送了的字节数
 status.PrintStatus(totalBytes); // 打印发送状态
 } while (bytesRead > 0); 
 Console.WriteLine("Total {0} bytes sent, Done!", totalBytes); 
 } catch { 
 Console.WriteLine("Server has lost..."); 
 } 
 streamToClient.Dispose(); 
 fs.Dispose(); 
 localClient.Close(); 
 } 
 // 接收文件
 private void receiveFile(FileProtocol protocol) { } 
 // 获取连接到远程的流 -- 公共方法
 private NetworkStream getStreamToClient(FileProtocol protocol, out TcpClient localClient) { 
 // 获取远程客户端的位置
 IPEndPoint endpoint = client.Client.RemoteEndPoint as IPEndPoint; 
 IPAddress ip = endpoint.Address; 
 // 使用新端口号,获得远程用于接收文件的端口
 endpoint = new IPEndPoint(ip, protocol.Port); 
 // 连接到远程客户端
 try { 
 localClient = new TcpClient(); 
 localClient.Connect(endpoint); 
 } catch { 
 Console.WriteLine("无法连接到客户端 --> {0}", endpoint); 
 localClient = null; 
 return null; 
 } 
 // 获取发送文件的流
 NetworkStream streamToClient = localClient.GetStream(); 
 return streamToClient; 
 } 
 // 随机获取一个图片名称
 private string generateFileName(string fileName) {} 
} 

服务端的 sendFile 方法和客户端的 SendFile()方法完全类似,上面的代码几乎是一次编写成功的。另外注意我将客户端使用的 SendStatus 类也拷贝到了服务端。接下来我们看下客户端。

2. 客户端的实现

首先要注意的是客户端的 SendFile()接收的参数是文件全路径,但是在写入到协议时只获取了路径中的文件名称。这是因为服务端不需 要知道文件在客户端的路径,所以协议中只写文件名;而为了使客户端的 SendFile()方法更通用,所以它接收本地文件的全路径。 客户端的 ReceiveFile()的实现也和服务端的 receiveFile()方法类似,同样,由于要保存到本地,为了避免文件名重复,我将服务端的 generateFileName()方法复制了过来。

public class ServerClient :IDisposable { 
 // 字段略
 public ServerClient() {} 
 // 发送消息到服务端
 public void SendMessage(string msg) {} 
 // 发送文件 - 异步方法
 public void BeginSendFile(string filePath) { } 
 private void SendFile(object obj) { } 
 
 // 发送文件 -- 同步方法
 public void SendFile(string filePath) {} 
 
 // 接收文件 -- 异步方法
 public void BeginReceiveFile(string fileName) { 
 ParameterizedThreadStart start = 
 new ParameterizedThreadStart(ReceiveFile); 
 start.BeginInvoke(fileName, null, null); 
 } 
 public void ReceiveFile(object obj) { 
 string fileName = obj as string; 
 ReceiveFile(fileName); 
 } 
 // 接收文件 -- 同步方法
 public void ReceiveFile(string fileName) { 
 IPAddress ip = IPAddress.Parse("127.0.0.1"); 
 TcpListener listener = new TcpListener(ip, 0); 
 listener.Start(); 
 // 获取本地侦听的端口号
 IPEndPoint endPoint = listener.LocalEndpoint as IPEndPoint; 
 int listeningPort = endPoint.Port; 
 // 获取发送的协议字符串
 FileProtocol protocol = 
 new FileProtocol(FileRequestMode.Receive, listeningPort, fileName); 
 string pro = protocol.ToString(); 
 SendMessage(pro); // 发送协议到服务端
 // 中断,等待远程连接
 TcpClient localClient = listener.AcceptTcpClient(); 
 Console.WriteLine("Start sending file..."); 
 NetworkStream stream = localClient.GetStream(); 
 // 获取文件保存的路劲
 string filePath = 
 Environment.CurrentDirectory + "/" + generateFileName(fileName); 
 // 创建文件流
 FileStream fs = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write); 
 byte[] fileBuffer = new byte[1024]; // 每次传 1KB 
 int bytesRead; 
 int totalBytes = 0; 
 // 从缓存 buffer 中读入到文件流中
 do { 
 bytesRead = stream.Read(buffer, 0, BufferSize); 
 fs.Write(buffer, 0, bytesRead); 
 totalBytes += bytesRead; 
 Console.WriteLine("Receiving {0} bytes ...", totalBytes); 
 } while (bytesRead > 0); 
 Console.WriteLine("Total {0} bytes received, Done!", totalBytes); 
 fs.Dispose(); 
 stream.Dispose(); 
 localClient.Close(); 
 listener.Stop(); 
 } 
 // 随机获取一个图片名称
 private string generateFileName(string fileName) {} 
 public void Dispose() { 
 if (streamToServer != null) 
 streamToServer.Dispose(); 
 if (client != null) 
 client.Close(); 
 } 
} 

上面关键的一句就是创建协议那句,注意到将 mode 由 Send 改为了 Receive,同时传去了想要接收的服务端的文件名称。

3. 程序测试

现在我们已经完成了所有收发文件的步骤,可以看到服务端的所有操作都是被动的,接下来我们修改客户端的 Main()程序,创建一个菜 单,然后根据用户输入发送或者接收文件。

class Program { 
 static void Main(string[] args) { 
 ServerClient client = new ServerClient(); 
 string input; 
 string path = Environment.CurrentDirectory + "/"; 
 do { 
 Console.WriteLine("Send File: S1 - Client01.jpg, S2 - Client02.jpg, S3 - Client03.jpg"); 
 Console.WriteLine("Receive File: R1 - Server01.jpg, R1 - Server02.jpg, R3- Server03.jpg"); 
 Console.WriteLine("Press 'Q' to exit. "); 
 Console.Write("Enter your choice: "); 
 input = Console.ReadLine(); 
 switch(input.ToUpper()){ 
 case "S1": 
 client.BeginSendFile(path + "Client01.jpg"); 
 break; 
 case "S2": 
 client.BeginSendFile(path + "Client02.jpg"); 
 break; 
 case "S3": 
 client.BeginSendFile(path + "Client02.jpg"); 
 break; 
 case "R1": 
 client.BeginReceiveFile("Server01.jpg"); 
 break; 
 case "R2": 
 client.BeginReceiveFile("Server01.jpg"); 
 break; 
 case "R3": 
 client.BeginReceiveFile("Server01.jpg"); 
 break; 
 } 
 } while (input.ToUpper() != "Q"); 
 client.Dispose(); 
 } 
} 

由于这是一个控制台应用程序,并且采用了异步操作,所以这个菜单的出现顺序有点混乱。我这里描述起来比较困难,你将代码下载下 来后运行一下就知道了:-)

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
张国生
上一篇:Part5
下一篇:没有了
评论列表

发表评论

评论内容
昵称:
关联文章