网络基础(四)异步模式,服务端部分

异步模式下,服务器可以使用BeginAccept 和EndAccept 方法完成连接到客户端的任务。

1.BeginAccept 的函数原型: BeginAccept(AsyncCallback asynCallback, Ojbect state)   //asynCallback代表回调函数;state表示状态信息,必须保证state中包含socket的句柄

调用BeginAccept后,程序将继续执行而不是阻塞在该语句上。等客户端连接上后,回调函数asynCallback将被执行。

在回调函数中,开发者可以使用EndAccept获取新客户端的套接字,还可以通过AsyncState获取state参数传入的数据。

使用方法如下:

listenfd.BeginAccept(AcceptCb, null);

private void AcceptCb(IAsyncResult ar)

{

    Socket socket = listenfd.EndAccept(ar); 

    //接收消息

}

2.BeginReceive实现的是异步数据接收

public IAsyncResult BeginReceive( byte[] buffer,  int offset,   int size,  SocketFlags socketFlags,  AsyncCallback callback, object state );

 

3.服务端要处理多个客户端消息,需要用一个数组来维护所有客户端的连接。每个客户端都有自己的Socket和缓冲区。编写Conn类来表示客户端连接,类的成员说明如下

4.编写服务端主体结构Serv类,它包含一个Conn类型的对象池(数组),用于维护客户端连接。

NewIndex 方法找出对象池中尚未使用的元素下标。

在start方法中,服务器经历Socket、Bind、Listen,然后调用BeginAccept 开始异步处理客户端的连接。

5.在MainClass.Main中创建Serv类的实例并开启服务器。

====服务端代码如下

using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Collections.Generic;  //包含定义泛型集合的接口和类,设置动态数组

namespace test2   //自己随意取的文件名
{

  public class Conn    //服务端要处理多个客户端消息,需要一个数组来维护所有客户端的连接。Conn类来表示客户端连接
  {
  //常量
  public const int BUFFER_SIZE = 1024;    //缓冲区大小
  //Socket
  public Socket socket;
  //是否使用
  public bool isUse = false;       //指明该对象是否被使用
  //Buff
  public byte[] readBuff = new byte[BUFFER_SIZE];      //读缓冲区
  public int buffCount = 0;      //当前缓冲区的长度
  //构造函数
  public Conn()
  {
    readBuff = new byte[BUFFER_SIZE];
  }
  //初始化
  public void Init(Socket socket)        //初始化方法,在启用一个连接时会调用该方法,从而给一些变量赋值
  {
    this.socket = socket;           
    isUse = true;
    buffCount = 0;
  }
  //缓冲区剩余的字节数
  public int BuffRemain()
  {
    return BUFFER_SIZE - buffCount;
  }
  //获取客户端地址  
  public string GetAdress()
  {
    if (!isUse)
      return"无法获取地址";
    return socket.RemoteEndPoint.ToString();    //调用socket.RemoteEndPoint获取客户端的IP地址和端口
  }
  //关闭
  public void Close()
  {
    if (!isUse)     //对象未被使用,则直接返回
      return;
    Console.WriteLine ("[断开连接]" + GetAdress ());
    socket.Close (); //关闭连接
    isUse = false;
  }
  }


  public class Serv    //主体结构,包含一个Conn类型的对象池(数组),用于维护客户端连接
  {
    //监听嵌套字
    public Socket listenfd;
    //客户端连接
    public Conn[] conns;    //数组,代表客户端连接列表(对象池)
    //最大连接数
    public int maxConn = 50;

