本文转自:http://www.tracefact.net/CSharp-Programming/Network-Programming-Part4.aspx

源码下载:http://www.tracefact.net/SourceCode/Network-Part5.rar

文件传输

前面两篇文章所使用的范例都是传输字符串,有的时候我们可能会想在服务端和客户端之间传递文件。比如,考虑这样一种情况,假如客户端显示了一个菜单,当我们输入S1、S2或S3(S为Send缩写)时,分别向服务端发送文件Client01.jpg、Client02.jpg、Client03.jpg;当我们输入R1、R2或R3时(R为Receive缩写),则分别从服务端接收文件Server01.jpg、Server02.jpg、Server03.jpg。那么,我们该如何完成这件事呢?此时可能有这样两种做法:

  • 类似于FTP协议,服务端开辟两个端口,并持续对这两个端口侦听:一个用于接收字符串,类似于FTP的控制端口,它接收各种命令(接收或发送文件);一个用于传输数据,也就是发送和接收文件。
  • 服务端只开辟一个端口,用于接收字符串,我们称之为控制端口。当接到请求之后,根据请求内容在客户端开辟一个端口专用于文件传输,并在传输结束后关闭端口。

现在我们只关注于上面的数据端口,回忆一下在第二篇中我们所总结的,可以得出:当我们使用上面的方法一时,服务端的数据端口可以为多个客户端的多次请求服务;当我们使用方法二时,服务端只为一个客户端的一次请求服务,但是因为每次请求都会重新开辟端口,所以实际上还是相当于可以为多个客户端的多次请求服务。同时,因为它只为一次请求服务,所以我们在数据端口上传输文件时无需采用异步传输方式。但在控制端口我们仍然需要使用异步方式。

从上面看出,第一种方式要好得多,但是我们将采用第二种方式。至于原因,你可以回顾一下Part.1(基本概念和操作)中关于聊天程序模式的讲述,因为接下来一篇文章我们将创建一个聊天程序,而这个聊天程序采用第三种模式,所以本文的练习实际是对下一篇的一个铺垫。

1.订立协议

1.1发送文件

我们先看一下发送文件的情况,如果我们想将文件client01.jpg由客户端发往客户端,那么流程是什么:

  1. 客户端开辟数据端口用于侦听,并获取端口号,假设为8005。
  2. 假设客户端输入了S1,则发送下面的控制字符串到服务端:[file=Client01.jpg, mode=send, port=8005]。
  3. 服务端收到以后,根据客户端ip和端口号与该客户端建立连接。
  4. 客户端侦听到服务端的连接,开始发送文件。
  5. 传送完毕后客户端、服务端分别关闭连接。

此时,我们订立的发送文件协议为:[file=Client01.jpg, mode=send, port=8005]。但是,由于它是一个普通的字符串,在上一篇中,我们采用了正则表达式来获取其中的有效值,但这显然不是一种好办法。因此,在本文及下一篇文章中,我们采用一种新的方式来编写协议:XML。对于上面的语句,我们可以写成这样的XML:

<protocol><file name="client01.jpg" mode="send" port="8005" /></protocol>

这样我们在服务端就会好处理得多,接下来我们来看一下接收文件的流程及其协议。

NOTE:这里说发送、接收文件是站在客户端的立场说的,当客户端发送文件时,对于服务器来收,则是接收文件。

1.2接收文件

接收文件与发送文件实际上完全类似,区别只是由客户端向网络流写入数据,还是由服务端向网络流写入数据。

  1. 客户端开辟数据端口用于侦听,假设为8006。
  2. 假设客户端输入了R1,则发送控制字符串:<protocol><file name="Server01.jpg" mode="receive" port="8006" /></protocol>到服务端。
  3. 服务端收到以后,根据客户端ip和端口号与该客户端建立连接。
  4. 客户端建立起与服务端的连接,服务端开始网络流中写入数据。
  5. 传送完毕后服务端、客户端分别关闭连接。

2.协议处理类的实现

和上面一章一样,在开始编写实际的服务端客户端代码之前,我们首先要编写处理协议的类,它需要提供这样两个功能:1、方便地帮我们获取完整的协议信息,因为前面我们说过,服务端可能将客户端的多次独立请求拆分或合并。比如,客户端连续发送了两条控制信息到服务端,而服务端将它们合并了,那么则需要先拆开再分别处理。2、方便地获取我们所想要的属性信息,因为协议是XML格式,所以还需要一个类专门对XML进行处理,获得字符串的属性值。

