技术思绪摘录旅行笔记
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。

首先是服务端:服务端采用控制台程序实现!

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text.RegularExpressions;
using System.Security.Cryptography;

namespace socket
{
    class Program
    {
        static void Main(string[] args)
        {
            new socketSe().Run();
        }
    }

    public class socketSe
    {
        private Dictionary<Socket, ClientInfo> clientPool = new Dictionary<Socket, ClientInfo>();
        private List<SocketMessage> msgPool = new List<SocketMessage>();
        private bool isClear = true;

        Socket soke = null;
        Thread trad = null;
        public void Run()
        {
            trad = new Thread(() =>
            {
                soke = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPAddress ia = IPAddress.Parse("192.168.8.106");
                IPEndPoint ie = new IPEndPoint(ia, 8043);
                soke.Bind(ie);
                //参数:挂起连接队列的最大长度。
                soke.Listen(10);
                soke.BeginAccept(new AsyncCallback(Accept), soke);
            });
            trad.Start();
            Console.WriteLine("服务器已启动");
            Broadcast();
        }
        private void Broadcast()
        {
            Thread broadcast = new Thread(() =>
            {
                while (true)
                {
                    if (!isClear)
                    {
                        byte[] msg = PackageServerData(msgPool[0]);
                        foreach (KeyValuePair<Socket, ClientInfo> cs in clientPool)
                        {
                            Socket client = cs.Key;
                            if (client.Poll(10, SelectMode.SelectWrite))
                            {
                                client.Send(msg, msg.Length, SocketFlags.None);
                            }
                        }
                        msgPool.RemoveAt(0);
                        isClear = msgPool.Count == 0 ? true : false;
                    }
                }
            });
            broadcast.Start();
        }

