本文详细介绍客户端接收来自服务端的文件。
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. \n"); 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(); } }
由于这是一个控制台应用程序,并且采用了异步操作,所以这个菜单的出现顺序有点混乱。我这里描述起来比较困难,你将代码下载下来后运行一下就知道了:-)
共同学习,写下你的评论
评论加载中...
作者其他优质文章