2.1 ProtocalHandler辅助类

我们先看下ProtocalHandler,它与上一篇中的RequestHandler作用相同。需要注意的是必须将它声明为实例的,而非静态的,这是因为每个TcpClient都需要对应一个ProtocalHandler,因为它内部维护的patialProtocal不能共享,在协议发送不完整的情况下,这个变量用于临时保存被截断的字符串。

public class ProtocolHandler {

     private string partialProtocal; // 保存不完整的协议

     public ProtocolHandler() {
partialProtocal = "";
} public string[] GetProtocol(string input) {
return GetProtocol(input, null);
} // 获得协议
private string[] GetProtocol(string input, List<string> outputList) {
if (outputList == null)
outputList = new List<string>(); if (String.IsNullOrEmpty(input))
return outputList.ToArray(); if (!String.IsNullOrEmpty(partialProtocal))
input = partialProtocal + input; string pattern = "(^<protocol>.*?</protocol>)"; // 如果有匹配,说明已经找到了,是完整的协议
if (Regex.IsMatch(input, pattern)) { // 获取匹配的值
string match = Regex.Match(input, pattern).Groups[].Value;
outputList.Add(match);
partialProtocal = ""; // 缩短input的长度
input = input.Substring(match.Length); // 递归调用
GetProtocol(input, outputList); } else {
// 如果不匹配,说明协议的长度不够,
// 那么先缓存,然后等待下一次请求
partialProtocal = input;
} return outputList.ToArray();
}
}

因为现在它已经不是本文的重点了,所以我就不演示对于它的测试了,本文所附带的代码中含有它的测试代码(我在ProtocolHandler中添加了一个静态类Test())。

2.2 FileRequestType枚举和FileProtocol结构

因为XML是以字符串的形式在进行传输,为了方便使用,我们最好构建一个强类型来对它们进行操作,这样会方便很多。我们首先可以定义FileRequestMode枚举,它代表是发送还是接收文件:

public enum FileRequestMode {
Send = ,
Receive
} 接下来我们再定义一个FileProtocol结构,用来为整个协议字符串提供强类型的访问,注意这里覆盖了基类的ToString()方法,这样在客户端我们就不需要再手工去编写XML,只要在结构值上调用ToString()就OK了,会方便很多。 public struct FileProtocol {
private readonly FileRequestMode mode;
private readonly int port;
private readonly string fileName; public FileProtocol
(FileRequestMode mode, int port, string fileName) {
this.mode = mode;
this.port = port;
this.fileName = fileName;
} public FileRequestMode Mode {
get { return mode; }
} public int Port {
get { return port; }
} public string FileName {
get { return fileName; }
} public override string ToString() {
return String.Format("<protocol><file name=\"{0}\" mode=\"{1}\" port=\"{2}\" /></protocol>", fileName, mode, port);
}
}

2.3 ProtocolHelper辅助类

这个类专用于将XML格式的协议映射为我们上面定义的强类型对象,这里我没有加入try/catch异常处理,因为协议对用户来说是不可见的,而且客户端应该总是发送正确的协议,我觉得这样可以让代码更加清晰:

public class ProtocolHelper {

     private XmlNode fileNode;
private XmlNode root; public ProtocolHelper(string protocol) {
XmlDocument doc = new XmlDocument();
doc.LoadXml(protocol);
root = doc.DocumentElement;
fileNode = root.SelectSingleNode("file");
} // 此时的protocal一定为单条完整protocal
private FileRequestMode GetFileMode() {
string mode = fileNode.Attributes["mode"].Value;
mode = mode.ToLower();
if (mode == "send")
return FileRequestMode.Send;
else
return FileRequestMode.Receive;
} // 获取单条协议包含的信息
public FileProtocol GetProtocol() {
FileRequestMode mode = GetFileMode();
string fileName = "";
int port = ; fileName = fileNode.Attributes["name"].Value;
port = Convert.ToInt32(fileNode.Attributes["port"].Value); return new FileProtocol(mode, port, fileName);
}
}

OK,我们又耽误了点时间,下面就让我们进入正题吧。

3.客户端发送数据

3.1 服务端的实现

