网络编程知识使用Socket通信,做一个简单的多人聊天室

Posted 呆呆敲代码的小Y

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程知识使用Socket通信,做一个简单的多人聊天室相关的知识,希望对你有一定的参考价值。


📢前言

  • Socket 作为网络编程超级重要的一个知识点,对于程序员自然是一个必备的东西。
  • 正好最近在看一些Socket套接字的内容,就想着能不能使用Socket做一个简单的通信功能,来加深对Socket的认知。
  • 说到Socket那自然少不了TCP/IP协议,想了解这块可以参考另一篇文章:Socket基本概念简单理解
  • 所以这篇文章就来使用Socket来简单制作一个多人聊天室,看看Socket到底怎样进行使用。
  • 本文使用 Unity引擎 + C# 实现,制作过程还是挺简单的,下面一起来看看吧!

🎬使用Socket通信,做一个简单的多人聊天室

套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。
一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。
从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。

要使用Socket制作一个简单的多人聊天室,需要有 服务端 和 客户端。

一个简单的 基于 TCP 协议的 客户端服务端 工作流程如下:

  • 服务端 和 客户端初始化 socket,得到文件描述符
  • 服务端 调用 bind,绑定 IP 地址和端口号
  • 服务端 调用 listen,进行监听 并 设置最大连接数量
  • 服务端 调用 accept,等待接收 客户端 连接
  • 客户端 调用 connect,向服务器端的地址和端口发起连接请求
  • 服务端 accept 返回用于传输的 socket 的文件描述符
  • 客户端 调用 send 写入数据
  • 服务端调用 recv 读取数据
  • 客户端 断开连接时,会调用 close,那么 服务端 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端 调用 close,表示连接关闭

先来说一下这个 多人聊天室 要满足的基本功能

  1. 有一个服务端 和 多个 客户端,服务端 建立监听,客户端 进行连接。
  2. 当有 新的客户端 连接成功时,会给 其他客户端 发送一条消息。
  3. 客户端 给 服务端 发消息时,服务端 把收到的消息转发给 除客户端本身(发消息的客户端) 外的 所有客户端。

这样就实现了一个 简单的多人即时通信聊天室 的功能。
下面就开始编写 服务端 和 客户端 的代码,使其建立连接实现通信。


🏳️‍🌈一个简单的UI

按照自己的需求喜好制作一个简易的聊天室。

必要条件:

  • 一个服务端开启的Button按钮
  • 客户端连接到服务器的按钮(自己几个随意)
  • 消息输入框InputField 和 内容显示框Text(跟随客户端数量添加)

🏳️‍🌈服务端 部分

服务端 部分大致步骤操作如下:

  • 第一步:创建一个服务器Socket对象。
  • 第二步:等待客户端的连接 并且创建与之通信的Socket
  • 第三步:服务器端不停的接收客户端发来的消息
  • 第四步:服务器向客户端发送消息

先在服务器创建一个Socket对象,绑定IP和端口号,设置最大连接数量,创建一个线程进行监听。
然后等待客户端的连接,并将已连接的客户端存取起来,有客户端加入连接时广播消息。
服务器端不停的接收客户端发来的消息,将消息字节流转化为字符串并群发消息。

新建脚本SocketServer.cs,完整代码如下:

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;