    //获取连接池索引,返回负数表示获取失败
    public int NewIndex()       //遍历数组,找出对象池中尚未使用的元素下标
    {
      if (conns == null)
        return -1;
      for (int i=0;i<conns.Length;i++)
      {
        if (conns[i] == null)       //对象还未初始化,则可用,返回下标
        {
          conns[i]=new Conn();
          return i;
        }
        else if (conns[i].isUse == false)       //对象还未使用,则可用,返回下标
        {
          return i;
        }
      }
      return -1;     //最后找不到可用的,则返回-1
    }
    //开启服务器
    public void Start(string host,int port)
    {
      //连接池,核心思想是连接复用,
      conns = new Conn[maxConn];      //初始化连接池
      for (int i=0;i < maxConn ; i++)
      {
         conns[i] = new Conn();
      }
      //开启Socket连接流程:1.Socket
      listenfd = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
      //2.Bind
      IPAddress ipAdr = IPAddress.Parse(host);
      IPEndPoint ipEp = new IPEndPoint(ipAdr,port);
      listenfd.Bind(ipEp);
      //3.Listen
      listenfd.Listen(maxConn);
      //4.BeginAccept
      listenfd.BeginAccept(AcceptCb,null);        //AcceptCb是BeginAccept的回调函数
      Console.WriteLine("[服务器]启动成功");
    }
  

    //AcceptCb 回调,做三件事情:1.给新的连接分配conn;2.异步接收客户端数据;3.再次调用BeginAccept实现循环。
    private void AcceptCb(IAsyncResult ar)
    {
      try
      {
        Socket socket = listenfd.EndAccept(ar); //获取新客户端的套接字
        int index = NewIndex();  

        if (index<0)     //如果连接池已满,则拒绝连接
        {
          socket.Close();
          Console.WriteLine("[警告]连接已满");
        }
        else
        {
          Conn conn = conns[index]; //给新的连接分配
          conn.Init(socket);
          string adr = conn.GetAdress();
          Console.WriteLine("客户端连接[" + adr +"]conn池ID: " +index);
          conn.socket.BeginReceive(conn.readBuff,conn.buffCount,conn.BuffRemain(),SocketFlags.None,ReceiveCb,conn);     //异步接收客户端数据
        }
        listenfd.BeginAccept(AcceptCb,null); //再次调用BeginAccept实现循环
      }
      catch(Exception e) //异常发生,则抛出错误
      {
        Console.WriteLine ("AcceptCb 失败:" + e.Message);
      }
    } 

   //ReceiveCb 回调,做三件事情:1.接收并处理消息,因为是多人聊天,服务端接收到消息后,要把它转发给所有人;2.如果收到客户端关闭连接的信号(count=0),则断开连接;3.继续调用BeginReceive接收下一个数据。

   private void ReceiveCb(IAsyncResult ar)
   {
      Conn conn = (Conn)ar.AsyncState;       //获取BeginReceive传入的Conn对象
      try
      {
        int count = conn.socket.EndReceive(ar);      //获取接收的字节数
        //关闭信号
        if(count <= 0)
        {
          Console.WriteLine("收到[" + conn.GetAdress() +"]断开连接");
          conn.Close();
          return;
        }
        //数据处理
        string str = System.Text.Encoding.Default.GetString(conn.readBuff,0,count);
        Console.WriteLine("收到 [" + conn.GetAdress() + "]数据:" +str);
        str = conn.GetAdress() + ":" + str;
        byte[] bytes = System.Text.Encoding.Default.GetBytes(str);
        //广播
        for(int i=0;i<conns.Length;i++) //将消息发送给所有正在使用的连接
        {
          if (conns[i] == null)
            continue;
          if(!conns[i].isUse)
            continue;
          Console.WriteLine("将消息转播给" + conns[i].GetAdress());
          conns[i].socket.Send(bytes);
        //继续接收
        conn.socket.BeginReceive(conn.readBuff,conn.buffCount,conn.BuffRemain(),SocketFlags.None,ReceiveCb,conn); //继续接收,实现循环
        }
      }
      catch(Exception e)
      {
        Console.WriteLine("收到[" + conn.GetAdress()+"]断开连接");
        conn.Close();
      }
    }
  }

  class MainClass
  {
    public static void Main (string[] args)
    {
      Console.WriteLine ("Hello World!");
      Serv serv = new Serv();
      serv.Start ("127.0.0.1", 1234); //开启服务器

      while (true)
      {
        string str = Console.ReadLine ();
        switch (str)
        {
          case "quit":
            return;
        }
      }
    }
  }
}

备注:

回调函数:如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

 

转载于:https://www.cnblogs.com/bainbian1234/p/11132733.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值