我们还是将一个问题分成两部分来处理,先是发送数据,然后是接收数据。我们先看发送数据部分的服务端。如果你从第一篇文章看到了现在,那么我觉得更多的不是技术上的问题而是思路,所以我们不再将重点放到代码上,这些应该很容易就看懂了。

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, ); listener.Start(); // 开启对控制端口 8500 的侦听
Console.WriteLine("Start Listening ..."); while (true) {
// 获取一个连接,同步方法,在此处中断
TcpClient client = listener.AcceptTcpClient();
RemoteClient wapper = new RemoteClient(client);
wapper.BeginRead();
}
}
} public class RemoteClient {
private TcpClient client;
private NetworkStream streamToClient;
private const int BufferSize = ;
private byte[] buffer;
private ProtocolHandler handler; public RemoteClient(TcpClient client) {
this.client = client; // 打印连接到的客户端信息
Console.WriteLine("\nClient Connected!{0} <-- {1}",
client.Client.LocalEndPoint, client.Client.RemoteEndPoint); // 获得流
streamToClient = client.GetStream();
buffer = new byte[BufferSize]; handler = new ProtocolHandler();
} // 开始进行读取
public void BeginRead() {
AsyncCallback callBack = new AsyncCallback(OnReadComplete);
streamToClient.BeginRead(buffer, , BufferSize, callBack, null);
} // 再读取完成时进行回调
private void OnReadComplete(IAsyncResult ar) {
int bytesRead = ;
try {
lock (streamToClient) {
bytesRead = streamToClient.EndRead(ar);
Console.WriteLine("Reading data, {0} bytes ...", bytesRead);
}
if (bytesRead == ) throw new Exception("读取到0字节"); string msg = Encoding.Unicode.GetString(buffer, , bytesRead);
Array.Clear(buffer,,buffer.Length); // 清空缓存,避免脏读 // 获取protocol数组
string[] protocolArray = handler.GetProtocol(msg);
foreach (string pro in protocolArray) {
// 这里异步调用,不然这里可能会比较耗时
ParameterizedThreadStart start =
new ParameterizedThreadStart(handleProtocol);
start.BeginInvoke(pro, null, null);
} // 再次调用BeginRead(),完成时调用自身,形成无限循环
lock (streamToClient) {
AsyncCallback callBack = new AsyncCallback(OnReadComplete);
streamToClient.BeginRead(buffer, , BufferSize, callBack, null);
}
} catch(Exception ex) {
if(streamToClient!=null)
streamToClient.Dispose();
client.Close();
Console.WriteLine(ex.Message); // 捕获异常时退出程序
}
} // 处理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 receiveFile(FileProtocol protocol) {
// 获取远程客户端的位置
IPEndPoint endpoint = client.Client.RemoteEndPoint as IPEndPoint;
IPAddress ip = endpoint.Address; // 使用新端口号,获得远程用于接收文件的端口
endpoint = new IPEndPoint(ip, protocol.Port); // 连接到远程客户端
TcpClient localClient;
try {
localClient = new TcpClient();
localClient.Connect(endpoint);
} catch {
Console.WriteLine("无法连接到客户端 --> {0}", endpoint);
return;
} // 获取发送文件的流
NetworkStream streamToClient = localClient.GetStream(); // 随机生成一个在当前目录下的文件名称
string path =
Environment.CurrentDirectory + "/" + generateFileName(protocol.FileName); byte[] fileBuffer = new byte[]; // 每次收1KB
FileStream fs = new FileStream(path, FileMode.CreateNew, FileAccess.Write); // 从缓存buffer中读入到文件流中
int bytesRead;
int totalBytes = ;
do {
bytesRead = streamToClient.Read(buffer, , BufferSize);
fs.Write(buffer, , bytesRead);
totalBytes += bytesRead;
Console.WriteLine("Receiving {0} bytes ...", totalBytes);
} while (bytesRead > ); Console.WriteLine("Total {0} bytes received, Done!", totalBytes); streamToClient.Dispose();
fs.Dispose();
localClient.Close();
} // 随机获取一个图片名称
private string generateFileName(string fileName) {
DateTime now = DateTime.Now;
return String.Format(
"{0}_{1}_{2}_{3}", now.Minute, now.Second, now.Millisecond, fileName
);
}
}

