专栏首页Unity TechnologyJtro的技术分享:游戏客户端与服务器(c#)通讯_异步Socket

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;
        /// <summary>
        /// 构造函数
        /// </summary>
        public Conn()
        {
            readBuff = new byte[BUFFER_SIZE];
        }
        /// <summary>
        /// 初始化
        /// 初始化方法,在启用一个连接的时候会调用该方法,从而给一些变量赋值
        /// </summary>
        /// <param name="socket"></param>
        public void Init(Socket socket)
        {
            this.socket = socket;
            isUse = true;
            buffCount = 0;
        }
        /// <summary>
        /// 缓冲区剩余的字节数
        /// </summary>
        public int BuffRemain()
        {
            return BUFFER_SIZE - buffCount;
        }
        /// <summary>
        /// 获得客户端地址
        /// 调用socket.RemoteEndPoint获取客户端的IP地址和端口
        /// </summary>
        /// <returns></returns>
        public string GetAdress()
        {
            if (!isUse)
            {
                return "无法获得地址";
            }
            else
            {
                return socket.RemoteEndPoint.ToString();
            }
        }
        /// <summary>
        /// 关闭
        /// 调用socket.Close()关闭这条连接
        /// </summary>
        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;        //最大连接数
/// <summary>
/// 获取连接池索引,返回负数就表示获取失败
/// </summary>
/// <returns></returns>
        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个组件:一个输入框,一个按钮

界面展示.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
        {

        }
    }

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

开启服务器.PNG

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

2个客户端连接服务器.PNG

然后测试发送消息

注意!!!!!

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

聊天界面.PNG

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Jtro的技术分享:游戏模式之命令模式

    命令模式 命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以...

    LittleU
  • Jtro的技术分享:unity的串口通讯

    串行接口(串口)通常指COM接口,是采用串行通信方式的扩展接口。串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线...

    LittleU
  • Jtro的技术分享:超高效率的遍历

    在c#中提到遍历,你会想到什么? for循环? foreach? 今天要告诉你的是:Parallel.For

    LittleU
  • 第三阶段-Java常见对象:【第十一章 Date、DateFormat和Calendar类】

    可以进行日期和字符串的格式化和解析,但是由于是抽象类,所以使用具体子类SimpleDateFormat。

    BWH_Steven
  • 【编程题】Java编程题五(10道)

    【编程题】Java编程题六(10道) 【程序41】 题目:海滩上有一堆桃子,五只猴子来分。第一只猴子把这堆桃子凭据分为五份,多了一个,这只猴子把多的一个扔入海...

    Java帮帮
  • Java期末 简答题(论述题)以及几个编程题(常考)

    1Java继承的关键字:extends 2优势: • 提高了代码的复用性; • 提高了代码的维护性; • 建立了类与类之间的关系,这个是多态的前提。 ...

    CaesarChang张旭
  • 重构

    最近公司做了个项目,深深体会到架构设计以及代码优化有多么的重要。 回头看自己的代码都觉得特别混乱,有时候还要看很久才能看懂,可扩展性特别差,完全是为了完成需求而...

    用户3467126
  • Effective Java(一)

    对于类而言,为了让客户端获取它自身的一个实例,最传统的方法就是提供一个公有的构造器。还有一种方法,也应该在每个程序员的工具箱中占有一席之地。类可以提供一个公有的...

    Remember_Ray
  • 写不好规范Java代码怎么去大厂

    解决办法 :引入Builder模式 场景:当构造器有5个或者以上的构造参数时或者目前参数不多但是以后会不断增多的时候。demo 如下:

    sowhat1412
  • 第三阶段-Java常见对象:【第八章 System类】

    System.gc() 可用于垃圾回收.当使用System.gc() 回收某个对象所占用的内存之前,通过要求程序调用适当的方法来清理资源,在没有明确指定资源清理...

    BWH_Steven

扫码关注云+社区

领取腾讯云代金券

,,