21.TCP同步服务端接收多客户端

21.网络通信-套接字Socket-TCP通信-同步-服务端综合练习题


21.1 知识点

使用Scoket服务端编程基础知识和多线程知识实现多客户端服务,允许多个客户端连入服务端,可以分别和多个客户端进行通信

创建套接字,绑定到指定IP地址和端口,开始监听连接请求
// 1. 建立Socket,绑定,监听
static Socket serverSocket; // 用于监听客户端连接的服务器套接字

// 创建一个IPv4套接字
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); // 指定服务器监听的IP地址和端口
serverSocket.Bind(serverEndPoint); // 绑定套接字到指定的IP地址和端口
serverSocket.Listen(1024); // 开始监听,允许最多1024个等待连接的客户端
使用单独的线程等待客户端连接
static List<Socket> connectedClientSockets = new List<Socket>(); // 用于存储客户端套接字的列表
static bool isServerClose = false; // 控制服务器关闭的标志


// 2. 等待客户端连接
Thread acceptThread = new Thread(AcceptClientConnect); // 创建用于接受客户端连接的线程
acceptThread.Start();


static void AcceptClientConnect()
{
    while (!isServerClose)
    {
        Socket clientSocket = serverSocket.Accept(); // 接受客户端连接
        connectedClientSockets.Add(clientSocket); // 将客户端套接字添加到列表
        clientSocket.Send(Encoding.UTF8.GetBytes("欢迎你连入服务端")); // 向客户端发送欢迎消息
    }
}
使用另一个线程接收和处理客户端发送的消息
// 3. 收发消息
Thread receiveThread = new Thread(ReceiveClientMessage); // 创建用于接收消息的线程
receiveThread.Start();



static void ReceiveClientMessage()
{
    Socket clientSocket;
    byte[] result = new byte[1024 * 1024]; // 存储接收数据的缓冲区
    int receiveNum;
    int i;
    while (!isServerClose)
    {
        for (i = 0; i < connectedClientSockets.Count; i++)
        {
            clientSocket = connectedClientSockets[i]; // 获取当前客户端套接字

            if (clientSocket.Available > 0) // 检查套接字是否有可接收的数据
            {
                receiveNum = clientSocket.Receive(result); // 接收数据并返回字节数
                ThreadPool.QueueUserWorkItem(HandleMessage, (clientSocket, Encoding.UTF8.GetString(result, 0, receiveNum))); // 使用线程池处理接收到的消息 
                //ThreadPool.QueueUserWorkItem是传入一个方法和他的参数
                // 这里是传入客户端的Socket对象和反序列化后的字符串
            }
        }
    }
}

static void HandleMessage(object obj)
{
    // 使用元组
    (Socket clientSocket, string message) messageInfo = ((Socket clientSocket, string message))obj;
    Console.WriteLine("收到客户端{0}发来的信息:{1}", messageInfo.clientSocket.RemoteEndPoint, messageInfo.message); // 打印接收到的消息和客户端的远程端点
}
在主线程中通过控制台输入来控制服务器的关闭
// 4. 关闭相关
while (true)
{
    string input = Console.ReadLine(); // 从控制台获取输入

    // 关闭服务器并断开所有连接
    if (input == "Quit")
    {
        isServerClose = true;
        for (int i = 0; i < connectedClientSockets.Count; i++)
        {
            connectedClientSockets[i].Shutdown(SocketShutdown.Both); // 关闭套接字的发送和接收
            connectedClientSockets[i].Close(); // 关闭套接字
        }
        connectedClientSockets.Clear(); // 清空客户端套接字列表
        break; // 退出循环,关闭服务器
    }

    // 广播消息给所有客户端
    else if (input.Substring(0, 2) == "B:")
    {
        for (int i = 0; i < connectedClientSockets.Count; i++)
        {
            connectedClientSockets[i].Send(Encoding.UTF8.GetBytes(input.Substring(2))); // 发送消息给客户端 截取掉B:
        }
    }
}

使用面向对象思想对服务端客户端Socket进行封装

创建客户端套接字类,定义成员变量和构造函数
class ClientSocket
{
    private static int CLIENT_BEGIN_ID = 1;  // 用于为客户端分配唯一的客户端ID
    public int clientID;  // 客户端的唯一ID
    public Socket clientSocket;  // 与客户端通信的套接字对象