这里应该没有什么新知识,需要注意的地方有这么几个:

  • 在OnReadComplete()回调方法中的foreach循环,我们使用委托异步调用了handleProtocol()方法,这是因为handleProtocol即将执行的是一个读取或接收文件的操作,也就是一个相对耗时的操作。
  • 在handleProtocol()方法中,我们深切体会了定义ProtocolHelper类和FileProtocol结构的好处。如果没有定义它们,这里将是不堪入目的处理XML以及类型转换的代码。
  • handleProtocol()方法中进行了一个条件判断,注意sendFile()方法我屏蔽掉了,这个还没有实现,但是我想你已经猜到它将是后面要实现的内容。
  • receiveFile()方法是实际接收客户端发来文件的方法,这里没有什么特别之处。需要注意的是文件存储的路径,它保存在了当前程序执行的目录下,文件的名称我使用generateFileName()生成了一个与时间有关的随机名称。

3.2客户端的实现

我们现在先不着急实现客户端S1、R1等用户菜单,首先完成发送文件这一功能,实际上,就是为上一节SendMessage()加一个姐妹方法SendFile()。

class Client {
static void Main(string[] args) {
ConsoleKey key; ServerClient client = new ServerClient();
string filePath = Environment.CurrentDirectory + "/" + "Client01.jpg"; if(File.Exists(filePath))
client.BeginSendFile(filePath); Console.WriteLine("\n\n输入\"Q\"键退出。");
do {
key = Console.ReadKey(true).Key;
} while (key != ConsoleKey.Q);
}
} public class ServerClient {
private const int BufferSize = ;
private byte[] buffer;
private TcpClient client;
private NetworkStream streamToServer; public ServerClient() {
try {
client = new TcpClient();
client.Connect("localhost", ); // 与服务器连接
} catch (Exception ex) {
Console.WriteLine(ex.Message);
return;
}
buffer = new byte[BufferSize]; // 打印连接到的服务端信息
Console.WriteLine("Server Connected!{0} --> {1}",
client.Client.LocalEndPoint, client.Client.RemoteEndPoint); streamToServer = client.GetStream();
} // 发送消息到服务端
public void SendMessage(string msg) { byte[] temp = Encoding.Unicode.GetBytes(msg); // 获得缓存
try {
lock (streamToServer) {
streamToServer.Write(temp, , temp.Length); // 发往服务器
}
Console.WriteLine("Sent: {0}", msg);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
return;
}
} // 发送文件 - 异步方法
public void BeginSendFile(string filePath) {
ParameterizedThreadStart start =
new ParameterizedThreadStart(BeginSendFile);
start.BeginInvoke(filePath, null, null);
} private void BeginSendFile(object obj) {
string filePath = obj as string;
SendFile(filePath);
} // 发送文件 -- 同步方法
public void SendFile(string filePath) { IPAddress ip = IPAddress.Parse("127.0.0.1");
TcpListener listener = new TcpListener(ip, );
listener.Start(); // 获取本地侦听的端口号
IPEndPoint endPoint = listener.LocalEndpoint as IPEndPoint;
int listeningPort = endPoint.Port; // 获取发送的协议字符串
string fileName = Path.GetFileName(filePath);
FileProtocol protocol =
new FileProtocol(FileRequestMode.Send, listeningPort, fileName);
string pro = protocol.ToString(); SendMessage(pro); // 发送协议到服务端 // 中断,等待远程连接
TcpClient localClient = listener.AcceptTcpClient();
Console.WriteLine("Start sending file...");
NetworkStream stream = localClient.GetStream(); // 创建文件流
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
byte[] fileBuffer = new byte[]; // 每次传1KB
int bytesRead;
int totalBytes = ; // 创建获取文件发送状态的类
SendStatus status = new SendStatus(filePath); // 将文件流转写入网络流
try {
do {
Thread.Sleep(); // 为了更好的视觉效果,暂停10毫秒:-)
bytesRead = fs.Read(fileBuffer, , fileBuffer.Length);
stream.Write(fileBuffer, , bytesRead);
totalBytes += bytesRead; // 发送了的字节数
status.PrintStatus(totalBytes); // 打印发送状态
} while (bytesRead > );
Console.WriteLine("Total {0} bytes sent, Done!", totalBytes);
} catch {
Console.WriteLine("Server has lost...");
} stream.Dispose();
fs.Dispose();
localClient.Close();
listener.Stop();
}
}

