在上篇博客简单理解socket写完以后我就但愿写出一个websocket的服务器了,可是一路困难重重,仍是从基础开始吧,先搞定C# socket编程基本知识,写一个支持广播的简单server/client交互demo,而后再拓展为websocket服务器。想要搞定这个须要一些基本知识javascript
进程与线程对CS的同窗来讲确定耳闻能像了,再啰嗦两句我我的的理解,每一个运行在系统上的程序都是一个进程,进程就是正在执行的程序,把编译好的指令放入特定一块内存,顺序执行,这就是一个进程,咱们平时写的if-else,for循环都按照咱们预期,一步步顺序执行,这是由于咱们写的是单线程的程序,所谓线程是一个进程的执行片断,咱们写的单线程程序,整个进程就一个主线程,全部代码在这个线程内顺序执行,但一个进程能够有多个线程同时执行,这就是多线程程序,利用多线程支持咱们可让程序一边监听客户端请求,一边广播消息。html
熟悉web开发的同窗确定了解这个概念,在使用ajax中咱们就会用到异步的请求,同步与异步正好和咱们生活中的理解相反(我尝试问过学管理的女友)java
同步:下一个调用在上一个调用返回结果后执行,也能够理解为事情必须一件作完再去作另外一件,咱们常常编写的语句都是同步调用web
int a=dosomething(); a+=1;
a+=1; 这条指令必须在dosomething()方法执行完毕返回结果后才能够执行,不然就乱了套ajax
异步:异步概念和同步相对,当一个异步过程调用发出后,调用者不能马上获得结果。实际处理这个调用的部件在完成后,经过状态、通知和回调来通知调用者(百度上抄的)。理解了同步概念后异步也就不难理解了,以javascript的ajax为例编程
ajax(arg1,arg2,function(){ //回调函数
a=3; });
a=4;
这个代码段执行完成后通常状况会把a赋值为3而不是4,由于在ajax方法调用后,a=4;这条语句并无等待ajax()返回结果就执行了,也就是在ajax()执行完成调用回调函数以前,a=4;已经执行了,回调函数再把a赋值为3使之成为最后结果,为此在ajax调用中咱们常常会使用回调函数,其实在不少异步处理中咱们都会使用到回调函数。服务器
了解了上面知识咱们就能够按照下图来写咱们的服务器了websocket
关于怎么具体一步步使用socket我就不说了,有兴趣同窗能够看看你得学会而且学得会的Socket编程基础知识,看看咱们服务器的结构,我写了一个TcpHelper类来处理服务器操做多线程
首先定义 一个ClientInfo类存放Client信息异步
而后是一个SocketMessage类,记录客户端发来的消息
而后定义两个全局变量记录全部客户端及全部客户端发来的消息
private Dictionary<Socket, ClientInfo> clientPool = new Dictionary<Socket, ClientInfo>(); private List<SocketMessage> msgPool = new List<SocketMessage>();
而后就是几个主要方法的定义
/// <summary> /// 启动服务器,监听客户端请求 /// </summary> /// <param name="port">服务器端进程口号</param> public void Run(int port); /// <summary> /// 在独立线程中不停地向全部客户端广播消息 /// </summary> private void Broadcast(); /// <summary> /// 把客户端消息打包处理(拼接上谁何时发的什么消息) /// </summary> /// <returns>The message.</returns> /// <param name="sm">Sm.</param> private byte[] PackageMessage(SocketMessage sm); /// <summary> /// 处理客户端链接请求,成功后把客户端加入到clientPool /// </summary> /// <param name="result">Result.</param> private void Accept(IAsyncResult result); /// <summary> /// 处理客户端发送的消息,接收成功后加入到msgPool,等待广播 /// </summary> /// <param name="result">Result.</param> private void Recieve(IAsyncResult result);
逐个分析一下把
这是该类惟一提供的共有方法,供外界调用,来根据port参数建立一个socket
public void Run(int port) { Thread serverSocketThraed = new Thread(() => { Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); server.Bind(new IPEndPoint(IPAddress.Any, port)); server.Listen(10); server.BeginAccept(new AsyncCallback(Accept), server); }); serverSocketThraed.Start(); Console.WriteLine("Server is ready"); Broadcast(); }
代码很简单,须要注意的有几点
1.在一个新线程中建立服务器socket,最多容许10个客户端链接。
2.在方法最后调用Broadcast()方法用于向全部客户端广播消息
3.BeginAccept方法,MSDN上有权威解释,可是以为不够接地气,简单说一下个人理解,首先这个方法是异步的,用于服务器接受一个客户端的链接,第一个参数其实是回调函数,在C#中使用委托,在回调函数中经过调用EndAccept就能够得到尝试链接的客户端socket,第二个参数是包含请求state的对象,传入server socket对象自己就能够了
方法用于处理客户端链接请求
private void Accept(IAsyncResult result) { Socket server = result.AsyncState as Socket; Socket client = server.EndAccept(result); try { //处理下一个客户端链接 server.BeginAccept(new AsyncCallback(Accept), server); byte[] buffer = new byte[1024]; //接收客户端消息 client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Recieve), client); ClientInfo info = new ClientInfo(); info.Id = client.RemoteEndPoint; info.handle = client.Handle; info.buffer = buffer; //把客户端存入clientPool this.clientPool.Add(client, info); Console.WriteLine(string.Format("Client {0} connected", client.RemoteEndPoint)); } catch (Exception ex) { Console.WriteLine("Error :\r\n\t" + ex.ToString()); } }
BeginRecieve方法的MSDN有解释,和Accept同样也是异步处理,接收客户端消息,放入第一个参数中,它也传入了一个回调函数的委托,和带有socket state的对象,用于处理下一次接收。咱们把接收成功地客户端socket及其对应信息存放到clientPool中
方法用于接收客户端消息,并把全部消息及其发送者信息存入msgInfo,等待广播
private void Recieve(IAsyncResult result) { Socket client = result.AsyncState as Socket; if (client == null || !clientPool.ContainsKey(client)) { return; } try { int length = client.EndReceive(result); byte[] buffer = clientPool[client].buffer; //接收消息 client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Recieve), client); string msg = Encoding.UTF8.GetString(buffer, 0, length); SocketMessage sm = new SocketMessage(); sm.Client = clientPool[client]; sm.Time = DateTime.Now; Regex reg = new Regex(@"{<(.*?)>}"); Match m = reg.Match(msg); if (m.Value != "") //处理客户端传来的用户名 { clientPool[client].NickName = Regex.Replace(m.Value, @"{<(.*?)>}", "$1"); sm.isLogin = true; sm.Message = "login!"; Console.WriteLine("{0} login @ {1}", client.RemoteEndPoint,DateTime.Now); } else //处理客户端传来的普通消息 { sm.isLogin = false; sm.Message = msg; Console.WriteLine("{0} @ {1}\r\n {2}", client.RemoteEndPoint,DateTime.Now,msg); } msgPool.Add(sm); } catch { //把客户端标记为关闭,并在clientPool中清除 client.Disconnect(true); Console.WriteLine("Client {0} disconnet", clientPool[client].Name); clientPool.Remove(client); } }
这个的代码都很简单,就很少解释了,我加入了用户名处理用于广播客户端消息的时候显示客户端自定义的昵称而不是生硬的ip地址+端口号,固然这里须要客户端配合
服务器已经和客户端链接成功,而且接收到了客户端消息,咱们就能够看看该怎么广播消息了,Broadcast()方法已经在run()方法内调用,看看它是怎么运做广播客户端消息的
private void Broadcast() { Thread broadcast = new Thread(() => { while (true) { if (msgPool.Count > 0) { byte[] msg = PackageMessage(msgPool[0]); foreach (KeyValuePair<Socket, ClientInfo> cs in clientPool) { Socket client = cs.Key; if (client.Connected) { client.Send(msg, msg.Length, SocketFlags.None); } } msgPool.RemoveAt(0); } } }); broadcast.Start(); }
Broadcast()方法启用了一个新线程,循环检测msgPool是否为空,当不为空的时候遍历全部客户端,调用send方法发送msgPool里面的第一条消息,而后清除该消息继续检测,直到消息广播完,其实这就是一个阉割版的观察者模式 ,顺便看一下打包数据方法
private byte[] PackageMessage(SocketMessage sm) { StringBuilder packagedMsg = new StringBuilder(); if (!sm.isLogin) //消息是login信息 { packagedMsg.AppendFormat("{0} @ {1}:\r\n ", sm.Client.Name, sm.Time.ToShortTimeString()); packagedMsg.Append(sm.Message); } else //处理普通消息 { packagedMsg.AppendFormat("{0} login @ {1}", sm.Client.Name, sm.Time.ToShortTimeString()); } return Encoding.UTF8.GetBytes(packagedMsg.ToString()); }
static void Main(string[] args) { TcpHelper helper = new TcpHelper(); helper.Run(8080); }
这样咱们就启用了server,看看简单的客户端实现,原理相似,再也不分析了
这样一个简单的支持广播地socket就完成了,咱们能够进行多个客户端聊天了,看看运行效果吧
其实socket编程没有一开始我想象的那么难,重要的仍是搞明白原理,接下来事情就迎刃而解了,这个简单的server还有很多待完善之处,主要是展现一下C# socket编程基本使用,为下一步作websocket server作准备,实习二者很类似,只是websocket server 添加了协议处理部分,这两天会尽快分享出来
感兴趣的同窗能够看看源码 (注释是我写博客的时候加上的,源码中没有,无论看过博客的人应该没问题)
出处:http://www.cnblogs.com/dolphinX/archive/2013/12/07/3462496.html