    /// <summary>
    /// 是否是连接状态
    /// </summary>
    public bool isClientConnected => this.clientSocket.Connected;  // 判断套接字是否处于连接状态

    public ClientSocket(Socket clientSocket)
    {
        this.clientID = CLIENT_BEGIN_ID;  // 初始化客户端ID
        this.clientSocket = clientSocket;  // 初始化套接字
        ++CLIENT_BEGIN_ID;  // 为下一个客户端分配不同的ID
    }
}
封装客户端套接字的成员方法
// 关闭套接字连接
public void Close()
{
    if (clientSocket != null)
    {
        clientSocket.Shutdown(SocketShutdown.Both);  // 关闭套接字的读写
        clientSocket.Close();  // 关闭套接字连接
        clientSocket = null;
    }
}

// 发送消息给客户端
public void Send(string info)
{
    if (clientSocket != null)
    {
        try
        {
            clientSocket.Send(Encoding.UTF8.GetBytes(info));  // 将消息编码为UTF-8字节数组并发送给客户端
        }
        catch (Exception e)
        {
            Console.WriteLine("发消息出错" + e.Message);
            Close();  // 如果发送出现异常,关闭套接字连接
        }
    }
}

// 接收来自客户端的消息
public void Receive()
{
    if (clientSocket == null)
        return;
    try
    {
        if (clientSocket.Available > 0)  // 如果套接字中有可读数据
        {
            byte[] resultByteArray = new byte[1024 * 5];  // 创建一个缓冲区来存储接收到的数据
            int byteLength = clientSocket.Receive(resultByteArray);  // 从套接字接收数据并存储在缓冲区中
            ThreadPool.QueueUserWorkItem(HandleMessage, Encoding.UTF8.GetString(resultByteArray, 0, byteLength));  // 异步处理接收到的消息
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("收消息出错" + e.Message);
        Close();  // 如果接收出现异常,关闭套接字连接
    }
}

// 处理接收到的消息
private void HandleMessage(object obj)
{
    string str = obj as string;
    Console.WriteLine("收到客户端{0}发来的消息:{1}", this.clientSocket.RemoteEndPoint, str);  // 将接收到的消息和客户端的信息输出到控制台
}
创建服务端套接字类,定义成员变量
class ServerSocket
{
    // 服务器端Socket
    public Socket serverSocket;
    // 保存客户端连接的所有Socket的字典
    public Dictionary<int, ClientSocket> clientSocketDictionary = new Dictionary<int, ClientSocket>();

    // 用于标识服务器是否关闭的标志
    private bool isServerClose;
}
封装服务端套接字的成员方法
// 开启服务器端
public void Start(string ipString, int port, int clientSocketMaxNum)
{
    // 初始化服务器关闭标志为假
    isServerClose = false;

    // 创建服务器套接字,指定地址族为IPv4、套接字类型为流套接字、协议类型为TCP
    serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    // 创建IP终结点,指定IP地址和端口号
    IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(ipString), port);

    // 将套接字绑定到指定的IP终结点
    serverSocket.Bind(serverEndPoint);

    // 启动服务器套接字,同时指定同时等待连接的最大客户端数
    serverSocket.Listen(clientSocketMaxNum);

    // 启动线程池中的线程来处理客户端连接请求和消息接收
    ThreadPool.QueueUserWorkItem(AcceptClientConnect);
    ThreadPool.QueueUserWorkItem(ReceiveClientMessage);
}

// 关闭服务器端
public void Close()
{
    // 设置服务器关闭标志为真
    isServerClose = true;

    // 关闭所有客户端连接
    foreach (ClientSocket client in clientSocketDictionary.Values)
    {
        client.Close();
    }
    clientSocketDictionary.Clear();

    // 关闭服务器套接字的读写
    serverSocket.Shutdown(SocketShutdown.Both);

    // 关闭服务器套接字
    serverSocket.Close();

    // 将服务器套接字设置为null
    serverSocket = null;
}

