zoukankan      html  css  js  c++  java
  • 基于UDP协议的Socket服务器

         上篇文章介绍了自己开发的基于TCP协议的服务器,也具有一定的扩展性;既然做了TCP的,那自然就得把UDP的也研究下,因此本篇文章将探讨下基于UDP协议的服务器。

           废话少说,下面开始介绍思路,由于UDP是无连接的,所以相对于TCP协议的服务器就简单很多,不需要去监听客户端,也不需要对会话进行维护,只需要对数据进行相应的处理就可以;因此整体思路就很明显了,用异步方式来接收客户端的数据,将数据放入队列中,用一个线程组来处理队列中的数据,每次从队列中取一个数据包进行处理,对于线程组中线程数可根据实际情况设定,首先给出成员定义:

    View Code
    private Socket server = null;  
    private bool serverStart = false;//服务器是否启动
    private int serverPort = 3130;//服务器端口
    private int clientPort = 3131;//远程客户端端口
    private IPAddress localIp;//服务器绑定的IP地址
    private string brodcastIp;//广播IP地址

    private byte[] tempReceiveData = null;//临时数据接收器
    private EndPoint tempRemotePoint = null;//远程客户端
    private Queue<BufferData> receivedBuffer;//数据缓冲区

    private Thread[] threadDataHandler;//数据处理线程
    private int threadDataHandlerNum = 1;//数据处理线程数

    private IDataHandler iRceivedDataHandler = null;//数据处理接口

    private Mutex m_ServerMutex; // 只能有一个服务器

    启动服务器后,绑定端口,启动数据处理线程组,开始异步接收数据:

    View Code
    /// <summary>   
    /// 启动服务器
    /// </summary>
    public void Start()
    {
    if (this.serverStart)
    {
    return;//服务器已经启动
    }
    this.server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    this.tempReceiveData = new byte[this.server.ReceiveBufferSize];
    IPAddress[] ip = Dns.GetHostAddresses(Dns.GetHostName());
    IPEndPoint addr = new IPEndPoint(ip[0], this.serverPort);
    this.localIp = ip[0];
    //算出广播IP地址
    string str = this.localIp.ToString();
    int len = str.LastIndexOf('.');
    str = str.Substring(0, len + 1);
    this.brodcastIp = str + "255";
    //绑定IP,端口
    this.server.Bind(addr);
    this.serverStart = true;
    this.threadDataHandler = new Thread[this.threadDataHandlerNum];
    for (int i = 0; i < this.threadDataHandlerNum; i++)
    {
    this.threadDataHandler[i] = new Thread(new ThreadStart(this.HandlerReceivedData));
    this.threadDataHandler[i].Start();
    }
    this.AsyncBeginReceive();//开始接收数据
    }

    异步接收数据及其回调函数:

    View Code
    /// <summary>   
    /// 异步接收数据
    /// </summary>
    private void AsyncBeginReceive()
    {
    try
    {
    if (this.serverStart)
    {
    this.server.BeginReceiveFrom(this.tempReceiveData, 0, this.server.ReceiveBufferSize, SocketFlags.None, ref this.tempRemotePoint, this.AsyncReceiveCallback, this);
    }
    }
    catch { }
    }
    /// <summary>
    /// 异步接收数据的回调函数
    /// </summary>
    /// <param name="iar"></param>
    private void AsyncReceiveCallback(IAsyncResult iar)
    {
    try
    {
    iar.AsyncWaitHandle.Close();
    if (this.serverStart)
    {
    AsyncBeginReceive();
    int byteReadLen = this.server.EndReceiveFrom(iar, ref this.tempRemotePoint);
    byte[] temp = new byte[byteReadLen];
    Array.Copy(this.tempReceiveData, temp, byteReadLen);
    BufferData buffer = new BufferData(this.tempRemotePoint, temp);
    this.receivedBuffer.Enqueue(buffer);
    }
    }
    catch
    { }
    }

    异步发送数据及其回调函数:

    View Code
    /// <summary>   
    /// 开始异步发送数据
    /// </summary>
    /// <param name="buffer"></param>
    private void AsyncBeginSend(IPAddress ip, string data)
    {
    try
    {
    byte[] byteData = Encoding.ASCII.GetBytes(data);
    EndPoint iep = (EndPoint)new IPEndPoint(ip, this.clientPort);
    this.server.BeginSendTo(byteData, 0, byteData.Length, SocketFlags.None, iep, new AsyncCallback(this.AsyncSendCallback), this);
    }
    catch { }
    }
    /// <summary>
    /// 开始异步发送数据
    /// </summary>
    /// <param name="ip"></param>
    /// <param name="data"></param>
    private void AsyncBeginSend(string ip, int port, string data)
    {
    try
    {
    byte[] byteData = Encoding.ASCII.GetBytes(data);
    EndPoint iep = (EndPoint)new IPEndPoint(IPAddress.Parse(ip), port);
    this.server.BeginSendTo(byteData, 0, byteData.Length, SocketFlags.None, iep, new AsyncCallback(this.AsyncSendCallback), this);
    }
    catch { }
    }
    /// <summary>
    /// 异步发送数据的回调
    /// </summary>
    /// <param name="iar"></param>
    private void AsyncSendCallback(IAsyncResult iar)
    {
    try
    {
    this.server.EndSendTo(iar);
    iar.AsyncWaitHandle.Close();
    }
    catch
    { }
    }

    缓冲区数据处理函数:

    View Code
       /// <summary>   
    /// 处理缓冲区中的数据
    /// </summary>
    /// <summary>
    /// 处理缓冲区中的数据
    /// </summary>
    private void HandlerReceivedData()
    {
    while (this.serverStart)
    {
    lock (this.receivedBuffer)
    {
    if (this.receivedBuffer.Count == 0) continue;
    BufferData buffer = this.receivedBuffer.Dequeue();
    if (this.iRceivedDataHandler != null) this.iRceivedDataHandler.ReceivedDataHandler(buffer);
    }
    }
    }

          数据处理线程组中的每个线程都会调用该函数,所以加个锁是必要的,对于数据的解析,给出了相应的数据接口,只要实现该接口即可实现其数据的处理,如向数据库存储,对数据的判断,向客户端回复等,都可以在该接口中去实现,因此具有一定的灵活性。

               数据接口很简单:

    public interface IDataHandler  
    {
    /// <summary>
    /// 数据处理接口
    /// </summary>
    /// <param name="buffer"></param>
    void ReceivedDataHandler(BufferData buffer);
    }

    对于该接口,朋友们可以自己根据需要进行扩展。

    指定客户端发送,广播,多播的实现:

    View Code
    /// <summary>   
    /// 向指定客户端发送数据
    /// </summary>
    /// <param name="ip">客户端IP</param>
    /// <param name="data">数据</param>
    public void SendToClient(IPAddress ip, string data)
    {
    if (this.serverStart)
    {
    this.server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
    this.AsyncBeginSend(ip, data);
    }
    }
    /// <summary>
    /// 向指定客户端发送数据
    /// </summary>
    /// <param name="ip"></param>
    /// <param name="data"></param>
    public void SendToClient(string ip, string data)
    {
    this.SendToClient(IPAddress.Parse(ip), data);
    }
    /// <summary>
    /// 向指定IP,指定端口的客户机发送数据
    /// </summary>
    /// <param name="ip">Ip地址</param>
    /// <param name="port">端口号</param>
    /// <param name="data">数据</param>
    public void SendToClient(string ip, int port, string data)
    {
    if (this.serverStart)
    {
    this.server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
    this.AsyncBeginSend(ip, port, data);
    }
    }
    /// <summary>
    /// 广播
    /// </summary>
    /// <param name="data"></param>
    public void Broadcast(string data)
    {
    this.SendToClient(this.brodcastIp, data);
    }
    /// <summary>
    /// 在指定端口上广播数据
    /// </summary>
    /// <param name="data">数据</param>
    /// <param name="port">端口号</param>
    public void Broadcast(string data,int port)
    {
    this.SendToClient(this.brodcastIp, port, data);
    }
    /// <summary>
    /// 多路广播
    /// </summary>
    /// <param name="ipAddress"></param>
    public void Multicast(string data)
    {
    if (this.serverStart)
    {
    this.server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
    this.AsyncBeginSend(IPAddress.Broadcast, data);
    }
    }
    /// <summary>
    /// 指定端口进行多路广播
    /// </summary>
    /// <param name="data"></param>
    /// <param name="port"></param>
    public void Multicast(string data, int port)
    {
    if (this.serverStart)
    {
    this.server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
    this.AsyncBeginSend(IPAddress.Broadcast.ToString(), port, data);
    }
    }

    希望以上代码会对有需要的朋友有些帮助,也希望和大家一起共同进步,呵呵,加油!得意奋斗 源代码下载:

    https://files.cnblogs.com/rookey/%E5%9F%BA%E4%BA%8Eudp%E5%8D%8F%E8%AE%AE%E7%9A%84socket%E6%9C%8D%E5%8A%A1%E5%99%A8.rar

  • 相关阅读:
    Web crawler study(1)
    SNMP
    Locked the resolv.conf via command chattr
    HTML 父窗口打开子窗口,并从子窗口返回值
    混合语言学习(一)
    Auto Scale TextView Text to Fit within Bounds
    Android SeekBar size
    Android设置全局字体
    PopupMenu使用,PopupMenu背景的设置
    Android-屏幕适配全攻略
  • 原文地址:https://www.cnblogs.com/rookey/p/2336188.html
Copyright © 2011-2022 走看看