今天看论坛看到一个人问了这一个问题:

———————————————————————————————————————————

c#socket编程中,
客户端通过socket.Send()传送完文件后,
服务端,接收后,如何将那些byte的内容还原为原来的文件啊。。。
求大侠指点,谢谢

———————————————————————————————————————————————————

 

其实这个问题出现得让人无语,却又显得很正常。

让人无语是因为文件存储的方式本就是二进制,而我们在网络中传输的却又是字节流,也就是二进制流,那么这个传输本就是一个复制粘贴的的问题,服务器端的文件作为做基本的二进制流传送到客户端,而客户端按照接受的顺序将二进制流重新写入本地文件,则客户端生成的本地文件与服务器端本就是一个复本。故而很南门为什么会问出还原为原来文件的问题。

但是显得正常是因为我们面对的都是文本流,我们很多时候不需要关注二进制流,更不需要关注扇区等硬件细节性问题了。这个使得我们很多人已经忘记文件是以二进制存储的,尤其很多从事纯软工作的,对于捣鼓过模拟电路数字电路捣鼓过bios等微机接口的人来说,很明显的二进制存储方式,对于纯软的人来说,可能不是那么天经地义了。这个也可以说成是软件进步的一个体现吧,分层、模块化、面向对象化的思想已经使我们真的从底层细节中脱离出来了。我们只需要关注我们的解决方案,却不再需要纠缠于实现细节问题。就像我们用一个statck的时候,我们很少去问底层的栈是怎么实现的,内存分配是怎么弄的。

这也是我在上次的stringstringbuilder比较中提到的那个小白的观点一样,我们说string是不可变的,是常量,string s="aa"; s +="bb";内存中将存在三个字符串复本,但是那个小白却说string s="1"; s="0"来证明string是变量,不是不可变的。这个是同样的道理,我们从事软件编程,却很多人套多局限于我们自己的视野,从来没有去问过操作系统是怎么实现的,内存是怎么分配的,进程是谁管理的,怎么管理的等等。

我们解决问题的时候需要不断抽象不断屏蔽,但是我们自己的知识框架可不能不断抽象屏蔽,否则最后我们会发现一切都不再理所当然,我们会到处失措的。

下面就说一下这位同学问的问题的解决方案吧,当然这些都是大家一看就觉得很白痴的解决方案了,基础问题的解决方案很多时候就是白痴的,但是我们却不一定都能想起来。

/// <summary>

/// 把对象序列化并返回相应的字节

/// </summary>

/// <param name="objectToSerialize">参数:OBJECT 待序列化对象</param>

/// <returns>返回:BYTE[] 序列化后的字节流</returns>

public static byte[] SerializeObject(object objectToSerialize){

if (objectToSerialize == null) return null;

MemoryStream memoryStream = new MemoryStream();

BinaryFormatter binaryFormatter = new BinaryFormatter();

binaryFormatter.Serialize(memoryStream, objectToSerialize);

memoryStream.Position = 0;

byte[] read = new byte[memoryStream.Length];

memoryStream.Read(read, 0, read.Length);

memoryStream.Close();

return read;

}

 

/// <summary>

/// 把字节反序列化成相应的对象

/// </summary>

/// <param name="byteToDeserialize">参数:BYTE[] 待还原的字节流</param>

/// <returns>返回:OBJECT 还原后的对象</returns>

public static object DeserializeObject(byte[] byteToDeserialize)

{

object originalObject = null;

if (byteToDeserialize == null)

return originalObject;

MemoryStream memoryStream = new MemoryStream(byteToDeserialize);

memoryStream.Position = 0;

BinaryFormatter formatter = new BinaryFormatter();

originalObject = formatter.Deserialize(memoryStream);

memoryStream.Close();

return originalObject;

}

 

这个是本地进程中实现的,通过MemoryStreamBinaryFormatter来实现的。

 

当然网络传输,我们更多的是通过FileStream来实现的。完整示例如下:

客户端Client:

public partial class ClientFrm : Form