// 接受客户端连接
private void AcceptClientConnect(object obj)
{
    while (!isServerClose)
    {
        try
        {
            // 等待并接受一个客户端连接请求
            Socket clientSocket = serverSocket.Accept();

            // 创建一个新的ClientSocket对象来管理客户端连接
            ClientSocket client = new ClientSocket(clientSocket);

            // 向客户端发送欢迎消息
            client.Send("欢迎连入服务器");

            // 将客户端Socket对象添加到字典中,以客户端ID作为键
            clientSocketDictionary.Add(client.clientID, client);
        }
        catch (Exception e)
        {
            Console.WriteLine("客户端连入报错" + e.Message);
        }
    }
}

// 接收客户端消息
private void ReceiveClientMessage(object obj)
{
    while (!isServerClose)
    {
        if (clientSocketDictionary.Count > 0)
        {
            foreach (ClientSocket client in clientSocketDictionary.Values)
            {
                // 从每个客户端接收消息
                client.Receive();
            }
        }
    }
}

// 向所有客户端广播消息
public void Broadcast(string info)
{
    foreach (ClientSocket client in clientSocketDictionary.Values)
    {
        client.Send(info);
    }
}
主入口创建服务端并开启,定义一些命令
// 创建一个ServerSocket对象,用于处理服务器端的操作
ServerSocket serverSocket = new ServerSocket();

// 启动服务器,绑定到本地IP地址 127.0.0.1,监听端口 8080,允许最大连接数为 1024
serverSocket.Start("127.0.0.1", 8080, 1024);

// 输出服务器开启成功的消息
Console.WriteLine("服务器开启成功");

while (true)
{
    // 从控制台读取用户输入
    string input = Console.ReadLine();

    // 如果用户输入 "Quit",则关闭服务器
    if (input == "Quit")
    {
        serverSocket.Close();
    }
    // 如果用户输入以 "B:" 开头,表示要广播消息给所有客户端
    else if (input.Substring(0, 2) == "B:")
    {
        // 提取输入的消息内容(去掉 "B:")
        string message = input.Substring(2);

        // 调用服务器的广播方法,向所有客户端发送消息
        serverSocket.Broadcast(message);
    }
}

21.2 知识点代码

Lesson21_网络通信_套接字Socket_TCP通信_同步_服务端综合练习题1

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

namespace Lesson21_网络通信_套接字Socket_TCP通信_同步_服务端综合练习题1
{
    class Program
    {
        static Socket serverSocket; // 用于监听客户端连接的服务器套接字

        static List<Socket> connectedClientSockets  = new List<Socket>(); // 用于存储客户 端套接字的列表

        static bool isServerClose = false; // 控制服务器关闭的标志

        static void Main(string[] args)
        {
            // 1. 建立Socket,绑定,监听
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 创建一个IPv4套接字
            IPEndPoint serverEndPoint  = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); // 指定服务器监听的IP地址和端口
            serverSocket.Bind(serverEndPoint ); // 绑定套接字到指定的IP地址和端口
            serverSocket.Listen(1024); // 开始监听,允许最多1024个等待连接的客户端

            // 2. 等待客户端连接
            Thread acceptThread = new Thread(AcceptClientConnect); // 创建用于接受客户端连接的线程
            acceptThread.Start();

            // 3. 收发消息
            Thread receiveThread = new Thread(ReceiveClientMessage); // 创建用于接收消息的线程
            receiveThread.Start();

            // 4. 关闭相关
            while (true)
            {
                string input = Console.ReadLine(); // 从控制台获取输入

                // 定义一个规则,关闭服务器并断开所有连接
                if (input == "Quit")
                {
                    isServerClose = true;
                    for (int i = 0; i < connectedClientSockets .Count; i++)
                    {
                        connectedClientSockets [i].Shutdown(SocketShutdown.Both); // 关闭套接字的发送和接收
                        connectedClientSockets [i].Close(); // 关闭套接字
                    }
                    connectedClientSockets .Clear(); // 清空客户端套接字列表
                    break; // 退出循环,关闭服务器
                }

                // 定义一个规则,广播消息给所有客户端
                else if (input.Substring(0, 2) == "B:")
                {
                    for (int i = 0; i < connectedClientSockets .Count; i++)
                    {
                        connectedClientSockets [i].Send(Encoding.UTF8.GetBytes(input.Substring(2))); // 发送消息给客户端 截取掉B:
                    }
                }
            }
        }