        private void Accept(IAsyncResult result)
        {
            Socket server = result.AsyncState as Socket;
            Socket client = server.EndAccept(result);
            try
            {
                server.BeginAccept(new AsyncCallback(Accept), server);
                byte[] buffer = new byte[1024];
                client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Recieve), client);
                ClientInfo info = new ClientInfo();
                info.Id = client.RemoteEndPoint;
                info.handle = client.Handle;
                info.buffer = buffer;
                this.clientPool.Add(client, info);
                Console.WriteLine(string.Format("客户端 {0} 连接", client.RemoteEndPoint));
            }
            catch (Exception ex)
            {
                Console.WriteLine("报错 :\r\n\t" + ex.ToString());
            }
        }


        private void Recieve(IAsyncResult result)
        {
            Socket client = result.AsyncState as Socket;

            if (client == null || !clientPool.ContainsKey(client))
            {
                return;
            }

            try
            {
                int length = client.EndReceive(result);
                byte[] buffer = clientPool[client].buffer;
                client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Recieve), client);
                string msg = Encoding.UTF8.GetString(buffer, 0, length);

                if (!clientPool[client].IsHandShaked && msg.Contains("Sec-WebSocket-Key"))
                {
                    client.Send(PackageHandShakeData(buffer, length));
                    clientPool[client].IsHandShaked = true;
                    return;
                }

                msg = AnalyzeClientData(buffer, length);

                SocketMessage sm = new SocketMessage();
                sm.Client = clientPool[client];
                sm.Time = DateTime.Now;

                Regex reg = new Regex(@"{<(.*?)>}");
                Match m = reg.Match(msg);
                if (m.Value != "")
                { //处理客户端传来的用户名
                    clientPool[client].NickName = Regex.Replace(m.Value, @"{<(.*?)>}", "$1");
                    sm.isLoginMessage = true;
                    sm.Message = "登录";
                    Console.WriteLine("{0} login @ {1}", client.RemoteEndPoint, DateTime.Now);
                }
                else
                {
                    sm.isLoginMessage = false;
                    sm.Message = msg;
                    Console.WriteLine("{0} @ {1}\r\n    {2}", client.RemoteEndPoint, DateTime.Now, sm.Message);
                }
                msgPool.Add(sm);
                isClear = false;

            }
            catch
            {
                client.Disconnect(true);
                Console.WriteLine("客户端 {0} 报错", clientPool[client].Name);
                clientPool.Remove(client);
            }
        }

        private byte[] PackageHandShakeData(byte[] handShakeBytes, int length)
        {
            string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, length);
            string key = string.Empty;
            Regex reg = new Regex(@"Sec\-WebSocket\-Key:(.*?)\r\n");
            Match m = reg.Match(handShakeText);
            if (m.Value != "")
            {
                key = Regex.Replace(m.Value, @"Sec\-WebSocket\-Key:(.*?)\r\n", "$1").Trim();
            }

            byte[] secKeyBytes = SHA1.Create().ComputeHash(
                                     Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
            string secKey = Convert.ToBase64String(secKeyBytes);

            var responseBuilder = new StringBuilder();
            responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + "\r\n");
            responseBuilder.Append("Upgrade: websocket" + "\r\n");
            responseBuilder.Append("Connection: Upgrade" + "\r\n");
            responseBuilder.Append("Sec-WebSocket-Accept: " + secKey + "\r\n\r\n");

            return Encoding.UTF8.GetBytes(responseBuilder.ToString());
        }
        private string AnalyzeClientData(byte[] recBytes, int length)
        {
            if (length < 2)
            {
                return string.Empty;
            }

            bool fin = (recBytes[0] & 0x80) == 0x80;
            if (!fin)
            {
                return string.Empty;
            }

            bool mask_flag = (recBytes[1] & 0x80) == 0x80;
            if (!mask_flag)
            {
                return string.Empty;
            }

            int payload_len = recBytes[1] & 0x7F;

            byte[] masks = new byte[4];
            byte[] payload_data;

            if (payload_len == 126)
            {
                Array.Copy(recBytes, 4, masks, 0, 4);
                payload_len = (UInt16)(recBytes[2] << 8 | recBytes[3]);
                payload_data = new byte[payload_len];
                Array.Copy(recBytes, 8, payload_data, 0, payload_len);

            }
            else if (payload_len == 127)
            {
                Array.Copy(recBytes, 10, masks, 0, 4);
                byte[] uInt64Bytes = new byte[8];
                for (int i = 0; i < 8; i++)
                {
                    uInt64Bytes[i] = recBytes[9 - i];
                }
                UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0);

                payload_data = new byte[len];
                for (UInt64 i = 0; i < len; i++)
                {
                    payload_data[i] = recBytes[i + 14];
                }
            }
            else
            {
                Array.Copy(recBytes, 2, masks, 0, 4);
                payload_data = new byte[payload_len];
                Array.Copy(recBytes, 6, payload_data, 0, payload_len);

            }

            for (var i = 0; i < payload_len; i++)
            {
                payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]);
            }

            return Encoding.UTF8.GetString(payload_data);
        }

        private byte[] PackageServerData(SocketMessage sm)
        {
            StringBuilder msg = new StringBuilder();
            if (!sm.isLoginMessage)
            {
                msg.AppendFormat("{0} @ {1}:\r\n    ", sm.Client.Name, sm.Time.ToShortTimeString());
                msg.Append(sm.Message);
            }
            else
            {
                msg.AppendFormat("{0} 登录 @ {1}", sm.Client.Name, sm.Time.ToShortTimeString());
            }


            byte[] content = null;
            byte[] temp = Encoding.UTF8.GetBytes(msg.ToString());

            if (temp.Length < 126)
            {
                content = new byte[temp.Length + 2];
                content[0] = 0x81;
                content[1] = (byte)temp.Length;
                Array.Copy(temp, 0, content, 2, temp.Length);
            }
            else if (temp.Length < 0xFFFF)
            {
                content = new byte[temp.Length + 4];
                content[0] = 0x81;
                content[1] = 126;
                content[2] = (byte)(temp.Length & 0xFF);
                content[3] = (byte)(temp.Length >> 8 & 0xFF);
                Array.Copy(temp, 0, content, 4, temp.Length);
            }
            else
            {
                // 不处理超长内容  
            }

            return content;
        }

    }

    public class ClientInfo
    {
        public byte[] buffer;

        public string NickName { get; set; }

        public EndPoint Id { get; set; }

        public IntPtr handle { get; set; }

        public string Name
        {
            get
            {
                if (!string.IsNullOrEmpty(NickName))
                {
                    return NickName;
                }
                else
                {
                    return string.Format("{0}#{1}", Id, handle);
                }
            }
        }

        public bool IsHandShaked { get; set; }
    }

    public class SocketMessage
    {
        public bool isLoginMessage { get; set; }

        public ClientInfo Client { get; set; }

        public string Message { get; set; }

        public DateTime Time { get; set; }
    }

}



