Socket通信

今天没事,自己做了一个非常非常简易的socket测试demo,我们就来最通俗的聊天室好了。由于是最简化版的,我放弃了传输协议,只是自己定义一个最简单的结构体,也放弃了其他好的东西,只有socket使用!

首先是server代码,我使用的是Winform,好歹有个界面不是:

网络,模块与辅助工具:

/**
 * Author: Garsonil
 * Des:
 * Time:
 */
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Timers;

namespace NetModule
{
    /// <summary>
    /// 服务器
    /// </summary>
    public class Server : IDisposable
    {
        private Socket socket;
        private Dictionary<int, SocketUtil> clientSockets;
        private int clientId;

        public Server(string ip, int port)
        {
            Start(ip, port);
            clientSockets = new Dictionary<int, SocketUtil>();
            clientId = 1;
        }

        /// <summary>
        /// 启动服务器
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        public void Start(string ip, int port)
        {
            IPAddress ipAddress = IPAddress.Parse(ip);
            IPEndPoint point = new IPEndPoint(ipAddress, port);

            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Bind(point);//绑定ip下的一个固定端口作为接受端口
            socket.Listen(100);//监听接受

            socket.BeginAccept(AcceptCallback, null);//开启异步接收
        }

        /// <summary>
        /// 客户端异步连接回调
        /// </summary>
        /// <param name="ar"></param>
        private void AcceptCallback(IAsyncResult ar)
        {
            Socket client = socket.EndAccept(ar);
            SocketUtil socketUtil = new SocketUtil(clientId, client, this);
            socketUtil.StartReceive();
            //发送消息,主要目的通知客户端ID
            Message message = MessageUtil.Set(0, "Server", clientId, "", "Hi there, I accept you request at " + DateTime.Now.ToString());
            socketUtil.Send(MessageUtil.MessageToByte(message));

            clientSockets.Add(clientId++, socketUtil);

            socket.BeginAccept(AcceptCallback, null);//再次开启异步接收
        }

        /// <summary>
        /// 释放客户端
        /// </summary>
        /// <param name="id"></param>
        public void DisposeSocket(int id)
        {
            if (clientSockets.ContainsKey(id))
            {
                clientSockets.Remove(id);
            }
        }

        public void Dispose()
        {
            if(socket != null)
                socket.Close();
        }
    }

    /// <summary>
    /// 消息缓存,简陋版
    /// </summary>
    public class MessageQueue
    {
        static List<Message> messageQueue;
        private static int startIndex;

        static MessageQueue()
        {
            messageQueue = new List<Message>();
            startIndex = 0;
        }

        /// <summary>
        /// 把消息放入缓存
        /// </summary>
        /// <param name="msg"></param>
        public static void EnQueue(Message msg)
        {
            messageQueue.Add(msg);
        }

        /// <summary>
        /// 获取未读的最上一条消息
        /// </summary>
        /// <returns></returns>
        public static Message GetQueue()
        {
            if (messageQueue.Count >= startIndex +1)
                return messageQueue[startIndex ++];
            return new Message();
        }

        /// <summary>
        /// 标志是否有新消息,在update中复用
        /// </summary>
        /// <returns></returns>
        public static bool hasNew()
        {
            return messageQueue.Count > startIndex;
        }

    }

    /// <summary>
    /// 客户端连接socket管理
    /// </summary>
    public class SocketUtil
    {
        private int id;
        private string name;
        private Socket socket;
        private Server server;
        private byte[] buffer;

        public SocketUtil(int id, Socket socket, Server server)
        {
            this.id = id;
            this.socket = socket;
            this.server = server;
            buffer = new byte[1024];

            ListenConnect();
        }

        /// <summary>
        /// 开始接收消息
        /// </summary>
        public void StartReceive()
        {
            socket.BeginReceive(buffer, 0, 1024, SocketFlags.None, ReceiveCallback, null);
        }