接下来我们来看下这段代码,有这么两点需要注意一下:

  • 在Main()方法中可以看到,图片的位置为应用程序所在的目录,如果你跟我一样处于调试模式,那么就在解决方案的Bin目录下的Debug目录中放置三张图片Client01.jpg、Client02.jpg、Client03.jpg,用来发往服务端。
  • 我在客户端提供了两个SendFile()方法,和一个BeginSendFile()方法,分别用于同步和异步传输,其中私有的SendFile()方法只是一个辅助方法。实际上对于发送文件这样的操作我们几乎总是需要使用异步操作。
  • SendMessage()方法中给streamToServer加锁很重要,因为SendFile()方法是多线程访问的,而在SendFile()方法中又调用了SendMessage()方法。
  • 我另外编写了一个SendStatus类,它用来记录和打印发送完成的状态,已经发送了多少字节,完成度是百分之多少,等等。本来这个类的内容我是直接写入在Client类中的,后来我觉得它执行的工作已经不属于Client本身所应该执行的领域之内了,我记得这样一句话:当你觉得类中的方法与类的名称不符的时候,那么就应该考虑重新创建一个类。我觉得用在这里非常恰当。

下面是SendStatus的内容:

// 即时计算发送文件的状态
public class SendStatus {
private FileInfo info;
private long fileBytes; public SendStatus(string filePath) {
info = new FileInfo(filePath);
fileBytes = info.Length;
} public void PrintStatus(int sent) {
string percent = GetPercent(sent);
Console.WriteLine("Sending {0} bytes, {1}% ...", sent, percent);
} // 获得文件发送的百分比
public string GetPercent(int sent){ decimal allBytes = Convert.ToDecimal(fileBytes);
decimal currentSent = Convert.ToDecimal(sent); decimal percent = (currentSent / allBytes) * ;
percent = Math.Round(percent, ); //保留一位小数 if (percent.ToString() == "100.0")
return "";
else
return percent.ToString();
}
}

3.3程序测试

接下里我们运行一下程序,来检查一下输出,首先看下服务端:

接着是客户端,我们能够看到发送的字节数和进度,可以想到如果是图形界面,那么我们可以通过扩展SendStatus类来创建一个进度条:

最后我们看下服务端的Bin\Debug目录,应该可以看到接收到的图片:

本来我想这篇文章就可以完成发送和接收,不过现在看来没法实现了,因为如果继续下去这篇文章就太长了,我正尝试着尽量将文章控制在15页以内。那么我们将在下篇文章中再完成接收文件这一部分。

C#网络编程(接收文件)  - Part.5

http://www.tracefact.net/CSharp-Programming/Network-Programming-Part5.aspx

这篇文章将完成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, ); 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[]; // 每次传1KB
int bytesRead;
int totalBytes = ; // 创建获取文件发送状态的类
SendStatus status = new SendStatus(filePath); // 将文件流转写入网络流
try {
do {
Thread.Sleep(); // 为了更好的视觉效果,暂停10毫秒:-)
bytesRead = fs.Read(fileBuffer, , fileBuffer.Length);
streamToClient.Write(fileBuffer, , bytesRead);
totalBytes += bytesRead; // 发送了的字节数
status.PrintStatus(totalBytes); // 打印发送状态
} while (bytesRead > );
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, );
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[]; // 每次传1KB
int bytesRead;
int totalBytes = ; // 从缓存buffer中读入到文件流中
do {
bytesRead = stream.Read(buffer, , BufferSize);
fs.Write(buffer, , bytesRead);
totalBytes += bytesRead;
Console.WriteLine("Receiving {0} bytes ...", totalBytes);
} while (bytesRead > ); 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();
}
}

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

程序的运行结果和上一节类似,这里我就不再贴图了。接下来是本系列的最后一篇,将发送字符串与传输文件的功能结合起来,创建一个可以发送消息并能收发文件的聊天程序,至于语音聊天嘛...等我学习了再告诉你 >_<、