{

private int size =66530000;

public ClientFrm()

{

InitializeComponent();

}

//发送文件

private void btnSendFile_Click(object sender, EventArgs e)

{FileSend();}

/// <summary>

/// 负责向服务器发送文件

/// </summary>

private void FileSend()

{

try

{

DialogResult result;

//当点击取消或文件名为空时终止程序

result = openFileDig.ShowDialog();

if (result == DialogResult.Cancel || openFileDig.FileName == "" || txtHostName.Text == "" || txtPort.Text == "")

{

MessageBox.Show("条件不全!");

return;

}

//tctClient对象可指定主机名和端口

TcpClient tc = new TcpClient(txtHostName.Text.Trim(), int.Parse(txtPort.Text.Trim()));

//创建网络流

NetworkStream ns = tc.GetStream();

//创建一个文件流,并将打开的文件以字节流形式读入内存

FileStream fsByte = new FileStream(@openFileDig.FileName, FileMode.Open, FileAccess.Read);

byte[] a = InsertFileSign(ns, fsByte);

int sCount = 0,curLen=0;

double c = 0.0, f = (double)fsByte.Length;

while (sCount < ((int)fsByte.Length) && ns.CanWrite)

{

byte[] byts = new Byte[size];

curLen =fsByte.Read(byts, 0, byts.Length);

ns.Write(byts, 0, curLen);

sCount = sCount + curLen;

c = sCount;

progressBar1.Value =Convert.ToInt32((c / f) * 100);

this.Refresh();

}

progressBar1.Value = 100;

//关闭打开的流

fsByte.Flush();

fsByte.Close();

ns.Flush();

ns.Close();

tc.Close();

this.Refresh();

MessageBox.Show("文件发送成功!");

progressBar1.Value = 0;

}

catch (Exception ex)

{

MessageBox.Show(ex.Message);

}

finally { }

}

/// <summary>

/// 向传输的文件写入文件结构信息标记

/// </summary>

/// <param name="ns"></param>

/// <param name="fsByte"></param>

/// <returns></returns>

private byte[] InsertFileSign(NetworkStream ns, FileStream fsByte)

{

string[] name = new string[openFileDig.FileName.Length];

name = openFileDig.FileName.Split(new char[] { '\\' });

//自定义编码方式#MName#文件名.扩展名#MLen#文件长度#End#"

string sign1 = "#MName#" + name[name.Length - 1] + "#MLen#" + fsByte.Length.ToString() + "#End#";

byte[] a = StringTOByts(sign1);

ns.Write(a,0,a.Length);

return a;

}

//将字符串转换为字节数组

public byte[] StringTOByts(string str)

{ //当字符向字节转换是2个字节表示一个字符

byte[] byts = new Byte[1000];

char[] chs = str.ToCharArray();

for (int index = 0; index < chs.Length; index++)

{ //取出字符的低8位并将二进制转换位十进制存入字节数组

byts[index * 2] = (byte)(chs[index] & 0xFF);

//用右移8位取出高8位并将二进制转换位十进制存入字节数组

byts[index * 2 + 1] = (byte)((chs[index] >> 8) & 0xFF);

}

//剩余空间用0填充

for (int i = (str.Length * 2); i < 1000; i++)

{

byts[i] = 0;

}

return byts;

}

 

private void btnCance_Click(object sender, EventArgs e)

{

this.Close();

}

}

服务器端:

public partial class ServerFrm : Form