        static void AcceptClientConnect()
        {
            while (!isServerClose)
            {
                Socket clientSocket = serverSocket.Accept(); // 接受客户端连接
                connectedClientSockets .Add(clientSocket); // 将客户端套接字添加到列表
                clientSocket.Send(Encoding.UTF8.GetBytes("欢迎你连入服务端")); // 向客户端发送欢迎消息
            }
        }

        static void ReceiveClientMessage()
        {
            Socket clientSocket;
            byte[] result = new byte[1024 * 1024]; // 存储接收数据的缓冲区
            int receiveNum;
            int i;
            while (!isServerClose)
            {
                for (i = 0; i < connectedClientSockets .Count; i++)
                {
                    clientSocket = connectedClientSockets [i]; // 获取当前客户端套接字

                    if (clientSocket.Available > 0) // 检查套接字是否有可接收的数据
                    {
                        receiveNum = clientSocket.Receive(result); // 接收数据并返回字节数
                        //如果直接在这收到消息 就处理 可能造成问题
                        //不能够即使的处理别人的消息
                        //为了不影响别人消息的处理 我们把消息处理 交给新的线程,为了节约线程相关的开销 我们使用线程池
                        ThreadPool.QueueUserWorkItem(HandleMessage, (clientSocket, Encoding.UTF8.GetString(result, 0, receiveNum))); // 使用线程池处理接收到的消息
                    }
                }
            }
        }

        static void HandleMessage(object obj)
        {
            (Socket clientSocket, string message) messageInfo = ((Socket clientSocket, string message))obj;
            Console.WriteLine("收到客户端{0}发来的信息:{1}", messageInfo.clientSocket.RemoteEndPoint, messageInfo.message); // 打印接收到的消息和客户端的远程端点
        }
    }
}

ClientSocket

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

namespace Lesson21_网络通信_套接字Socket_TCP通信_同步_服务端综合练习题2
{
    class ClientSocket
    {
        private static int CLIENT_BEGIN_ID = 1;  // 静态变量,用于为客户端分配唯一的客户端ID
        public int clientID;  // 客户端的唯一ID
        public Socket clientSocket;  // 与客户端通信的套接字对象

        /// <summary>
        /// 是否是连接状态
        /// </summary>
        public bool isClientConnected => this.clientSocket.Connected;  // 判断套接字是否处于连接状态

        public ClientSocket(Socket clientSocket)
        {
            this.clientID = CLIENT_BEGIN_ID;  // 初始化客户端ID
            this.clientSocket = clientSocket;  // 初始化套接字
            ++CLIENT_BEGIN_ID;  // 为下一个客户端分配不同的ID
        }


        // 关闭套接字连接
        public void Close()
        {
            if (clientSocket != null)
            {
                clientSocket.Shutdown(SocketShutdown.Both);  // 关闭套接字的读写
                clientSocket.Close();  // 关闭套接字连接
                clientSocket = null;
            }
        }

        // 发送消息给客户端
        public void Send(string info)
        {
            if (clientSocket != null)
            {
                try
                {
                    clientSocket.Send(Encoding.UTF8.GetBytes(info));  // 将消息编码为UTF-8字节数组并发送给客户端
                }
                catch (Exception e)
                {
                    Console.WriteLine("发消息出错" + e.Message);
                    Close();  // 如果发送出现异常,关闭套接字连接
                }
            }
        }

        // 接收来自客户端的消息
        public void Receive()
        {
            if (clientSocket == null)
                return;
            try
            {
                if (clientSocket.Available > 0)  // 如果套接字中有可读数据
                {
                    byte[] resultByteArray = new byte[1024 * 5];  // 创建一个缓冲区来存储接收到的数据
                    int byteLength = clientSocket.Receive(resultByteArray);  // 从套接字接收数据并存储在缓冲区中
                    ThreadPool.QueueUserWorkItem(HandleMessage, Encoding.UTF8.GetString(resultByteArray, 0, byteLength));  // 异步处理接收到的消息
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("收消息出错" + e.Message);
                Close();  // 如果接收出现异常,关闭套接字连接
            }
        }

        // 处理接收到的消息
        private void HandleMessage(object obj)
        {
            string str = obj as string;
            Console.WriteLine("收到客户端{0}发来的消息:{1}", this.clientSocket.RemoteEndPoint, str);  // 将接收到的消息和客户端的信息输出到控制台
        }
    }
}

ServerSocket

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

namespace Lesson21_网络通信_套接字Socket_TCP通信_同步_服务端综合练习题2
{
    class ServerSocket
    {
        // 服务器端Socket
        public Socket serverSocket;
        // 保存客户端连接的所有Socket的字典
        public Dictionary<int, ClientSocket> clientSocketDictionary = new Dictionary<int, ClientSocket>();