[转]C#网络编程(订立协议和发送文件) - Part.4的更多相关文章

  1. C#网络编程(订立协议和发送文件) - Part.4

    文件传输 前面两篇文章所使用的范例都是传输字符串,有的时候我们可能会想在服务端和客户端之间传递文件.比如,考虑这样一种情况,假如客户端显示了一个菜单,当我们输入S1.S2或S3(S为Send缩写)时, ...

  2. Java网络编程(UDP协议:发送端)

    package WebProgramingDemo; import java.io.IOException; import java.net.DatagramPacket; import java.n ...

  3. 网络编程_UDP协议_发送端与接收端

    创建UDP传输的发送端 : 1.建立udp的socket服务   2.将要发送的数据封装到数据包中   3.通过udp的socket服务 将数据包发送出去   4.关闭socket服务(因为调用了系统 ...

  4. 网络编程TCP协议-聊天室

    网络编程TCP协议-聊天室(客户端与服务端的交互); <span style="font-size:18px;">1.客户端发数据到服务端.</span> ...

  5. 网络编程——TCP协议、UDP协议、socket套接字、粘包问题以及解决方法

    网络编程--TCP协议.UDP协议.socket套接字.粘包问题以及解决方法 TCP协议(流式协议) ​ 当应用程序想通过TCP协议实现远程通信时,彼此之间必须先建立双向通信通道,基于该双向通道实现数 ...

  6. java网络编程+通讯协议的理解

    参考: http://blog.csdn.net/sunyc1990/article/details/50773014 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很 ...

  7. iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载+使用输出流代替文件句柄

    前言:本篇讲解,在前篇iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载的基础上,使用输出流代替文件句柄实现大文件断点续传.    在实际开发中,输入输出流用的比较少,但 ...

  8. C# 网络编程之基于SMTP发送电子邮件

     本文主要讲述基于C#网络编程的发送邮件的编程,邮件发送功能是基于邮件协议的,常见的电子邮件协议有SMTP(简单邮件传输协议).POP3(邮局协议).IMAP(Internet邮件访问协议),文章主要 ...

  9. python 网络编程 -- Tcp协议

    Socket是网络编程的一个抽象概念.通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可. 客户端 大多数连接都是可靠 ...

随机推荐

  1. MagicSuggest – Bootstrap 主题的多选组合框

    MagicSuggest 是专为 Bootstrap 主题开发的多选组合框.它支持自定义呈现,数据通过 Ajax 异步获取,使用组件自动过滤.它允许空间免费项目,也有动态加载固定的建议. 您可能感兴趣 ...

  2. 【追寻javascript高手之路04】理解prototype

    前言 中午时候我去药店称了下体重,好家伙!我减肥成功了,足足比上个月瘦了10斤!于是想减肥就去郑州吧... 然后回来迷迷糊糊睡了一会,居然想起了周三的面试,有点小遗憾有点小触动. 这次回成都后,还没有 ...

  3. [deviceone开发]-cnodejs论坛移动端App

    一. 简介 这个App是利用cnodejs.net的API来实现论坛的移动端,使用了deviceone的官方的js库(github.com/do-js). 从而使代码非常简洁,便于阅读和参考,值得推荐 ...

  4. 学习zepto.js(Hello World)

    Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api. 如果你会用jquery,那么你也会用zepto. 昨天听说了zepto.js,正好最近也比较闲 ...

  5. Flex Viewer

    一.Flex Viewer简介 Flex Viewer是ESRI公司推出的可以高效开发基于WEB的地理信息应用系统的一种完全免费的应用程序框架.业务人员使用该框架可以无需任何额外的编程就能够通过简单配 ...

  6. javascript的浅拷贝和深拷贝

    1.浅拷贝:复制一份引用,所有引用对象都指向一份数据,并且都可以修改这份数据. 2.深拷贝(复杂):复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制. 这里画一个简单的图来加深理解: ...

  7. Client JQuery invoke NetSuite Suitelet

    Please indicate the source if you need to repost. Client jQuery could initialize a cross-domain requ ...

  8. 验证码做得不错,有.net 版本 https://captcha.com/

    https://captcha.com/ https://captcha.com/demos/features/captcha-demo.aspx

  9. 机器数据的价值 - Web 访问日志和数据库审计日志

    计算机数据 大量的数据流,不断增长的来源,蕴含着巨大的价值 在 Splunk,我们大量谈及计算机数据.这些数据是指在数据中心.“物联网”和互联设备世界中运行的所有系统产生的数据.其中包括支撑组织的应用 ...

  10. Oracle SQL Developer如何配置TNS

    安装了ORACLE的SQL Developer 4.0.3.16,但是连接数据库时,如果选择连接类型为"TNS",无法获取网络别名,那么要如何设置,才能访问到TNS文件呢? 此时需 ...