客户端:使用一个html文件实现即可

<!DOCTYPE html>
<html>
<head>
    <title>简简单单的聊天</title>
    <meta charset="utf-8" />
</head>
<body style="padding:10px;">
    <h1 style="text-align: center;color: cornflowerblue;">聊天室</h1>
    <div style="margin:5px 0px;">
        <div><input id="name" type="text" value="" placeholder="请输入用户名" style="width:100%;height: 30px; border-radius: 5px;border: 1px green solid;" /></div>
    </div>
    <div>
        <button id="connect" onclick="connect();">连接服务器</button> &nbsp;&nbsp;
        <button id="disconnect" onclick="quit();">下线</button>&nbsp;&nbsp;
        <button id="clear" onclick="clearMsg();">清空</button>
    </div>

    <div id="message" style="border:solid 1px #333; border-radius: 5px; padding:0px; width:100%; overflow:auto;
	 	background-color:#404040; height:300px; margin-bottom:8px; font-size:14px;">
        <h5 style="margin:4px 0px; color: white;font-size: 15px;font-family: '微软雅黑'; text-align: center;border-bottom: 1px red dashed;">消息</h5>
    </div>
    <input id="text" type="text" onkeypress="enter(event);" placeholder="消息" style="width:100%;height: 30px; border-radius: 5px;border: 1px green solid;" /> &nbsp;&nbsp;
    <button id="send" onclick="send();" style="background-color: goldenrod;border: none;float: right;height: 30px;width: 50px;margin-top: 5px;border-radius: 5px;">发送</button>
    <script type="text/javascript">
        var name = document.getElementById('name').value;
        var msgContainer = document.getElementById('message');
        var text = document.getElementById('text');

        function connect() {
            ws = new WebSocket("ws://192.168.8.106:8043");
            ws.onopen = function (e) {
                var msg = document.createElement('div');
                msg.style.color = '#0f0';
                msg.innerHTML = "Server > 已连接,可以聊天了.";
                msgContainer.appendChild(msg);
                ws.send('{<' + document.getElementById('name').value + '>}');
            };
            ws.onmessage = function (e) {
                var msg = document.createElement('div');
                msg.style.color = '#fff';
                msg.innerHTML = e.data;
                msgContainer.appendChild(msg);
            };
            ws.onerror = function (e) {
                var msg = document.createElement('div');
                msg.style.color = '#0f0';
                msg.innerHTML = 'Server > ' + e.data;
                msgContainer.appendChild(msg);
            };
            ws.onclose = function (e) {
                var msg = document.createElement('div');
                msg.style.color = '#0f0';
                msg.innerHTML = "Server > 服务器连接不上或已关闭";
                msgContainer.appendChild(msg);
            };
            text.focus();
        }

        function quit() {
            if (ws) {
                ws.close();
                var msg = document.createElement('div');
                msg.style.color = '#0f0';
                msg.innerHTML = 'Server > 连接关闭.';
                msgContainer.appendChild(msg);
                ws = null;
            }
        }

        function send() {
            ws.send(text.value);
            setTimeout(function () {
                msgContainer.scrollTop = msgContainer.getBoundingClientRect().height;
            }, 100);
            text.value = '';
            text.focus();
        }

        function clearMsg() {
            msgContainer.innerHTML = "";
        }

        function enter(event) {
            if (event.keyCode == 13) {
                send();
            }
        }
    </script>
</body>
</html>



CarsonIT 微信扫码关注公众号 策略、创意、技术

留下您的脚步

 

最近评论

查看更多>>

站点统计

总文章数:275 总分类数:18 总评论数:88 总浏览数:128.37万

精选推荐

阅读排行

友情打赏

请打开您的微信,扫一扫