C#网络通信

搭建方式主要可以分为同步与异步两种。同步相对来说会更简单直接暴力,但因为会造成线程阻塞,所以适用性不高,但从学习入门角度来看,可以更简单直接地了解整个过程。

同步Socket程序:

服务器遵照Socket基本流程,首先创建Socket,再调用Bind绑定IP和Port,然后调用Listen等待客户端连接,最后在一个无限循环中调用Accept接收客户端,并且再进行后序操作。大致的代码如下:

class ServerTest
    {
        static void Main(string[] args)
        {
            //Socket
            Socket listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);


            //Bind
            IPAddress ip = IPAddress.Parse("127.0.0.1");
            IPEndPoint ep = new IPEndPoint(ip, 8742);
            listenfd.Bind(ep);


            //Listen
            listenfd.Listen(0);     // 0代表当连接数满了的时候的菊花队列
            Console.WriteLine("服务器启动成功");


            while(true)
            {
                //Accept
                Socket conn = listenfd.Accept();
                Console.WriteLine("服务器接收客户端");


                //Receive
                byte[] readBuff = new byte[1024];
                int count = conn.Receive(readBuff);
                string str = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
                Console.WriteLine("服务器接收信息成功");


                //Send,对接收信息稍作修改然后发出给客户端
                byte[] bytes = System.Text.Encoding.Default.GetBytes("from server..." + str);
                conn.Send(bytes);
            }
        }
    }

---------------------------------------------------------------------

上述代码实现了一个同步通信的过程,但是实用性并不高,当执行某一个线程阻塞的方法时程序就会一直处于卡死状态,直到得到了客户端的回应才会继续执行,这显然不符合使用要求,因此需要使用多线程的方式对此过程进行修改,某些会导致线程阻塞的方法需要开一条新的线程去执行,这样每当辅助线程因调用了某些阻塞方法处于等待状态时,主线程依然可以继续进行其他工作,并不影响程序运行。

异步Socket程序的代码如下:

服务端:

namespace NetworkBaseTest
{
    class UserToken
    {
        public const int BUFFER_SIZE = 1024;
        public Socket socket;
        public bool isUsed = false;
        public byte[] readBuff;//= new byte[BUFFER_SIZE];       不知有什么意义,这里和构造函数可能冲突了
        public int buffCount = 0;
        
        public int BuffRemain
        {
            get { return BUFFER_SIZE - buffCount; }
        }


        public string GetAddress
        {
            get
            {
                if(!isUsed)
                {
                    return "无法获取地址";
                }
                return socket.RemoteEndPoint.ToString();
            }
        }


        public UserToken()
        {
            readBuff = new byte[BUFFER_SIZE];
        }


        public void Init(Socket socket)
        {
            this.socket = socket;
            isUsed = true;
            buffCount = 0;
        }


        public void Close()
        {
            if (!isUsed) return;


            Console.WriteLine("[断开连接]" + GetAddress);
            socket.Close();
            isUsed = false;
        }
    }
}

--------------

namespace NetworkBaseTest
{
    class Server
    {
        //监听套接字
        public Socket listenfd;
        //客户端连接
        public UserToken[] tokens;
        //最大连接数
        public int maxTokens = 50;


        //获取连接池索引,返回负数表示获取失败
        public int NewIndex
        {
            get
            {
                if (tokens == null) return -1;


                //此例子运用数组作为容器不够好,每次都要遍历获取可用token,
                //如果使用Stack会更合适,因为不需要再遍历
                for (int i = 0; i < tokens.Length; i++)        
                {
                    if(tokens[i]==null)
                    {
                        tokens[i] = new UserToken();
                        return i;
                    }
                    else if(tokens[i].isUsed==false)
                    {
                        return i;
                    }
                }


                return -1;
            }
        }


        //开启服务器
        public void Start(string host, int port)
        {
            //初始化连接池
            tokens = new UserToken[maxTokens];
            for (int i = 0; i < maxTokens; i++)
            {
                tokens[i] = new UserToken();
            }


            //Socket
            listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);


            //Bind
            IPAddress ipAdr = IPAddress.Parse(host);
            IPEndPoint ipEP = new IPEndPoint(ipAdr, port);
            listenfd.Bind(ipEP);


            //Listen
            listenfd.Listen(10);            //当连接数满了后,能够处于菊花阵的人数为10人队列


            //Accept
            listenfd.BeginAccept(AcceptCb, null);           //这里的null为什么不是listenfd自身
            Console.WriteLine("[服务器] 启动成功");
        }


