C#网络编程(接收文件) - Part.5
这篇文章将完成 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();
}
}
由于这是一个控制台应用程序,并且采用了异步操作,所以这个菜单的出现顺序有点混乱。我这里描述起来比较困难,你将代码下载下来后运行一下就知道了:-)
程序的运行结果和上一节类似,这里我就不再贴图了。接下来是本系列的最后一篇,将发送字符串与传输文件的功能结合起来,创建一个可以发送消息并能收发文件的聊天程序,至于语音聊天嘛...等我学习了再告诉你 >_<
出处:http://www.cnblogs.com/JimmyZhang/archive/2008/09/16/1291858.html
C#网络编程(接收文件) - Part.5的更多相关文章
- Java 网络编程---分布式文件协同编辑器设计与实现
目录: 第一部分:Java网络编程知识 (一)简单的Http请求 一般浏览网页时,使用的时Ip地址,而IP(Internet Protocol,互联网协议)目前主要是IPv4和IPv6. IP地址是一 ...
- Unix网络编程 — 头文件解析
1.1. < sys/types.h > primitive system data types(包含很多类型重定义,如pid_t.int8_t等) 1.2. < sys/socke ...
- iOS开发之网络编程--获取文件的MIMEType
前言:有时候我们需要获取文件的MIMEType的信息,下面就介绍关于获取MIMEType的方法. 1.直接百度搜索关键字"MIMEType",你会找到,然后查吧: 2.用代码获取文 ...
- Android网络编程之——文件断点下载
一:关于断点下载所涉及到的知识点 1.对SQLite的增删改查(主要用来保存当前任务的一些信息) 2.HttpURLConnection的请求配置 HttpURLConnection connecti ...
- [转]C#网络编程(订立协议和发送文件) - Part.4
本文转自:http://www.tracefact.net/CSharp-Programming/Network-Programming-Part4.aspx 源码下载:http://www.trac ...
- Python网络编程总结
----learn from luffycity---- 1. 什么是C/S架构? C指的是client(客户端软件),S指的是Server(服务端软件),C/S架构就是基于网络实现客户端与服务端通信 ...
- day09网络编程
一 操作系统基础 操作系统:(Operating System,简称OS)是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才 ...
- 12 . Python3之网络编程
互联网的本质 两台计算机之间的通信与两个人打电话原理是一样的. # 1. 首先要通过各种物理连接介质连接 # 2. 找准确对方计算机(准确到软件)的位置 # 3. 通过统一的标准(一般子协议)进行数据 ...
- Android网络编程只局域网传输文件
Android网络编程之局域网传输文件: 首先创建一个socket管理类,该类是传输文件的核心类,主要用来发送文件和接收文件 具体代码如下: package com.jiao.filesend; im ...
随机推荐
- JVM调优总结(一)
数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:他代表的值就是数值本身:而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对象本身, ...
- php编写的抽奖程序中奖概率算法
本文给大家分享的是php中奖概率算法,可用于刮刮卡,大转盘等抽奖算法.用法很简单,代码里有详细注释说明,一看就懂,有需要的小伙伴参考下吧. 我们先完成后台PHP的流程,PHP的主要工作是负责配置奖项及 ...
- jquery在线引用
转载:http://www.cnblogs.com/lzx-1024/p/7716615.html jquery-3.1.1(最新)官网jquery压缩版引用地址:<script src=&qu ...
- Hibernate多对多关系映射
两张表的多对多关系,在数据库中通常是通过第三张中间表来实现的,第三张中间表放的是两张表各自的主键值,通过主键与主键的对应来体现表直接的关系.比如在权限系统中,一个用户可以拥有多种权限,而一种权限也可以 ...
- C++中map的用法
map的特性是,所有元素都会根据元素的减值自动被排序.map的所有元素都是pair,同时拥有实值(value)和键值(key).pair的第一个元素会被视为键值,第二个元素会被视为实值.map不允许两 ...
- Three.js基础:建立Cube并实现鼠标交互,动画旋转
index.html文件: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
- jedis使用管道(pipeline)对redis进行读写(使用hmset、hgetall测试)
一般情况下,Redis Client端发出一个请求后,通常会阻塞并等待Redis服务端处理,Redis服务端处理完后请求命令后会将结果通过响应报文返回给Client.这有点类似于HBase的Scan, ...
- Prism初研究之使用Prism实现WPF的MVVM模式
转自:http://www.cnblogs.com/qianzi067/p/5804880.html
- oracle内存分析
oracle时间内存=SGA+PGA SGA(System Global Area):由所有服务进程和后台进程共享: PGA(Program Global Area):由每个服务进程.后台进程专有:每 ...
- 条款22:将成员变量声明为private
protected成员变量的封装性并非高于public变量. 如果有个public的成员变量,一旦其需要改变,那么所有使用它的代码都需要改变. 如果有个protected的成员变量,一点其需要改变,那 ...