这篇文章将完成Part.4中剩余的部分,它们本来是一篇完整的文章,但是因为上一篇比较长,合并起来页数太多,浏览起来可能会比较不方便,我就将它拆为两篇了,本文便是它的后半部分。我们继续进行上一篇没有完成的步骤:客户端接收来自服务端的文件。 4.客户端接收文件 4.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类也拷贝到了服务端。接下来我们看下客户端。 4.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,同时传去了想要接收的服务端的文件名称。 4.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();
-
- }
-
- }
由于这是一个控制台应用程序,并且采用了异步操作,所以这个菜单的出现顺序有点混乱。我这里描述起来比较困难,你将代码下载下来后运行一下就知道了:-) 程序的运行结果和上一节类似,这里我就不再贴图了。接下来是本系列的最后一篇,将发送字符串与传输文件的功能结合起来,创建一个可以发送消息并能收发文件的聊天程序,至于语音聊天嘛...等我学习了再告诉你 >_<、
|
请发表评论