        /// <summary>
        /// Accept 回调  当服务器Accept到客户端的时候,就会回调此函数
        /// </summary>
        /// <param name="ar"></param>
        private void AcceptCb(IAsyncResult ar)
        {
            try
            {
                //名为listenfd的Socket对象永远只负责Accept客户端,每次Accept到一个都会返回一个新的Socket
                //也就是通过EndAccept方法返回,我们只能通过这个返回的Socket对象来获取客户端信息
                Socket socket = listenfd.EndAccept(ar);
                
                //在tokens池中寻找空余位置供客户端使用
                int index = NewIndex;
                if(index<0)
                {
                    socket.Close();
                    Console.WriteLine("[警告] 连接已满");
                }
                else
                {
                    UserToken token = tokens[index];
                    token.Init(socket);
                    string adr = token.GetAddress;
                    Console.WriteLine("客户端连接 ["+adr+"] tokens池ID:" +index);


                    //开始异步接收客户端消息
                    token.socket.BeginReceive(token.readBuff, token.buffCount, token.BuffRemain, SocketFlags.None, ReceiveCb, token);
                }


                //初步推断此方案应该是服务器接收了客户端后,就把信息保存在tokens池中
                //然后重新调用BeginAccept方法继续工作,按照这种流程应该是一个一个地来接收的
                //因此同一时间只有一个BeginAccept在执行
                listenfd.BeginAccept(AcceptCb, null);
            }
            catch(Exception e)
            {
                Console.WriteLine("AcceptCb失败:"+e.Message);
            }
        }


        //Receive 回调
        private void ReceiveCb(IAsyncResult ar)
        {
            UserToken token = ar.AsyncState as UserToken;


            try
            {
                int count = token.socket.EndReceive(ar);
                //关闭信号  约定了如果能接收信息,但是长度为0的话就是客户端的断开信号
                if(count<=0)
                {
                    Console.WriteLine("收到 [" + token.GetAddress + "] 断开连接");
                    token.Close();
                    return;
                }


                //数据处理
                //收到了客户端1的消息
                string str = Encoding.UTF8.GetString(token.readBuff, 0, count);
                Console.WriteLine("收到 [" + token.GetAddress + "] 数据:" + str);


                str = token.GetAddress + ":" + str;
                byte[] bytes = Encoding.Default.GetBytes(str);


                //广播
                //把客户端1的消息转发给所有的客户端
                for (int i = 0; i < tokens.Length; i++)
                {
                    if (tokens[i] == null)
                        continue;
                    if (!tokens[i].isUsed)
                        continue;


                    Console.WriteLine("将消息转播给"+tokens[i].GetAddress);


                    tokens[i].socket.Send(bytes);
                }


                //继续接收      (类似递归)
                token.socket.BeginReceive(token.readBuff, token.buffCount, token.BuffRemain, SocketFlags.None, ReceiveCb, token);
            }
            catch(Exception e)
            {
                Console.WriteLine("收到 ["+token.GetAddress+"] 断开连接");
                token.Close();
            }
        }
    }
}

------------

namespace NetworkBaseTest
{
    class Program
    {
        static void Main(string[] args)
        {
            
            Console.WriteLine("Hello World!");
            Server server = new Server();
            string ip = "127.0.0.1";
            int port = 1889;
            server.Start(ip, port);


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


            
        }
    }
}

------------------

客户端:

客户端的代码在Unity中实现:以网络聊天室为例子

public class ClientNet : MonoBehaviour 
{
    //服务器IP和端口
    public InputField input_host;
    public InputField input_port;
    //显示客户端收到的消息
    public Text txt_recv;
    public string recv;
    //显示客户端IP和端口
    public Text txt_clientip;
    //聊天输入框
    public InputField input_text;
    //Socket和接收缓冲区
    private Socket socket;
    private const int BUFFER_SIZE = 1024;
    public byte[] readBuff = new byte[BUFFER_SIZE];


    //C#使用线程池处理异步调用,所以ReceiveCb并不在主线程中,但只有主线程可以
    //设置UI组件,因此ReceiveCb只设置字符串recv,再由主线程Update方法处理UI组件
    private void Update()
    {
        txt_recv.text = recv;
    }


    public void OnConnectClick()
    {
        //清理text
        txt_recv.text = "";
        //Socket
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);


        //Connect
        string host = input_host.text;
        int port = int.Parse(input_port.text);
        socket.Connect(host, port);
        txt_clientip.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 (recv.Length > 300) recv = "";
            recv += str + "\n";


            //继续接收
            socket.BeginReceive(readBuff, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCb, null);
        }
        catch(Exception e)
        {
            txt_recv.text += "连接已断开";
            socket.Close();
        }
    }


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

-----------------------------------------------------

关于异步Socket的实现方法不止一种,也可以通过自行封装SocketAsyncEventArgs构建出其他流程。


异步通信的大致流程如上图所示

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页