今天看论坛看到一个人问了这一个问题:
———————————————————————————————————————————
在c#的socket编程中,
客户端通过socket.Send()传送完文件后,
服务端,接收后,如何将那些byte的内容还原为原来的文件啊。。。
求大侠指点,谢谢
———————————————————————————————————————————————————
其实这个问题出现得让人无语,却又显得很正常。
让人无语是因为文件存储的方式本就是二进制,而我们在网络中传输的却又是字节流,也就是二进制流,那么这个传输本就是一个复制粘贴的的问题,服务器端的文件作为做基本的二进制流传送到客户端,而客户端按照接受的顺序将二进制流重新写入本地文件,则客户端生成的本地文件与服务器端本就是一个复本。故而很南门为什么会问出还原为原来文件的问题。
但是显得正常是因为我们面对的都是文本流,我们很多时候不需要关注二进制流,更不需要关注扇区等硬件细节性问题了。这个使得我们很多人已经忘记文件是以二进制存储的,尤其很多从事纯软工作的,对于捣鼓过模拟电路数字电路捣鼓过bios等微机接口的人来说,很明显的二进制存储方式,对于纯软的人来说,可能不是那么天经地义了。这个也可以说成是软件进步的一个体现吧,分层、模块化、面向对象化的思想已经使我们真的从底层细节中脱离出来了。我们只需要关注我们的解决方案,却不再需要纠缠于实现细节问题。就像我们用一个statck的时候,我们很少去问底层的栈是怎么实现的,内存分配是怎么弄的。
这也是我在上次的string与stringbuilder比较中提到的那个小白的观点一样,我们说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; } |
这个是本地进程中实现的,通过MemoryStream与BinaryFormatter来实现的。
当然网络传输,我们更多的是通过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; }
} |
上面这个例子也顺并解决了另一个同学的问题,他问的是如何解析出文件的名称作者等信息。最原始的方法就是通过协议来解决。自己定义传输的二进制流的协议。这样服务器端对着接受的到的字节流,按照协议来解析既可。