{

private string paths=@"D:\";

private int size =66530000;

public ServerFrm()

{

InitializeComponent();

}

 

private void button1_Click(object sender, EventArgs e)

{

DialogResult result;

//当点击取消或文件名为空时终止程序

result = openPath.ShowDialog();

if (result == DialogResult.Cancel || txtHostName.Text == "" || txtPort.Text == "")

{

MessageBox.Show("条件不全或保存路径为空!");

return;

}

else

paths = openPath.FileName;

ReceivedFile();

}

/// <summary>

/// 接收文件

/// </summary>

private TcpListener tpclis=null;

private FileStream fsWriteFile = null;

private void ReceivedFile()

{

try

{

//IPHostEntry hostInfo = Dns.GetHostByAddress("127.0.0.1");

//IPAddress[] ipAddree =hostInfo.AddressList;

//IPAddress ip = ipAddree[0];

////获得本地Ip

IPAddress ip = IPAddress.Parse(txtHostName.Text.Trim());

//监听本地端口

tpclis = new TcpListener(ip, int.Parse(txtPort.Text.Trim()));

tpclis.Start();

//创建接收文件线程

Thread thread = new Thread(new ThreadStart(GetFileData));

Control.CheckForIllegalCrossThreadCalls = false;

thread.IsBackground = true;

thread.Start();

//GetFileData();

}

catch (Exception ex)

{

MessageBox.Show(ex.Message);

}

finally { }

}

//获得文件数据

private void GetFileData()

{

while (true)

{

TcpClient tc=tpclis.AcceptTcpClient();

//获得网络流

NetworkStream ns = tc.GetStream();

int MLen;

string Mname;

 

GetFileSignInfo(ns, out MLen, out Mname);

//检查路径如果不存在就创建它

bool fag=Directory.Exists(paths);

if (!fag)

Directory.CreateDirectory(paths);

 

//创建文件

fsWriteFile = new FileStream(paths +@"\"+ Mname, FileMode.OpenOrCreate);

int sCount = 0, curLen = 0;

double c = 0.0,f=MLen;

while (sCount < MLen && ns.CanRead)

{

byte[] file = new Byte[size];

curLen = ns.Read(file, 0, file.Length);

fsWriteFile.Write(file, 0, curLen);

sCount = sCount + curLen;

c = sCount;

progressBar1.Value =Convert.ToInt32((c / f) * 100);

}

progressBar1.Value = 100;

fsWriteFile.Flush();

fsWriteFile.Close();

ns.Flush();

ns.Close();

MessageBox.Show("文件已收到!");

progressBar1.Value = 0;

}

}

/// <summary>

/// 获得文件标记信息

/// </summary>

/// <param name="ns"></param>

/// <param name="bytes"></param>

/// <param name="MLen"></param>

/// <param name="Mname"></param>

private void GetFileSignInfo(NetworkStream ns, out int MLen, out string Mname)

{

byte[] a = new Byte[1000];

ns.Read(a, 0, 1000);

string str = BytsTOString(a, 1000);

//解析规则"#98#MName#光良 - 童话.mp3#MLen#244582#End#";

int ln1 = str.IndexOf("#MName#", 0);

int ln2 = str.IndexOf("#MLen#", ln1 + 6);

int ln3 = str.IndexOf("#End#", ln2 + 5);

//获得文件参数

MLen = int.Parse(str.Substring(ln2 + 6, ln3 - ln2 - 6));//获得歌曲长度

Mname = str.Substring(ln1 + 7, ln2 - ln1 - 7);//获得歌曲名及扩展名

}

 

private void button2_Click(object sender, EventArgs e)

{

this.Close();

}

//将字节型数据转换成字符串

public string BytsTOString(byte[] byts, int len)

{

string str = ""; char ch;

for (int index = 0; index < len / 2; index++)

{

ch = (char)(byts[index * 2] + (byts[index * 2 + 1] << 8));

str += ch;

}

 

return str;

}

 

/// <summary>

/// 把对象序列化并返回相应的字节

/// </summary>

/// <param name="objectToSerialize">参数:OBJECT 待序列化对象</param>

/// <returns>返回:BYTE[] 序列化后的字节流</returns>

public static byte[] SerializeObject(object objectToSerialize)

{

if (objectToSerialize == null)

return null;

MemoryStream memoryStream = new MemoryStream();

BinaryFormatter binaryFormatter = new BinaryFormatter();

binaryFormatter.Serialize(memoryStream, objectToSerialize);

memoryStream.Position = 0;

byte[] read = new byte[memoryStream.Length];

memoryStream.Read(read, 0, read.Length);

memoryStream.Close();

return read;

}

 

/// <summary>

/// 把字节反序列化成相应的对象

/// </summary>

/// <param name="byteToDeserialize">参数:BYTE[] 待还原的字节流</param>

/// <returns>返回:OBJECT 还原后的对象</returns>

public static object DeserializeObject(byte[] byteToDeserialize)

{

object originalObject = null;

if (byteToDeserialize == null)

return originalObject;

MemoryStream memoryStream = new MemoryStream(byteToDeserialize);

memoryStream.Position = 0;

BinaryFormatter formatter = new BinaryFormatter();

originalObject = formatter.Deserialize(memoryStream);

memoryStream.Close();

return originalObject;

}

 

 

}

 

上面这个例子也顺并解决了另一个同学的问题,他问的是如何解析出文件的名称作者等信息。最原始的方法就是通过协议来解决。自己定义传输的二进制流的协议。这样服务器端对着接受的到的字节流,按照协议来解析既可。

posted on 2010-07-02 14:25  BLoodMaster  阅读(1510)  评论(0编辑  收藏  举报