        /// <summary>
        /// 异步接收转换与缓存
        /// </summary>
        /// <param name="ar"></param>
        private void ReceiveCallback(IAsyncResult ar)
        {
            int length = 0;
            try
            {
                length = socket.EndReceive(ar);
            }
            catch (Exception e)
            {
                Console.WriteLine("Receive Error : " + e.ToString());
            }

            byte[] bytes = new byte[length];
            Buffer.BlockCopy(buffer, 0, bytes, 0, length);

            Message msg = MessageUtil.ByteToMessage(bytes);
            if (msg.rcvId == 0)
                name = msg.sendName.ToString();
            MessageQueue.EnQueue(msg);

            StartReceive();//复接
        }

        /// <summary>
        /// 发送,用于服务器广播或 P2P连接
        /// </summary>
        /// <param name="bytes"></param>
        public void Send(byte[] bytes)
        {
            socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallback, null);
        }

        private void SendCallback(IAsyncResult ar)
        {
            socket.EndSend(ar);
        }

        /// <summary>
        /// 监听是否掉线,掉线删除,监听频率为0.1秒
        /// </summary>
        private void ListenConnect()
        {
            Timer timer = new Timer()
            {
                Interval = 0.1d,
                AutoReset = true,
            };
            timer.Elapsed += (sender, args) =>
            {
                if (!socket.Connected)
                {
                    server.DisposeSocket(id);
                    timer.Stop();
                }
            };
            timer.Start();
        }
    }

    /*消息结构体*/
    [StructLayout(LayoutKind.Sequential)]
    public struct Message
    {
        public int sendId;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
        public char[] sendName;
        public int rcvId;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
        public char[] rcvName;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
        public char[] msg;

        public long time;
    }


    /// <summary>
    /// 消息辅助类
    /// </summary>
    public class MessageUtil
    {
        /// <summary>
        /// 快速生成结构体,可直接挪到结构体中
        /// </summary>
        /// <param name="sid">send id</param>
        /// <param name="sname">send name</param>
        /// <param name="rid">receive id</param>
        /// <param name="rname">receive name</param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public static Message Set(int sid, string sname, int rid, string rname, string msg)
        {
            Message message;
            message.sendId = sid;
            message.sendName = new char[32];
            CpyString(ref message.sendName, 32, sname);
            message.rcvId = rid;
            message.rcvName = new char[32];
            CpyString(ref message.rcvName, 32, rname);
            message.msg = new char[512];
            CpyString(ref message.msg, 512, msg);
            message.time = DateTime.Now.ToBinary();
            return message;
        }

        /// <summary>
        /// 快速将string转为char并储存
        /// </summary>
        /// <param name="chars"></param>
        /// <param name="size">chars的长度</param>
        /// <param name="str"></param>
        static void CpyString(ref char[] chars, int size, string str)
        {
            if (string.IsNullOrEmpty(str)) return;
            char[] cs = str.ToCharArray();
            Array.Copy(cs, 0, chars, 0, (cs.Length > size) ? size : cs.Length);
        }

        /// <summary>
        /// byte转结构体
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static Message ByteToMessage(byte[] bytes)
        {
            Message head;
            int length = Marshal.SizeOf(typeof(Message));
            IntPtr buffer = Marshal.AllocHGlobal(length);
            try //struct_bytes转换
            {
                Marshal.Copy(bytes, 0, buffer, length);
                head = (Message)Marshal.PtrToStructure(buffer, typeof(Message));
                return head;
            }
            catch (Exception ex)
            {
                throw new Exception("Error in BytesToStruct ! " + ex.Message);
            }
            finally
            {
                Marshal.FreeHGlobal(buffer);
            }
        }

        /// <summary>
        /// Message转byte
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public static byte[] MessageToByte(Message message)
        {
            int size = Marshal.SizeOf(typeof(Message));
            byte[] bytes = new byte[size];
            IntPtr structPtr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(message, structPtr, false);
            Marshal.Copy(structPtr, bytes, 0, size);
            Marshal.FreeHGlobal(structPtr);
            return bytes;
        }

    }
}
在Winform中的点击事件中的代码是:

private void button1_Click(object sender, EventArgs e)
        {
            if (server == null || button1.Text.Equals("启动"))
            {
                server = new Server(textBox1.Text, int.Parse(textBox2.Text));
                timer = new Timer()
                {
                    AutoReset = true,
                    Interval = 0.1d,
                };
                timer.Elapsed += ShowReceiveMessage;
                timer.Start();
                button1.Text = "停止";
            }
            else
            {
                server.Dispose();
                server = null;
                timer.Stop();
                timer = null;
                button1.Text = "启动";
            }
        }

        private string msg = "";

        private void ShowReceiveMessage(object sender, System.Timers.ElapsedEventArgs e)
        {
            while (MessageQueue.hasNew())
            {
                NetModule.Message message = MessageQueue.GetQueue();
                msg += new string(message.msg);//"\n\t" + new string(message.sendName) +"->" + new string(message.rcvName) + ":" + new string(message.msg);
                Action<string> ac = new Action<string>(ShowText);
                this.Invoke(ac, "");//线程限制原因
            }
        }

        void ShowText(string aa)
        {
            richTextBox1.Text = msg;
        }

具体作用已写注释。

客户端使用的是unity,如果要迁移其他的工具上,除了更改显示你不需做任何操作。客户端代码和服务器代码基本相同,只用主类有一点区别:

 public class NetWork:IDisposable
    {
        private Socket socket;
        private int id = -1;
        private string selfName = "Garson";

        const int BUFFER_SiZE = 2048;
        byte[] buffer = new byte[BUFFER_SiZE];
        public bool isConnect
        {
            get
            {
                if (socket != null)
                    return socket.Connected;
                return false;
            }
        }

        public void Connect(string ip, int port, Action callback )
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ipAddress = IPAddress.Parse(ip);
            IPEndPoint point = new IPEndPoint(ipAddress, port);
            socket.BeginConnect(point, ar =>
            {
                try
                {
                    socket.EndConnect(ar);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Connect Error : " + e.ToString());
                }

                if (socket.Connected)
                {
                    StartReceive();
                    if (callback != null)
                        callback();
                }
            } , null);
        }

        private void StartReceive()
        {
            IAsyncResult result = socket.BeginReceive(buffer, 0, BUFFER_SiZE, SocketFlags.None, ReceiveCallback, null);
            if (result.IsCompleted)
            {
                
            }
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            int length = 0;
            try
            {
                length = socket.EndReceive(ar);
            }
            catch (Exception e)
            {
                Console.WriteLine("Receive Error : " + e.ToString());
            }

            byte[] bytes = new byte[length];
            Buffer.BlockCopy(buffer, 0, bytes, 0, length);

            Message message = MessageUtil.ByteToMessage(bytes);
            if (id == -1 && message.sendId == 0)
            {
                id = message.rcvId;
                Message msg = MessageUtil.Set(id, selfName, 0, "Server", selfName + " is Comming!");
                Send(MessageUtil.MessageToByte(msg));
            }
            MessageQueue.EnQueue(message);
            StartReceive();
        }

        public void Send(byte[] bytes)
        {
            socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallback, null);
        }

        public void Send(int tid, string tname, string msg)
        {
            Message message = MessageUtil.Set(id, selfName, tid, tname, msg);
            Send(MessageUtil.MessageToByte(message));
        }

        private void SendCallback(IAsyncResult ar)
        {
            socket.EndSend(ar);
        }


        public void Dispose()
        {
            if(socket != null)
                socket.Close();
        }
    }

在MonoBehaviour中的调用同样简单:

private NetWork netWork;   
    void Start()
    {
        netWork = new NetWork();
        netWork.Connect("127.0.0.1", 11479, delegate()
        {
            Debug.Log("Connect Succeed!");
        });
    }

    private string msgcache = "";
    private string sendMsg = "";

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            netWork.Send(0, "Server", sendMsg);
            sendMsg = "";
        }

        if (MessageQueue.hasNew())
        {
            NetModule.Message message = MessageQueue.GetQueue();
            msgcache += "\n" + new string(message.sendName) + "->" + new string(message.rcvName) + ":" + new string(message.msg);
        }
    }
    

    void OnGUI()
    {
        GUILayout.Label("isConnect:  " + netWork.isConnect);
        sendMsg = GUILayout.TextField(sendMsg);
        GUILayout.Label(msgcache);
    }

最后来一发效果展示:



posted @ 2017-02-16 17:32  Garsonlab  阅读(229)  评论(0编辑  收藏  举报