public class SocketServer : MonoBehaviour

    private List<string> _ClientList;
    private List<Socket> _SocketsList;
    private Dictionary<string, string> _ClientName;

    private void Awake()
    
        _ClientList = new List<string>();
        _SocketsList = new List<Socket>();
        _ClientName = new Dictionary<string, string>();
    

    public void StartServer()
    
        //服务器连接 按钮点击事件
        bt_connnect_Click();
    

    /// <summary>
    /// 第一步:创建一个服务器Socket对象
    /// </summary>
    private void bt_connnect_Click()
    
        try
        
            int _port = 6000;
            string _ip = "127.0.0.1";

            //点击开始监听时 在服务端创建一个负责监听IP和端口号的Socket
            Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse(_ip);
            //创建对象端口
            IPEndPoint point = new IPEndPoint(ip, _port);

            //绑定端口号
            socketWatch.Bind(point);
            Debug.Log("监听成功!");
            //设置监听,最大同时连接10台
            socketWatch.Listen(10);

            //创建监听线程
            Thread thread = new Thread(Listen);
            thread.IsBackground = true;
            thread.Start(socketWatch);
        
        catch  
    

    /// <summary>
    /// 第二步:等待客户端的连接 并且创建与之通信的Socket
    /// </summary>
    Socket socketSend;
    void Listen(object o)
    
        try
        
            int client=1;
            Socket socketWatch = o as Socket;
            while (true)
            
                socketSend = socketWatch.Accept();//等待接收客户端连接
                Debug.Log(socketSend.RemoteEndPoint.ToString() + ":" + "连接成功!");

                //将连接到服务器的客户端添加到列表中
                _ClientList.Add(socketSend.RemoteEndPoint.ToString());
                _SocketsList.Add(socketSend);
                _ClientName.Add(socketSend.RemoteEndPoint.ToString(), client+"号");
                client++;

                for (int i = 0; i <= _ClientList.Count - 1; i++)
                
                    //服务器向所有客户端转发消息,除了发这条消息的客户端本身
                    if (socketSend.RemoteEndPoint.ToString() != _ClientList[i])
                    
                        Sends(_ClientName[socketSend.RemoteEndPoint.ToString()]+ "加入房间\\n" ,  i);
                    
                    
                

                //开启一个新线程,执行接收消息方法
                Thread r_thread = new Thread(Received);
                r_thread.IsBackground = true;
                r_thread.Start(socketSend);
            
        
        catch(Exception e)
        
            Debug.Log(e);
        
    

    /// <summary>
    /// 第三步:服务器端不停的接收客户端发来的消息
    /// </summary>
    /// <param name="o"></param>
    void Received(object o)
    
        try
        
            Socket socketSend = o as Socket;
            while (true)
            
                //客户端连接服务器成功后,服务器接收客户端发送的消息
                byte[] buffer = new byte[1024 * 1024 * 3];
                //实际接收到的有效字节数
                int len = socketSend.Receive(buffer);
                if (len == 0)
                
                    break;
                
                string str = Encoding.UTF8.GetString(buffer, 0, len);
                Debug.Log("服务器打印:" + socketSend.RemoteEndPoint + ":" + str);

                for (int i=0;i<= _ClientList.Count-1;i++)
                
                    //服务器向所有客户端转发消息,除了发这条消息的客户端本身
                    if (socketSend.RemoteEndPoint.ToString() != _ClientList[i])
                    
                        Sends(_ClientName[socketSend.RemoteEndPoint.ToString()] + ":" + str,i);
                    
                
            
        
        catch (Exception e)
        
            Debug.Log(e);
        
    

    /// <summary>
    /// 第四步:服务器向客户端发送消息
    /// </summary>
    /// <param name="str"></param>
    void Sends(string msg,int socket)
    
        byte[] buffer = Encoding.UTF8.GetBytes(msg);
        _SocketsList[socket].Send(buffer);
    


🏳️‍🌈客户端部分

客户端部分 比 服务端 简单,大致步骤如下:

  • 第一步:客户端连接到服务器
  • 第二步:接收服务端返回的消息
  • 第三步:向服务器发送消息

这部分和服务端其实差不多,就是从监听改为了连接到服务端。
然后使用了Loom插件从多线程中给主线程中的UI添加内容,对Loom插件使用不熟悉的可以参考这篇文章:

Unity零基础到进阶 | Unity中的多线程的使用,普通创建Thread + 使用Loom插件创建

当然也可以不使用Loom插件来进行Socket通信,我这里只是简单利用Loom插件来将服务端发来的消息直接绘制在聊天框里了。

也可以自己将服务端发来的内容通过别的方式发送到主线程的UI中,避免增加项目的复杂度。

新建脚本SocketClient.cs,完整代码如下:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;

