在程序设计中,涉及数据存储和数据交换的时候,不论是B/S仍是C/S模式,都有这样一个概念:数据库服务器。这要求一台性能和配置都比较好的主机做为服务器,以知足数目众多的客户端进行频繁访问。可是对于一些数据交换的要求不主同,并且涉及到的通信个体数目很少,若是还采用“一主机多客户机”的模式,便要求一台硬件配置良好并且软件上安装了相关数据服务软件,这样会形成硬件和软件上的不少没必要要的成本,这时Socket在点对点的平行对象之间的网络通信的优点就就发挥出来了。html
其实对于Socket通信来讲,服务器和客户端的界定不像数据库服务器与客户端那样明显,甚至能够说Socket通信里面的服务器和客户端只是相对的,由于网络通信的对象基本上是处于平等层面的,只是为了方便对两台联网通信的主机的描述才这样定义称谓的。数据库
因为在.NET中Socket通信的创建很容易,因此本文主要介绍一个Socket的比较典型的应用的流程:客户端向服务器发送图片请求,图片服务器接收到请求,并将服务器硬盘上的图片编码,发送到客户端,客户端获得图片数据后,再将这些数据写成图片文件,保存在客户端上。数组
本文主要是对Socket的一个应用进行介绍,因此至于其原理在此没有深究,至于如何创建Socket还有如何实现网络的七层协议在此都没有进行相关研究和介绍,本文主要介绍如何实现一个用户想要的功能,即在两台主机之间进行通信,经过网络来收发用户想要收发的数据。安全
2、通信相关的代码服务器
本文以Windows控制台程序为例来实现引功能。网络
不论是通信服务器或者通信客户端,本文均以一个不断运行的线程来实现对端口的侦听,将通信相关的变量的函数作成一个类,在Program.cs中只负责初始化一些参数,而后创建通信的线程。具体代码以下:socket
2.1服务器端ide
Program.cs:函数
using System; using System.Net; using System.Net.Sockets; using System.Threading; namespace ConsoleSocketsDemo { class Program { static void Main(string[] args) { int sendPicPort = 600;//发送图片的端口 int recvCmdPort = 400;//接收请求的端口开启后就一直进行侦听 SocketServer socketServerProcess = new SocketServer(recvCmdPort, sendPicPort); Thread tSocketServer = new Thread(new ThreadStart(socketServerProcess.thread));//线程开始的时候要调用的方法为threadProc.thread tSocketServer.IsBackground = true;//设置IsBackground=true,后台线程会自动根据主线程的销毁而销毁 tSocketServer.Start(); Console.ReadKey();//直接main里边最后加个Console.Read()不就行了。要按键才退出。 } } }
SocketServer.cs:性能
using System; using System.Text; using System.Net; using System.Net.Sockets; using System.IO; namespace ConsoleSocketsDemo { class SocketServer { Socket sRecvCmd; int recvCmdPort;//接收图片请求命令 int sendPicPort;//发送图片命令 public SocketServer(int recvPort,int sendPort) { recvCmdPort = recvPort; sendPicPort = sendPort; //创建本地socket,一直对4000端口进行侦听 IPEndPoint recvCmdLocalEndPoint = new IPEndPoint(IPAddress.Any, recvCmdPort); sRecvCmd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sRecvCmd.Bind(recvCmdLocalEndPoint); sRecvCmd.Listen(100); } public void thread() { while (true) { System.Threading.Thread.Sleep(1);//每一个线程内部的死循环里面都要加个“短期”睡眠,使得线程占用资源获得及时释放 try { Socket sRecvCmdTemp = sRecvCmd.Accept();//Accept 以同步方式从侦听套接字的链接请求队列中提取第一个挂起的链接请求,而后建立并返回新的 Socket sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 5000);//设置接收数据超时 sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);//设置发送数据超时 sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 1024); //设置发送缓冲区大小 1K sRecvCmdTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 1024);//设置接收缓冲区大小1K byte[] recvBytes = new byte[1024];//开启一个缓冲区,存储接收到的信息 sRecvCmdTemp.Receive(recvBytes); //将读得的内容放在recvBytes中 string strRecvCmd = Encoding.Default.GetString(recvBytes);// //程序运行到这个地方,已经能接收到远程发过来的命令了 //************* //解码命令,并执行相应的操做----以下面的发送本机图片 //************* string[] strArray = strRecvCmd.Split(';'); if (strArray[0] == "PicRequest") { string[] strRemoteEndPoint =sRecvCmdTemp.RemoteEndPoint.ToString().Split(':');//远处终端的请求端IP和端口,如:127.0.0.1:4000 string strRemoteIP = strRemoteEndPoint[0]; SentPictures(strRemoteIP, sendPicPort); //发送本机图片文件 recvBytes = null; } } catch(Exception ex) { Console.Write(ex.Message); } } } /// <summary> /// 向远程客户端发送图片 /// </summary> /// <param name="strRemoteIP">远程客户端IP</param> /// <param name="sendPort">发送图片的端口</param> private static void SentPictures(string strRemoteIP, int sendPort) { string path = "D:\\images\\"; string strImageTag = "image";//图片名称中包含有image的全部图片文件 try { string[] picFiles = Directory.GetFiles(path, strImageTag + "*", SearchOption.TopDirectoryOnly);//知足要求的文件个数 if (picFiles.Length == 0) { return;//没有图片,不作处理 } long sendBytesTotalCounts = 0;//发送数据流总长度 //消息头部:命令标识+文件数目+……文件i长度+ string strMsgHead = "PicResponse;" + picFiles.Length + ";"; //消息体:图片文件流 byte[][] msgPicBytes = new byte[picFiles.Length][]; for (int j = 0; j < picFiles.Length; j++) { FileStream fs = new FileStream(picFiles[j].ToString(), FileMode.Open, FileAccess.Read); BinaryReader reader = new BinaryReader(fs); msgPicBytes[j] = new byte[fs.Length]; strMsgHead += fs.Length.ToString() + ";"; sendBytesTotalCounts += fs.Length; reader.Read(msgPicBytes[j], 0, msgPicBytes[j].Length); } byte[] msgHeadBytes = Encoding.Default.GetBytes(strMsgHead);//将消息头字符串转成byte数组 sendBytesTotalCounts += msgHeadBytes.Length; //要发送的数据流:数据头+数据体 byte[] sendMsgBytes = new byte[sendBytesTotalCounts];//要发送的总数组 for (int i = 0; i < msgHeadBytes.Length; i++) { sendMsgBytes[i] = msgHeadBytes[i]; //数据头 } int index = msgHeadBytes.Length; for (int i = 0; i < picFiles.Length; i++) { for (int j = 0; j < msgPicBytes[i].Length; j++) { sendMsgBytes[index + j] = msgPicBytes[i][j]; } index += msgPicBytes[i].Length; } //程序执行到此处,带有图片信息的报文已经准备好了 //PicResponse;2;94223;69228; //+图片1比特流+……图片2比特流 try { #region 发送图片 Socket sSendPic = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress ipAddress = IPAddress.Parse(strRemoteIP);//remoteip = "127.0.0.1" try { sSendPic.Connect(ipAddress, sendPort);//链接无故客户端主机 sSendPic.Send(sendMsgBytes, sendMsgBytes.Length, 0);//发送本地图片 } catch (System.Exception e) { System.Console.Write("SentPictures函数在创建远程链接时出现异常:" +e.Message); }finally { sSendPic.Close(); } #endregion } catch { } } catch(Exception ex) { Console.Write(ex.Message); } } } }
2.2客户端端
Program.cs:
using System; using System.Text; using System.Net; using System.Net.Sockets; using System.IO; namespace ConsoleClientSocketDemo { class RecvPic { Socket sRecvPic;//接收图片的socket int recvPicPort;//接收图片端口 public RecvPic(int recvPort) { recvPicPort = recvPort; IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, recvPicPort); sRecvPic = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sRecvPic.Bind(localEndPoint); sRecvPic.Listen(100); } public void thread() { while (true) { System.Threading.Thread.Sleep(1);//每一个线程内部的死循环里面都要加个“短期”睡眠,使得线程占用资源获得及时释放 try { Socket sRecvPicTemp = sRecvPic.Accept();//一直在等待socket请求,并创建一个和请求相同的socket,覆盖掉原来的socket sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 5000); //设置接收数据超时 sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);//设置发送数据超时 sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 1024);//设置发送缓冲区大小--1K大小 sRecvPicTemp.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 1024); //设置接收缓冲区大小 #region 先取出数据头部信息---并解析头部 byte[] recvHeadBytes = new byte[1024];//先取1K的数据,提取出数据的头部 sRecvPicTemp.Receive(recvHeadBytes, recvHeadBytes.Length, 0); string recvStr = Encoding.UTF8.GetString(recvHeadBytes); string[] strHeadArray = recvStr.Split(';');//PicResponse;2;94223;69228; string strHeadCmd = strHeadArray[0];//头部命令 int picCounts = Convert.ToInt32(strHeadArray[1]) ;//数据流中包含的图片个数 int[] picLength=new int[picCounts];//每一个图片的长度 for (int i = 0; i < picCounts;i++ ) { picLength[i] = Convert.ToInt32(strHeadArray[i+2]); } #endregion int offset=0;//数据头的长度 for (int k = 0; k < strHeadArray.Length - 1;k++ ) { offset += strHeadArray[k].Length + 1;//由于后面的分号 } int picOffset = recvHeadBytes.Length - offset;//第一张图片在提取数据头的时候已经被提取了一部分了 if (strHeadCmd == "PicResponse") { #region 储存图片--为了节约内存,能够每接收一次就保存一次图片 for (int i = 0; i < picCounts; i++) { byte[] recvPicBytes = new byte[(picLength[i])];//每次只接收一张图片 if (i == 0)//第一幅图片有一部分在提取数据头的时候已经提取过了。 { byte[] recvFirstPicBuffer = new byte[picLength[i] -picOffset]; sRecvPicTemp.Receive(recvFirstPicBuffer, recvFirstPicBuffer.Length, 0); for (int j = 0; j < picOffset; j++) { recvPicBytes[j] = recvHeadBytes[offset + j];//第一幅图片的前一部分 } for (int j = 0; j < recvFirstPicBuffer.Length; j++)//第一张图片的后半部分 { recvPicBytes[picOffset + j] = recvFirstPicBuffer[j]; } //将图片写入文件 SavePicture(recvPicBytes, "-0"); } else { sRecvPicTemp.Receive(recvPicBytes, recvPicBytes.Length, 0);//每次取一张图片的长度 SavePicture(recvPicBytes, "-"+i.ToString()); //将图片数据写入文件 } } #endregion } } catch(Exception ex) { Console.Write(ex.Message); } finally { } } } /// <summary> /// 保存图片到指定路径 /// </summary> /// <param name="picBytes">图片比特流</param> /// <param name="picNum">图片编号</param> public void SavePicture(byte[] picBytes, string picNum) { string filename = "receivePic"; if (!Directory.Exists("E:\\images\\")) Directory.CreateDirectory("E:\\images\\"); if (File.Exists("E:\\images\\" + filename + picNum + ".jpg")) return; FileStream fs = new FileStream("E:\\images\\" + filename + picNum + ".jpg", FileMode.OpenOrCreate, FileAccess.Write); fs.Write(picBytes, 0, picBytes.Length); fs.Dispose(); fs.Close(); } } }
3、测试socket的链接方法,telnet远程登陆
用户能够同时对客户端和服务器端的Socket程序进行编写,而后进行联调,也能够一次只编写一个,而后经过下面的方法来测试Socket链接。
通常经过远程登陆来测试链接是否成功,好比测试本机的400端口是否能链接成功:
“运行->cmd->telnet 127.0.0.1 400”
在没有运行对本机的400端口进行不断侦听的程序时,会出现链接失败的提示:
Socket sRecvCmdTemp = sRecvCmd.Accept();
以后的语句上断点。
--------------------------------------------------------------------------------------------------
http://shihuan830619.iteye.com/blog/1113837 (我JavaEye的博客, 有附件)
附近演示程序的说明:
1.使用VS2005建立。
2.主要实现的功能是:主机A向主机B发图片请求,主机B将D盘image目录下的image0.jpg,image1.jpg文件编码发送到主机B,主机B再解码并写成图片文件到E盘的image目录下。
3.为了方便调试,演示程序将服务器和客户端同时放在本机上,即localhost或者127.0.0.1,即本程序最终实现的效果就是将本机的D盘image目录下的两个指定名称的图片传送到E盘image目录下。因此在运行本程序前,先在D:/image目录下放置两张命名为image0.jpg,image1.jpg的图片文件
4.先运行服务器程序,再运行客户端程序
特别声明:目前,对于传输和图片数据的报文格式存在必定的小问题,由于目前是用的分号“;”做为分隔符,因此在当图片数据流中存在和分号的ASCII码值相同的数时,在客户端解码是便会出现问题,比较稳妥的方法是严格限定死数据头报文的长度(宁肯多花几位为空均可以,但要有严格的编码格式),而后在解码的时候,按照位来解码,而不是按照分号的分隔符来解码。因此应用Byte数组来进行编码,而不该该是string字符串,用string字符串的话会出现不少问题的:好比,遇到空字符串就认为是结尾了,遇到“;”就表示是编码分隔符号,而这些字符都是有可能在图片数据中存在的,因此用sting字符串会有安全隐患的。
出处:http://blog.sina.com.cn/s/blog_4f925fc3010186mf.html