C#实现异步Socket通信(控制台)

     同步模式下,服务器使用Accept接受连接请求,客户端使用Connect连接服务器。相对地,在异步模式下,服务器可以使用BeginAccept和EndAccept方法 完成连接到客户端的任务

1 服务端

BeginAccept(AsyncCallback asyncCallBack,Object state);

参数 说明
asyncCallBack 代表回调函数
state 表示状态信息,必须保证state中包含socket句柄

Socket EndAccept(IAsyncResult asyncResult)写在回调函数中,用于获取客户端套接字

调用该函数后,程序继续执行,有客户端连接上,回调函数会执行。
IAsyncResult BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state);

参数 说明
buffer Byte类型数组,用于存储接受到的数据
offset buffer参数中存储数据的位置,该位置应该从零开始技术
size 最多接受的字节数
socketFlags SocketFlags值的按位组合,这里设置为0
callback 回调函数,一个AsynCallback委托
state 一个用户定义对象,其中包含接受操作的相关信息。递给EndReceiv委托

int EndReceive(IAsyncResult asyncResult);写在BeginReceive函数中,用于得到接收数据的大小。
异步服务端如图:
在这里插入图片描述
     服务端需要处理多个客户端消息,所以需要一个数组维持这些客户端的连接。每个客户端都有自己的名字,Socket和缓冲区。所以定义Conn类来描述客户端连接,然后用Conn数组维持所有客户端的连接。

using System;
using System.Net;
using System.Net.Sockets;

namespace Server
{
    class Conn
    {
        public const int BUFFER_SIZE = 1024;
        public Socket socket;
        public bool isUse = false;//该客户端是否在使用中
        public byte[] readBuff = new byte[BUFFER_SIZE];//内容缓冲区
        public int buffCount = 0;//当前缓冲区数据字节量
        public string m_name = "";//客户端名字
        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()//获取客户端套接字地址  ip:端口
        {
               if (!isUse)
                return "无法获取地址";
            return socket.RemoteEndPoint.ToString();
        }
        public void close()//关闭客户端
        {
            if(!isUse)
                return;
            Console.WriteLine("[断开连接]" + GetAdress());
            socket.Close();
            isUse = false;
        }
    }
    class Serv
    {
        public Socket listenfd;//服务端套接字
        public Conn[] conns;//连接池
        public int maxConn = 50;//最大连接数
        public int NewIndex()//从连接池获取一个没在使用的Conn的下标
        {
            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;
        }
        public void Start(string host, int port)//初始化连接池,开始监听端口
        {
            conns = new Conn[maxConn];
            for (int i = 0; i < maxConn; i++)
            {
                conns[i] = new Conn();
            }
            listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ipAdr = IPAddress.Parse(host);
            IPEndPoint ipEp = new IPEndPoint(ipAdr, port);
            listenfd.Bind(ipEp);
            listenfd.Listen(maxConn);
            listenfd.BeginAccept(AcceptCb, null);
            Console.WriteLine("[服务器]启动成功");

        }
        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);//继续监听,等待客户端连接,
                }

            }
            catch (System.Exception ex)
            {
                Console.WriteLine("AcceptCb失败:" + ex.Message);
            }
        }
        private void ReceiveCb(IAsyncResult ar)//回调函数 接收到客户端内容进行调用
        {
            Conn conn = (Conn)ar.AsyncState;
            try
            {
                int count = conn.socket.EndReceive(ar);//获取客户端内容的字节数
                if (count <= 0)
                {
                    Console.WriteLine("收到 【" + conn.m_name + "】断开连接 ");
                    conn.close();
                    return;
                }
                string str = System.Text.Encoding.Default.GetString(conn.readBuff, 0, count);
                //将客户端名字和消息内容分割开  yar:hello  =》yar  hello
                string[] arr = str.Split(':');
                string name = "";
                int t = 0;
                foreach (string s in arr)
                {
                    if (t == 0) name = s ;
                    t = 1;
                    str = s;
                }
                conn.m_name = name;



                Console.WriteLine("收到 【" + conn.m_name + "】数据: " + str);
                byte[] bytes = System.Text.Encoding.Default.GetBytes(conn.m_name+":"+str);
                for (int i = 0; i < conns.Length; i++)//转播给其他正在使用的客户端
                {
                    if (conns[i] == null)
                        continue;
                    if (!conns[i].isUse)
                        continue;
                    Console.WriteLine("将消息转播给" + conns[i].m_name);
                    conns[i].socket.Send(bytes);
                }
                conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn);//继续接收该客户端的数据
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("收到 【" + conn.m_name + "】断开连接 ");
                conn.close();
                return;
            }
        }
    }
    class Program
    {

       
        static void Main(string[] args)
        {
            Serv serv = new Serv();
            serv.Start("127.0.0.1", 1234);
            while (true)
            {
                string str = Console.ReadLine();
                switch (str)
                {
                    case "quit":
                        return;
                }
            }


        }


    }

}

2 客户端

客户端示意图:
在这里插入图片描述

using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;

namespace Client
{
    class Cli
    {
        string m_name = "无名";
        Socket socket;
        const int BUFFER_SIZE = 1024;
        public byte[] readBuff = new byte[BUFFER_SIZE];
        public Cli(String name)
        {
            m_name = name;
        }
        public void Connection()
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            string host = "127.0.0.1";
            int port = 1234;
            socket.Connect(host, port);
           // socket.LocalEndPoint.ToString();
            socket.BeginReceive(readBuff,0,BUFFER_SIZE,SocketFlags.None,ReceiveCb,null);
        }
        private void ReceiveCb(IAsyncResult ar)
        {
            try
            {
                int count = socket.EndReceive(ar);
                string str = System.Text.Encoding.Default.GetString(readBuff, 0, count);
                Console.WriteLine(str);
                socket.BeginReceive(readBuff, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCb, null);

            }
            catch (System.Exception ex)
            {
                socket.Close();
            }
            
        }
        public void Send(string str)
        {
            
            byte[] bytes = System.Text.Encoding.Default.GetBytes(m_name+":"+str);
            try
            {
                socket.Send(bytes);
            }
            catch { }
        }
        public static void Main(string[] args)
        {
            Console.Write("请输入你的名字:");
            string username = Console.ReadLine();
            Cli cli = new Cli(username);
            cli.Connection();
            while (true)
            {
                string str = Console.ReadLine();
                switch (str)
                {
                    case "quit":
                        return;
                }
                cli.Send(str);
            }


        }
    }
}

3 运行结果展示

服务端启动:
在这里插入图片描述
启动两个客户端:
在这里插入图片描述
在这里插入图片描述
服务端显示客户端已经连接:
在这里插入图片描述
客户端发送信息:
在这里插入图片描述

4 总结

       之前写了一篇同步通信,有诸多弊端。现在写了一篇异步通信,可以看成简单的即时群聊程序其实就是调不一样的函数而已。这个程序现在可以进行多个客户端同时连接,一个客户端发送信息,其他客户端都能收到信息;但是还有些缺点,比如,没有图形化界面,没有用户登录,存在客户端长时间不发送消息占用连接池资源等。后续会出用于检测客户端在线状态的心跳机制,连接数据库实现用户登录,聊天记录查询,添加图形化界面。说不住它就是下一个QQ呢。有感兴趣者,自己可以实现这些功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章