public class SocketClient : MonoBehaviour

    public InputField input;//聊天输入框
    public Text text;//聊天内容显示框

    public void StartClient()
    
        bt_connect_Click();
    

    public void SendMsg()
    
        bt_send_Click(input.text);
    

    Socket socketSend;
    /// <summary>
    /// 第一步:客户端连接到服务器
    /// </summary>
    private void bt_connect_Click()
    
        try
        
            int _port = 6000;
            string _ip = "127.0.0.1";

            //创建客户端Socket,获得远程ip和端口号
            socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse(_ip);
            IPEndPoint point = new IPEndPoint(ip, _port);

            //向服务器端的地址和端口发起连接请求
            socketSend.Connect(point);

            Loom.RunAsync(() =>
            
                //开启新的线程,不停的接收服务器发来的消息
                Thread c_thread = new Thread(Received);
                c_thread.IsBackground = true;
                c_thread.Start();
            );
        
        catch (Exception)
        
            Debug.Log("IP或者端口号错误...");
        
    

    /// <summary>
    /// 第二步:接收服务端返回的消息
    /// </summary>
    void Received()
    
        while (true)
        
            try
            
                byte[] buffer = new byte[1024 * 1024 * 3];
                //实际接收到的有效字节数
                int len = socketSend.Receive(buffer);
                if (len == 0)
                
                    break;
                
                string str = Encoding.UTF8.GetString(buffer, 0, len);
                Debug.Log("客户端打印:" + socketSend.RemoteEndPoint + ":" + str);

                //使用Loom调用主线程的内容
                Loom.QueueOnMainThread((param) =>
                
                    text.text += str + "\\n";
                , null);
            
            catch (Exception e)
            
                Debug.Log("错误..."+e);
            
        
    

    /// <summary>
    /// 第三步:向服务器发送消息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void bt_send_Click(string str)
    
        try
        
            string msg = str;
            byte[] buffer = new byte[1024 * 1024 * 3];
            buffer = Encoding.UTF8.GetBytes(msg);
            socketSend.Send(buffer);

            text.text += "我:" + str + "\\n";
            input.text = "";
        
        catch  
    
   

🏳️‍🌈Loom插件的使用

Loom插件 就一个脚本,目的是可以从多线程中调用Unity的UI部分。
在上面的客户端中用到,直接将Loom脚本挂在到场景中就可以使用。

Loom脚本完整代码如下:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;

public class Loom : MonoBehaviour

    public static int maxThreads = 8;
    static int numThreads;

    private static Loom _current;
    //private int _count;
    public static Loom Current
    
        get
        
            Initialize();
            return _current;
        
    

    void Awake()
    
        _current = this;
        initialized = true;
    

    static bool initialized;

    public static void Initialize()
    
        if (!initialized)
        

            if (!Application.isPlaying)
                return;
            initialized = true;
            var g = new GameObject("Loom");
            _current = g.AddComponent<Loom>();
#if !ARTIST_BUILD
            UnityEngine.Object.DontDestroyOnLoad(g);
#endif
        

    
    public struct NoDelayedQueueItem
    
        public Action<object> action;
        public object param;
    

    private List<NoDelayedQueueItem> _actions = new List<NoDelayedQueueItem>();
    public struct DelayedQueueItem
    
        public float time;
        public Action<object> action;
        public object param;
    
    private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();

    List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();

    public static void QueueOnMainThread(Action<object> taction, object tparam)
    
        QueueOnMainThread(taction, tparam, 0f);
    
    public static void QueueOnMainThread(Action<object> taction, object tparam, float time)
    
        if (time != 0)
        
            lock (Current._delayed)
            
                Current._delayed.Add(new DelayedQueueItem  time = Time.time + time, action = taction, param = tparam );
            
        
        else
        
            lock (Current._actions)
            
                Current._actions.Add(new NoDelayedQueueItem  action = taction, param = tparam );
            
        
    

    public static Thread RunAsync(Action a)
    
        Initialize();
        while (numThreads >= maxThreads)
        
            Thread.Sleep(100);
        
        Interlocked.Increment(ref numThreads);
        ThreadPool.QueueUserWorkItem(RunAction, a);
        return null;
    

    private static void RunAction(object action)
    
        try
        
            ((Action)action)();
        
        catch
        
        
        finally
        
            Interlocked.Decrement(ref numThreads);
        
    

    void OnDisable()
    
        if (_current == this)
        

            _current = null;
        
    



    // Use this for initialization
    void Start()
    

    

    List<NoDelayedQueueItem> _currentActions = new List<NoDelayedQueueItem>();

    // Update is called once per frame
    void Update()
    
        if (_actions.Count > 0)
        
            lock (_actions)
            
                _currentActions.Clear();
                _currentActions.AddRange(_actions);
                _actions.Clear(以上是关于网络编程知识使用Socket通信,做一个简单的多人聊天室的主要内容,如果未能解决你的问题,请参考以下文章

游戏开发实战Unity使用Socket通信实现简单的多人聊天室(万字详解 | 网络 | TCP | 通信 | Mirror | Networking)

游戏开发实战Unity使用Socket通信实现简单的多人聊天室(万字详解 | 网络 | TCP | 通信 | Mirror | Networking)

Linux下基于Socket网络通信的多人聊天室

JAVA实战用socket通信编程制作多人聊天室

Java--Socket通信

java实现的多人聊天(控制台输入)