Jtro的技术分享:游戏客户端与服务器(c#)通讯_异步Socket

首先跟大家道个歉,上一个同步Socket文章里用的不是Markdown编写的,所以代码看起来不是很清爽,我用的鹅厂的浏览器,终于发现是浏览器的锅,图片拖不上去 -_-|| , 真的是很失败啊,现在好了,已经下载了火狐浏览器,编辑什么的都很好用,向大家推荐一下。不知道火狐浏览器能不把鹅厂的书签迁移过来呢。。。。

------------------------------------------------------------------------------------分割线-------------------------------------------------------------------------------
在同步模式中,服务器使用Accpet接收连接请求,客户端使用Connect连接服务器。同步模式中,如果没有客户端连接的话,它会卡在accpet处,而异步就很好的避免了此类问题。接下来,我通过查找资料和询问大神,实现了一个聊天室的功能,接下来将具体的来实现这个功能。
首先,用到新的函数: BeginAccept 、新的类:Conn
在服务器端程序中添加Conn的类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Server
{
    class Conn
    {
        public const int BUFFER_SIZE = 1024;            //常量
        public Socket socket;                           //socket
        public bool isUse;                              //是否使用
        public byte[] readBuff = new byte[BUFFER_SIZE]; // Buff
        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;
        }
        /// 
        /// 获得客户端地址
        /// 调用socket.RemoteEndPoint获取客户端的IP地址和端口
        /// 
        /// 
        public string GetAdress()
        {
            if (!isUse)
            {
                return "无法获得地址";
            }
            else
            {
                return socket.RemoteEndPoint.ToString();
            }
        }
        /// 
        /// 关闭
        /// 调用socket.Close()关闭这条连接
        /// 
        public void Close()
        {
            if (!isUse)
                return;
            Console.WriteLine("[ 断开连接 ]"+GetAdress ());
            socket.Close();
            isUse = false;

        }


    }
}

修改服务器端程序:

/*
 * 脚本功能:服务器
 * 作者       :张曙光
 * 日期       :2017.11.15
 */
using System;
using System.Net;                                                 //引入命名空间 
using System.Net.Sockets;                                         //引入命名空间
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Server
{
    class Serv
    {
        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;
        }
        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,    //Socket
                SocketType .Stream ,ProtocolType.Tcp);

            IPAddress ipAdr = IPAddress.Parse(host);              //Start Bind
            IPEndPoint ipEp = new IPEndPoint(ipAdr ,port );
            listenfd.Bind(ipEp);                                  //End Bind  
            listenfd.Listen(maxconn );                            //Listen
            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("[ 警告 ]连接已满");
                }
                //如果连接池未满,那就连接分配新的conn
                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 (Exception e)
            {
                Console.WriteLine("AcceptCn失败 :"+e .Message);

            }
        }
    

服务器端接收回调函数

    private void ReceiveCb(IAsyncResult ar)
        {
            Conn conn = (Conn)ar.AsyncState;
            try
            {
                int count = conn.socket.EndReceive(ar);
                //关闭信号
                if (count <=0)
                {
                    Console.WriteLine(" 收到 ["+conn .GetAdress ()  +" ] 断开连接");
                    conn.Close();
                    return;
                }
                //数据处理
                string str = System.Text.Encoding.UTF8.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 () +"] 断开连接");
                Console.WriteLine(e.Message);
                conn.Close();
            }
        }

服务器端开启服务端

  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;
                }
            }
        }

到这里,只是将服务器端的事做完了,接下来就是客户端了,客户端改动的很少的一部分
首先打开同步Socket的工程,添加2个组件:一个输入框,一个按钮


Jtro的技术分享:游戏客户端与服务器(c#)通讯_异步Socket_第1张图片
界面展示.PNG

在net的脚本中改写connetion的方法:

public void Btn_Connetion()
    {
        //清空text
        RecvText.text = "";
        //Socket
        socket = new Socket(AddressFamily .InterNetwork,
            SocketType.Stream ,ProtocolType.Tcp);
        //Connect
        string host = HostInput.text;
        string strport = PortInput.text;
        int port = int .Parse(strport);
        socket.Connect(host ,port);
        ClientText.text = "客户端地址 " + socket.LocalEndPoint.ToString();

        //Recv
        socket.BeginReceive(readBuff ,0,BUFFER_SIZE ,SocketFlags.None,ReceiveCb ,null);
    }

当收到服务器端发来的消息的时候,异步接收回调启用,开启异步接收的代码如下:

private void ReceiveCb(IAsyncResult ar)
    {
        try
        {
            //count 是接收数据的大小
            int count = socket.EndReceive(ar);
            //数据处理
            string str = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
            if (recvStr.Length > 300) recvStr = "";
            recvStr += str + "\n";
            //继续接收
            socket.BeginReceive(readBuff, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCb, null);
        }
        catch (Exception)
        {
            RecvText.text += "连接已断开";
            socket.Close(); 
        }
    }

然后编写Send函数,向服务器发送消息

 public void Send()
    {
        string str = textinput.text;
        byte[] bytes = System.Text.Encoding.Default.GetBytes(str);
        try
        {
            socket.Send(bytes);
        }
        catch
        {

        }
    }

然后调试一下,没有错误之后,打包测试。首先开启服务器:


Jtro的技术分享:游戏客户端与服务器(c#)通讯_异步Socket_第2张图片
开启服务器.PNG

然后打开打包好的客户端,打开两次并输入ip地址和端口号,点击连接,连接服务器


Jtro的技术分享:游戏客户端与服务器(c#)通讯_异步Socket_第3张图片
2个客户端连接服务器.PNG

然后测试发送消息
注意!!!!!

只能发送英文,中文发送只能是乱码,这是正常的,因为编码的问题。
运行之后的图:


Jtro的技术分享:游戏客户端与服务器(c#)通讯_异步Socket_第4张图片
聊天界面.PNG

OK,感谢,感谢读完的你。

你可能感兴趣的:(Jtro的技术分享:游戏客户端与服务器(c#)通讯_异步Socket)