        // 用于标识服务器是否关闭的标志
        private bool isServerClose;

        // 开启服务器端
        public void Start(string ipString, int port, int clientSocketMaxNum)
        {
            // 初始化服务器关闭标志为假
            isServerClose = false;

            // 创建服务器套接字,指定地址族为IPv4、套接字类型为流套接字、协议类型为TCP
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // 创建IP终结点,指定IP地址和端口号
            IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(ipString), port);

            // 将套接字绑定到指定的IP终结点
            serverSocket.Bind(serverEndPoint);

            // 启动服务器套接字,同时指定同时等待连接的最大客户端数
            serverSocket.Listen(clientSocketMaxNum);

            // 启动线程池中的线程来处理客户端连接请求和消息接收
            ThreadPool.QueueUserWorkItem(AcceptClientConnect);
            ThreadPool.QueueUserWorkItem(ReceiveClientMessage);
        }

        // 关闭服务器端
        public void Close()
        {
            // 设置服务器关闭标志为真
            isServerClose = true;

            // 关闭所有客户端连接
            foreach (ClientSocket client in clientSocketDictionary.Values)
            {
                client.Close();
            }
            clientSocketDictionary.Clear();

            // 关闭服务器套接字的读写
            serverSocket.Shutdown(SocketShutdown.Both);

            // 关闭服务器套接字
            serverSocket.Close();

            // 将服务器套接字设置为null
            serverSocket = null;
        }

        // 接受客户端连接
        private void AcceptClientConnect(object obj)
        {
            while (!isServerClose)
            {
                try
                {
                    // 等待并接受一个客户端连接请求
                    Socket clientSocket = serverSocket.Accept();

                    // 创建一个新的ClientSocket对象来管理客户端连接
                    ClientSocket client = new ClientSocket(clientSocket);

                    // 向客户端发送欢迎消息
                    client.Send("欢迎连入服务器");

                    // 将客户端Socket对象添加到字典中,以客户端ID作为键
                    clientSocketDictionary.Add(client.clientID, client);
                }
                catch (Exception e)
                {
                    Console.WriteLine("客户端连入报错" + e.Message);
                }
            }
        }

        // 接收客户端消息
        private void ReceiveClientMessage(object obj)
        {
            while (!isServerClose)
            {
                if (clientSocketDictionary.Count > 0)
                {
                    foreach (ClientSocket client in clientSocketDictionary.Values)
                    {
                        // 从每个客户端接收消息
                        client.Receive();
                    }
                }
            }
        }

        // 向所有客户端广播消息
        public void Broadcast(string info)
        {
            foreach (ClientSocket client in clientSocketDictionary.Values)
            {
                client.Send(info);
            }
        }
    }
}

Lesson21_网络通信_套接字Socket_TCP通信_同步_服务端综合练习题2

namespace Lesson21_网络通信_套接字Socket_TCP通信_同步_服务端综合练习题2
{
    class Program
    {
        static void Main(string[] args)
        {
            // 创建一个ServerSocket对象,用于处理服务器端的操作
            ServerSocket serverSocket = new ServerSocket();

            // 启动服务器,绑定到本地IP地址 127.0.0.1,监听端口 8080,允许最大连接数为 1024
            serverSocket.Start("127.0.0.1", 8080, 1024);

            // 输出服务器开启成功的消息
            Console.WriteLine("服务器开启成功");

            while (true)
            {
                // 从控制台读取用户输入
                string input = Console.ReadLine();

                // 如果用户输入 "Quit",则关闭服务器
                if (input == "Quit")
                {
                    serverSocket.Close();
                }
                // 如果用户输入以 "B:" 开头,表示要广播消息给所有客户端
                else if (input.Substring(0, 2) == "B:")
                {
                    // 提取输入的消息内容(去掉 "B:")
                    string message = input.Substring(2);

                    // 调用服务器的广播方法,向所有客户端发送消息
                    serverSocket.Broadcast(message);
                }
            }
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