本文介绍了当另一个客户端加入时,客户端的 Socket.send 无法正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 C# Winform 中的套接字编写一个聊天程序.
它适用于单个客户端,但是当我打开另一个客户端时,以前的客户端(运行良好)的发送功能从不工作,但它一直在接收数据.

I'm writing a chat program using sockets in C# Winform.
It works well with single client, but when I turn on another client, previous client's (worked well) Send function never works, but it keeps receiving data.

这是我的整个源代码(因为我知道它有什么问题,我得到了一些建议,但我给了他们一些糟糕的细节)

Here's my whole source code (because idk what's wrong with it, and I got some advices that I gave them poor details)

服务器:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace Player__Server_
{
    public partial class Form1 : Form
    {
        Socket Serv;
        List<Socket> ClnSocket = new List<Socket>();
        List<string> MusList = new List<string>();
        List<string> Nickname = new List<string>();

        int ClnCounter = 0;
        int MusCounter = 0;
        int BufferingCount = 0;
        Socket socket;
        Thread run;
        private delegate void sDelegate(string sData, int socketIndex);
        public Form1()
        {
            InitializeComponent();
        }

        new public string Left(string Text, int TextLength)
        {
            if (Text.Length < TextLength)
            {
                TextLength = Text.Length;
            }
            return Text.Substring(0, TextLength);
        }

        new public string Right(string Text, int TextLength)
        {
            if (Text.Length < TextLength)
            {
                TextLength = Text.Length;
            }
            return Text.Substring(Text.Length - TextLength, TextLength);
        }

        new public string Mid(string sString, int nStart, int nLength)
        {
            string sReturn;
            --nStart;

            if (nStart <= sString.Length)
            {
                if ((nStart + nLength) <= sString.Length)
                {
                    sReturn = sString.Substring(nStart, nLength);
                }
                else
                {
                    sReturn = sString.Substring(nStart);
                }
            }
            else
            {
                sReturn = string.Empty;
            }
            return sReturn;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // prevent exception
            Stream fe = new FileStream(Environment.CurrentDirectory + "/Music.server", FileMode.OpenOrCreate);
            fe.Close();

            // Read "Music.server" file and add to MusList (List)
            string line;
            StreamReader fr = new StreamReader(Environment.CurrentDirectory + "/Music.server");
            while ((line = fr.ReadLine()) != null)
            {
                MusList.Add(line);
                MusCounter++;
            }
            fr.Close();

            // prevent exception
            Stream fa = new FileStream(Environment.CurrentDirectory + "/Account.server", FileMode.OpenOrCreate);
            fa.Close();

            Serv = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Serv.Bind(new IPEndPoint(IPAddress.Any, 9180));
            Serv.Listen(100);

            Thread accept = new Thread(this.Accept);
            accept.Start();
        }

        private void runChatting(object s)
        {
            byte[] str = new byte[2048];
            socket = s as Socket;
                while (true)
                {
                    try
                    {
                        str = new byte[2048];
                        socket.Receive(str);
                        sDelegate sdelegate = new sDelegate(this.Receive);
                        this.Invoke(sdelegate, Encoding.Default.GetString(str), ClnSocket.IndexOf(socket));
                    }
                    catch
                    {
                        try
                        {
                            ClnSocket.Remove(socket);
                            ClnCounter--;
                        }
                        catch { }
                        return;
                    }


                }
        }

        private string GetMusic(string MusName)
        {
            // Function :: return original information of music- search by name

            int i;
            for (i = 0; i < MusCounter; i++)
            {
                try
                {
                    if (MusList[i].IndexOf(MusName) > 0)
                    {
                        return MusList[i];
                    }
                }
                catch { }
            }
            return null;
        }

        private void Receive(string sData, int socketIndex)
        {
            TextBox.AppendText("GET : " + sData);
            if (sData.IndexOf(Environment.NewLine) > 0) { ; } else TextBox.AppendText(Environment.NewLine);
            sData = sData.Replace("\0", "");
            if (Left(sData,10) == "#musicadd#")
            {
                string TempData = Mid(sData, 11, sData.Length);
                string[] SpliteData = TempData.Split('#');
                if (GetMusic(SpliteData[1]) == null)
                {
                    Stream fs = new FileStream(Environment.CurrentDirectory + "/Music.server", FileMode.Append);
                    StreamWriter ws = new StreamWriter(fs);
                    ws.WriteLine(SpliteData[0] + "#" + SpliteData[1] + "#" + SpliteData[2] + "#sc");
                    ws.Close();
                    fs.Close();

                    MusList.Add(SpliteData[0] + "#" + SpliteData[1] + "#" + SpliteData[2] + "#sc");
                    MusCounter++;
                }
                SendTo("#musicadd#" + SpliteData[1], socketIndex);
            }
            else if (Left(sData, 7) == "#login#")
            {
                SendAll(Mid(sData, 8, sData.Length) + " Connected." + Environment.NewLine);
            }
            else if (Left(sData, 14) == "#requestmusic#")
            {
                string requestValue = GetMusic(Mid(sData, 15, sData.Length));
                SendAll("#buffermusic#" + requestValue);
                BufferingCount = 0;
            }
            else if (Left(sData, 12) == "#bufferdone#")
            {
                BufferingCount++;
                if (BufferingCount == ClnCounter)
                {
                    SendAll("#musicplay#");
                }
            }
            else
            {
                SendAll(sData);
            }
        }

        private void SendAll(string sData)
        {
            int i;
            for (i = 0; i < ClnSocket.Count; i++)
            {
                try
                {
                    ClnSocket[i].Send(Encoding.Default.GetBytes(sData));
                }
                catch { }
            }
            TextBox.AppendText("POST : " + sData);
            if (sData.IndexOf(Environment.NewLine) > 0) { ; } else TextBox.AppendText(Environment.NewLine);
        }

        private void SendTo(string sData, int socketIndex)
        {
            try
            {
                ClnSocket[socketIndex].Send(Encoding.Default.GetBytes(sData));
                TextBox.AppendText("POST TO (" + socketIndex.ToString() + ") : " + sData);
                if (sData.IndexOf(Environment.NewLine) > 0) { ; } else TextBox.AppendText(Environment.NewLine);
            }
            catch { }
        }
        private void Accept()
        {
                while (true)
                {
                    ClnSocket.Add(Serv.Accept());
                    ClnCounter++;
                    run = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(this.runChatting));
                    run.Start(ClnSocket[ClnCounter - 1]);
                }
        }
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            this.Serv.Close();
            this.Serv = null;
            for (int i = 0; i < this.ClnSocket.Count; i++)
            {
                this.ClnSocket[i].Close();
            }
            this.ClnSocket.Clear();
            System.Environment.Exit(0);
        }
    }
}

客户:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Media;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.IO;

namespace MineSky_Player
{
    public partial class Form1 : Form
    {
        WMPLib.WindowsMediaPlayer Player = new WMPLib.WindowsMediaPlayer();
        public Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        Thread run;
        string BufferingInformation;
        int current_min = 0, current_sec = 0;
        int duration_min = 0, duration_sec = 0;
        int MusCounter = 0;

        public string mynick { get; set; }
        string MyNick;

        List<string> MusList = new List<string>();

        public delegate void sDelegate(string sData);
        public Form1()
        {
            try
            {
                InitializeComponent();
                socket.Connect("localhost", 9180);
                run = new Thread(new ParameterizedThreadStart(Run));
                run.Start();
            }
            catch (Exception ex){
                MessageBox.Show(ex.ToString());
                System.Environment.Exit(0);
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // prevent exception
            Stream fe = new FileStream(Environment.CurrentDirectory + "/Music.client", FileMode.OpenOrCreate);
            fe.Close();

            // Read "Music.client" file and add to MusList (List)
            string line;
            StreamReader fr = new StreamReader(Environment.CurrentDirectory + "/Music.client");
            while ((line = fr.ReadLine()) != null)
            {
                MusList.Add(line);
                MusCounter++;
                MusicList.Items.Add(line);
            }
            fr.Close();

            MyNick = mynick;
        }

        new public string Left(string Text, int TextLength)
        {
            if (Text.Length < TextLength)
            {
                TextLength = Text.Length;
            }
            return Text.Substring(0, TextLength);
        }

        new public string Right(string Text, int TextLength)
        {
            if (Text.Length < TextLength)
            {
                TextLength = Text.Length;
            }
            return Text.Substring(Text.Length - TextLength, TextLength);
        }

        new public string Mid(string sString, int nStart, int nLength)
        {
            string sReturn;
            --nStart;

            if (nStart <= sString.Length)
            {
                if ((nStart + nLength) <= sString.Length)
                {
                    sReturn = sString.Substring(nStart, nLength);
                }
                else
                {
                    sReturn = sString.Substring(nStart);
                }
            }
            else
            {
                sReturn = string.Empty;
            }
            return sReturn;
        }
        private void BufferTick_Tick(object sender, EventArgs e)
        {
            if (Player.playState.ToString() == "wmppsPlaying")
            {
                Player.controls.stop();
                ToSocket("#bufferdone#");
                BufferTick.Enabled = false;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Player.controls.play();
        }

        private void Run(object s)
        {
            byte[] str = new byte[2048];
            try
            {
                while (true)
                {
                    str = new byte[2048];
                    socket.Receive(str);
                    sDelegate sdelegate = new sDelegate(this.Receive);
                    IntPtr x;
                    if (!this.IsHandleCreated) x = this.Handle;
                    this.Invoke(sdelegate, Encoding.Default.GetString(str));
                }
            }
            catch (Exception e)
            {
                MessageBox.Show("Connection Lost." + Environment.NewLine + e.ToString());
                Application.Exit();
            }
        }

        public void Receive(string sData)
        {
            //MessageBox.Show("GET : " + sData);
            sData = sData.Replace("\0", "");
            if (Left(sData, 10) == "#musicadd#")
            {
                if (MusicList.Items.Contains(Mid(sData, 11, sData.Length)))
                {
                    MessageBox.Show("Already in the list!", "MSPlayer", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
                else
                {
                    Stream fs = new FileStream(Environment.CurrentDirectory + "/Music.client", FileMode.Append);
                    StreamWriter ws = new StreamWriter(fs);
                    ws.WriteLine(Mid(sData, 11, sData.Length));
                    ws.Close();
                    fs.Close();
                    MusList.Add(Mid(sData, 11, sData.Length));
                    MusicList.Items.Add(Mid(sData, 11, sData.Length));
                    MusCounter++;
                }
            }
            else if (Left(sData, 13) == "#buffermusic#")
            {
                PlayProgressBar.Value = 0;
                current_min = 0;
                current_sec = 0;
                Player.URL = "";
                BufferingInformation = Mid(sData, 14, sData.Length);
                playingLabel.Text = BufferingInformation.Split('#')[1];
                playLabel.Text = "Buffering...";
                Player.URL = "https://player.soundcloud.com/player.swf?url=https%3A//api.soundcloud.com/tracks/" + BufferingInformation.Split('#')[0] + ";color=ff5500&show_comments=false&auto_play=true& color=a2eeff";
                BufferTick.Enabled = true;
            }
            else if (Left(sData, 11) == "#musicplay#")
            {
                duration_min = Int32.Parse(BufferingInformation.Split('#')[2]) / 60;
                duration_sec = Int32.Parse(BufferingInformation.Split('#')[2]) % 60;
                playLabel.Text = "0:00 / " + duration_min.ToString() + ":" + duration_sec.ToString();
                PlayProgressBar.Maximum = Int32.Parse(BufferingInformation.Split('#')[2]);
                Player.controls.play();
                PlayTick.Enabled = true;
            }
            else
            {
                Text_Board.AppendText(sData.Replace("\#\", "#"));
            }
        }

        public void Send(string sData)
        {
            sData = sData.Replace("#", "\#\");
            Send_Supplied(sData);
        }

        public void Send_Supplied(string sData)
        {
            if (Left(sData, 2) == "//")
            {
                sData = sData.ToLower();
                if (Left(sData, 6) == "//exit") Application.Exit();
            }
            else
            {
                ToSocket(sData);
            }
        }

        private void ToSocket(string sData)
        {
            socket.Send(Encoding.Default.GetBytes(sData));
        }
        private void Text_Chat_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                string str = this.Text_Chat.Text.Replace(Environment.NewLine, "");
                this.Text_Chat.Text = "";
                Send(MyNick + " : " + str + Environment.NewLine);
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            socket.Close();
            System.Environment.Exit(0);
        }

        private void Button_Exit_Click(object sender, EventArgs e)
        {
            socket.Close();
            System.Environment.Exit(0);
        }

        private void MusicList_DoubleClick(object sender, EventArgs e)
        {
            ToSocket("#requestmusic#" + MusicList.GetItemText(MusicList.SelectedItem));
        }

        private void Button_MusicAdd_Click(object sender, EventArgs e)
        {
            AddForm addform = new AddForm();
            addform.socket = socket;
            addform.ShowDialog();
        }

        private void MusicList_SelectedIndexChanged(object sender, EventArgs e)
        {

        }

        private void PlayTick_Tick(object sender, EventArgs e)
        {
            if (Player.playState.ToString() == "wmppsPlaying")
            {
                current_sec++;
                PlayProgressBar.Value++;
                if (current_sec == 60)
                {
                    current_min++;
                    current_sec = 0;
                }

                playLabel.Text = current_min.ToString() + ":" + current_sec.ToString() + " / " + duration_min.ToString() + ":" + duration_sec.ToString();
                if (PlayProgressBar.Value == PlayProgressBar.Maximum)
                {
                    Player.controls.stop();
                    playingLabel.Text = "Not Playing";
                    playLabel.Text = "0:00 / 0:00";
                    PlayProgressBar.Value = 0;
                    PlayTick.Enabled = false;
                }
            }
        }
    }
}

Addform 和 Enterform 不是问题,我认为.我已经测试过了.

Addform and Enterform are not the problem, I think. I've tested.

推荐答案

当我将您的代码复制到新项目时,我无法编译它,所以我不确定.不过有一件事是肯定的,你必须在这里发生许多 while 循环.我建议用 Socket 类可用的 async 模式 替换它们.它将删除所有的 while 循环.一些额外的抽象也会有所帮助.

I wasn't able to get your code to compile when I copied it in to a new project, so I'm not sure. One thing is for sure though, you have to many while loops taking place here. I would recommend replacing those with the async pattern that the Socket class has available. It would remove all of the while loops. A little bit of extra abstraction would help as well.

我已经将服务器分解成它自己的类,然后为每个 Socket 连接提供一个包装类,服务器将持有一个引用.

I have broken the server up in to its own class, then provided each Socket connection a wrapper class that the server will hold a reference to.

请注意,我使用的是 Visual Studio 2015,因此我的字符串格式使用新的 C# 6.0 样式.如果您不能使用 C# 6,则很容易转换回 string.Format("{0}", foo);

Note that I am using Visual Studio 2015, so my string formatting is using the new C# 6.0 style. If you can not use C# 6, it is easy enough to convert back to string.Format("{0}", foo);

我们需要一种检查当前服务器状态的方法.这可以像一个小的 enum 一样简单.

We need to have a way of checking what the current server status is. This can be as simple as a small enum.

/// <summary>
/// Provides status values for the server to use, indicating its current state.
/// </summary>
public enum ServerStatus
{
    /// <summary>
    /// The server has stopped.
    /// </summary>
    Stopped,

    /// <summary>
    /// Server is in the process of starting.
    /// </summary>
    Starting,

    /// <summary>
    /// Server is up and running.
    /// </summary>
    Running
}

ConnectedArgs

接下来,Server 类需要引发一些事件,以便在客户端连接时以及客户端何时向服务器发送消息时可以告知 Form 代码隐藏.我们将提供一个名为 ConnectedArgs 的类作为我们的事件处理程序参数.这个类将保存对我们实际客户端的引用,使用我们接下来将创建的包装类.

ConnectedArgs

Next, the Server class needs to raise some events so that the Form code-behind can be told when a client connects, and when the client has sent the server a message. We will provide a class called ConnectedArgs as our event handler argument. This class will hold a reference to our actual client, using the wrapper class we will create next.

public class ConnectedArgs : EventArgs
{
    /// <summary>
    /// Instances a new ConnectedArgs class.
    /// </summary>
    /// <param name="state">The state of a client connection.</param>
    public ConnectedArgs(ConnectionState state)
    {
        this.ConnectedClient = state;
    }

    /// <summary>
    /// Gets the client currently connected.
    /// </summary>
    public ConnectionState ConnectedClient { get; private set; }
}

连接状态

该类负责持有与连接的客户端关联的Socket,并异步处理接收客户端的消息数据.这使用 BeginInvokeEndInvoke 异步模式 包含在套接字中.

ConnectionState

This class is responsible for holding the Socket associated with the connected client, and handle receiving the clients message data asynchronously. This uses the BeginInvoke and EndInvoke async pattern included with the Socket.

此类将有一个事件,用于通知 Form 收到新消息.请注意,这是从我现有的项目之一中提取的,因此数据解析基本上会检查缓冲区,如果缓冲区不包含 \r\n,则认为它不完整.它缓存它并等待来自客户端的下一个数据块来处理并尝试完成.您需要将 ProcessReceivedData 方法替换为处理接收数据的自定义方法.完成后,只需将结果推送到 OnDataReceived 方法,以便您的 Form 可以得到它.

This class will have an event that will be used to notify the Form that a new message was received. Note that this was pulled from one of my existing projects, so the data parsing basically checks the buffer and if the buffer does not include a \r\n, it is considered incomplete. It caches it and waits for the next chunk of data from the client to process and try and complete. You will want to replace the ProcessReceivedData method with your custom method that handles processing the received data. When you are done, just push the results in to the OnDataReceived method so your Form can be given it.

public sealed class ConnectionState
{
    /// <summary>
    /// The size of the buffer that will hold data sent from the client
    /// </summary>
    private readonly int bufferSize;

    /// <summary>
    /// A temporary collection of incomplete messages sent from the client. These must be put together and processed.
    /// </summary>
    private readonly List<string> currentData = new List<string>();

    /// <summary>
    /// What the last chunk of data sent from the client contained.
    /// </summary>
    private string lastChunk = string.Empty;

    /// <summary>
    /// Instances a new PlayerConnectionState.
    /// </summary>
    /// <param name="player">An instance of a Player type that will be performing network communication</param>
    /// <param name="currentSocket">The Socket used to communicate with the client.</param>
    /// <param name="bufferSize">The storage size of the data buffer</param>
    public ConnectionState(Socket currentSocket, int bufferSize)
    {
        this.CurrentSocket = currentSocket;
        this.bufferSize = bufferSize;
        this.Buffer = new byte[bufferSize];
    }

    /// <summary>
    /// This event is raised when the server has received new, valid, data from the client.
    /// </summary>
    public event EventHandler<string> DataReceived;

    /// <summary>
    /// Gets the Socket for the player associated with this state.
    /// </summary>
    public Socket CurrentSocket { get; private set; }

    /// <summary>
    /// Gets the data currently in the network buffer
    /// </summary>
    public byte[] Buffer { get; private set; }

    /// <summary>
    /// Gets if the current network connection is in a valid state.
    /// </summary>
    public bool IsConnectionValid
    {
        get
        {
            return this.CurrentSocket != null && this.CurrentSocket.Connected;
        }
    }

    /// <summary>
    /// Starts listening for network communication sent from the client to the server
    /// </summary>
    public void StartListeningForData()
    {
        this.Buffer = new byte[bufferSize];
        this.CurrentSocket.BeginReceive(this.Buffer, 0, bufferSize, 0, new AsyncCallback(this.ReceiveData), null);
    }

    /// <summary>
    /// Receives the input data from the user.
    /// </summary>
    /// <param name="result">The result.</param>
    private void ReceiveData(IAsyncResult result)
    {
        // If we are no longer in a valid state, dispose of the connection.
        if (!this.IsConnectionValid)
        {
            this.CurrentSocket?.Dispose();
            return;
        }

        int bytesRead = this.CurrentSocket.EndReceive(result);
        if (bytesRead == 0 || !this.Buffer.Any())
        {
            this.StartListeningForData();
            return;
        }

        ProcessReceivedData(bytesRead);
        this.StartListeningForData();
    }

    /// <summary>
    /// Process the data we received from the client.
    /// </summary>
    /// <param name="bytesRead"></param>
    private void ProcessReceivedData(int bytesRead)
    {
        // Encode our input string sent from the client
        this.lastChunk = Encoding.ASCII.GetString(this.Buffer, 0, bytesRead);

        // If the previous chunk did not have a new line feed, then we add this message to the collection of currentData.
        // This lets us build a full message before processing it.
        if (!lastChunk.Contains("\r\n"))
        {
            // Add this to our incomplete data stash and read again.
            this.currentData.Add(lastChunk);
            return;
        }

        // This message contained at least 1 new line, so we split it and process per line.
        List<string> messages = lastChunk.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();

        foreach (string line in this.PruneReceivedMessages(messages))
        {
            this.OnDataReceived(line);
        }
    }

    /// <summary>
    /// Runs through the messages collection and prepends data from a previous, incomplete, message
    /// and updates the internal message tracking state.
    /// </summary>
    /// <param name="messages"></param>
    private List<string> PruneReceivedMessages(List<string> messages)
    {
        // Append the first line to the incomplete line given to us during the last pass if one exists.
        if (this.currentData.Any() && messages.Any())
        {
            messages[0] = string.Format("{0} {1}", string.Join(" ", this.currentData), messages[0]);
            this.currentData.Clear();
        }

        // If we have more than 1 line and the last line in the collection does not end with a line feed
        // then we add it to our current data so it may be completed during the next pass.
        // We then remove it from the lines collection because it can be infered that the remainder will have
        // a new line due to being split on \n.
        if (messages.Count > 1 && !messages.Last().EndsWith("\r\n"))
        {
            this.currentData.Add(messages.Last());
            messages.Remove(messages.Last());
        }

        return messages;
    }

    private void OnDataReceived(string data)
    {
        var handler = this.DataReceived;
        if (handler == null)
        {
            return;
        }

        handler(this, data);
    }
}

服务器

既然我们已经让客户端异步接收和处理数据,我们需要编写服务器组件来真正异步地接受传入的Socket 连接.

这个类将有两个事件,Form 将订阅这些事件.一个用于客户端连接时,另一个用于客户端断开连接时.每个连接的客户端都被分配一个 ConnectionState 并缓存在 List 的集合中.这消除了您保留连接客户端数量的脆弱计数器的需要.

This class will have two events that the Form will subscribe to. One for when a client connects, and another for when the client disconnects. Each client that is connected, is assigned a ConnectionState and cached in a collection of List<ConnectionState>. This removes the need for you to keep a fragile counter of the number of clients connected.

您可以选择连接一个定时器,它会定期修剪 List 集合.您可以检查集合中的每个实例是否都将其 IsConnectionValid 设置为 true.如果返回 false,则将其从集合中删除.

You could optionally wire up a timer that periodically prunes the List<ConnectionState> collection. You can check if each instance in the collection has its IsConnectionValid set to true. If it returns false, remove it from the collection.

/// <summary>
/// The Default Desktop game Server
/// </summary>
public sealed class Server
{
    /// <summary>
    /// The user connection buffer size
    /// </summary>
    private const int UserConnectionBufferSize = 1024;

    /// <summary>
    /// The server socket
    /// </summary>
    private Socket serverSocket;

    /// <summary>
    /// The player connections
    /// </summary>
    private List<ConnectionState> connectedClients;

    /// <summary>
    /// Used for making access to the connectedClients collection thread-safe
    /// </summary>
    private object lockObject = new object();

    /// <summary>
    /// Initializes a new instance of the <see cref="Server"/> class.
    /// </summary>
    public Server()
    {
        this.Status = ServerStatus.Stopped;
        this.connectedClients = new List<ConnectionState>();
    }

    /// <summary>
    /// Occurs when a client connects to the server.
    /// </summary>
    public event EventHandler<ConnectedArgs> ClientConnected;

    /// <summary>
    /// Occurs when a client is disconnected from the server.
    /// </summary>
    public event EventHandler<ConnectedArgs> ClientDisconnected;

    /// <summary>
    /// Gets or sets the port that the server is running on.
    /// </summary>
    public int Port { get; set; }

    /// <summary>
    /// Gets or sets the maximum queued connections.
    /// </summary>
    public int MaxQueuedConnections { get; set; }

    /// <summary>
    /// Gets the current server status.
    /// </summary>
    public ServerStatus Status { get; private set; }

    public void Start()
    {
        if (this.Status != ServerStatus.Stopped)
        {
            throw new InvalidOperationException("The server is either starting or already running. You must stop the server before starting it again.");
        }
        else if (this.Port == 0)
        {
            throw new InvalidOperationException("You can not start the server on Port 0.");
        }

        this.Status = ServerStatus.Starting;

        // Get our server address information
        IPHostEntry serverHost = Dns.GetHostEntry(Dns.GetHostName());
        var serverEndPoint = new IPEndPoint(IPAddress.Any, this.Port);

        // Instance the server socket, bind it to a port.
        this.serverSocket = new Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        this.serverSocket.Bind(serverEndPoint);
        this.serverSocket.Listen(this.MaxQueuedConnections);

        // Begin listening for connections.
        this.serverSocket.BeginAccept(new AsyncCallback(this.ConnectClient), this.serverSocket);

        this.Status = ServerStatus.Running;
    }

    /// <summary>
    /// Stops the server.
    /// </summary>
    public void Stop()
    {
        this.DisconnectAll();

        // We test to ensure the server socket is still connected and active.
        this.serverSocket.Blocking = false;
        try
        {
            this.serverSocket.Send(new byte[1], 0, 0);

            // Message was received meaning it's still receiving, so we can safely shut it down.
            this.serverSocket.Shutdown(SocketShutdown.Both);
        }
        catch (SocketException e)
        {
            // Error code 10035 indicates it works, but will block the socket.
            // This means it is still receiving and we can safely shut it down.
            // Otherwise, it's not receiving anything and we don't need to shut down.
            if (e.NativeErrorCode.Equals(10035))
            {
                this.serverSocket.Shutdown(SocketShutdown.Both);
            }
        }
        finally
        {
            this.Status = ServerStatus.Stopped;
        }
    }

    /// <summary>
    /// Disconnects the specified IServerPlayer object.
    /// </summary>
    /// <param name="connection">The client to disconnect.</param>
    public void Disconnect(ConnectionState connection)
    {
        if (connection != null && connection.IsConnectionValid)
        {
            connection.CurrentSocket.Shutdown(SocketShutdown.Both);
            this.connectedClients.Remove(connection);
            this.OnClientDisconnected(connection);
        }
    }

    /// <summary>
    /// Disconnects everyone from the server.
    /// </summary>
    public void DisconnectAll()
    {
        // Loop through each connection and disconnect them.
        foreach (ConnectionState state in this.connectedClients)
        {
            Socket connection = state.CurrentSocket;
            if (connection != null && connection.Connected)
            {
                connection.Shutdown(SocketShutdown.Both);
                this.OnClientDisconnected(state);
            }
        }

        this.connectedClients.Clear();
    }

    /// <summary>
    /// Called when a client connects.
    /// </summary>
    private void OnClientConnected(ConnectionState connection)
    {
        EventHandler<ConnectedArgs> handler = this.ClientConnected;
        if (handler == null)
        {
            return;
        }

        handler(this, new ConnectedArgs(connection));
    }

    /// <summary>
    /// Called when a client disconnects.
    /// </summary>
    private void OnClientDisconnected(ConnectionState connection)
    {
        EventHandler<ConnectedArgs> handler = this.ClientDisconnected;
        if (handler == null)
        {
            return;
        }

        handler(this, new ConnectedArgs(connection));
    }

    /// <summary>
    /// Connects the client to the server and then passes the connection responsibilities to the client object.
    /// </summary>
    /// <param name="result">The async result.</param>
    private void ConnectClient(IAsyncResult result)
    {
        // Connect and register for network related events.
        Socket connection = this.serverSocket.EndAccept(result);

        // Send our greeting
        byte[] buffer = Encoding.ASCII.GetBytes("Welcome to the Music App Server!");
        connection.BeginSend(buffer, 0, buffer.Length, 0, new AsyncCallback(asyncResult => connection.EndReceive(asyncResult)), null);

        // Fetch the next incoming connection.
        this.serverSocket.BeginAccept(new AsyncCallback(this.ConnectClient), this.serverSocket);

        this.CompleteClientSetup(new ConnectionState(connection, UserConnectionBufferSize));
    }

    /// <summary>
    /// Caches the ConnectionState and has the state begin listening to client data.
    /// </summary>
    /// <param name="connectionState"></param>
    private void CompleteClientSetup(ConnectionState connectionState)
    {
        lock (this.lockObject)
        {
            this.connectedClients.Add(connectionState);
        }

        // Start receiving data from the client.
        connectionState.StartListeningForData();
        this.OnClientConnected(connectionState);
    }
}

表格

现在我们将服务器代码放在一起,可以构建表单.我只是做了一个简单的表格,有一个用于连接的按钮和一个用于查看数据的多行文本框.

Form

Now that we have our server code put together, the Form can be constructed. I just did a simple form, with a single button for connecting and a single multi-line textbox for viewing the data.

我为表单的 Loading 事件和按钮的 Clicked 事件添加了一个事件处理程序.

I added an event handler for the Form's Loading event and for the Button's Clicked event.

public partial class Form1 : Form
{
    private Server server;

    public Form1()
    {
        InitializeComponent();
        server = new Server { Port = 9180, MaxQueuedConnections = 100 };
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        server.ClientConnected += OnClientConnected;
        server.ClientDisconnected += OnClientDisconnected;
    }

    private void OnClientDisconnected(object sender, ConnectedArgs e)
    {
        this.Invoke(new Action(() => this.textBox1.AppendText("A Client disconnected.\n")));
    }

    private void OnClientConnected(object sender, ConnectedArgs e)
    {
        this.Invoke(new Action(() =>
        {
            this.textBox1.AppendText("New Client Connected.\n");
            e.ConnectedClient.DataReceived += OnClientSentDataToServer;
        }));
    }

    private void OnClientSentDataToServer(object sender, string e)
    {
        this.Invoke(new Action(() => this.textBox1.AppendText($"{e}\n")));
    }

    private void button1_Click(object sender, EventArgs e)
    {
        this.textBox1.AppendText("Server starting.\n");
        server.Start();
        this.textBox1.AppendText("Server running.\n");
    }
}

由于服务器作为后台进程运行,您必须在访问 TextBox 时将事件处理程序编组回 UI 线程.您也可以将那部分抽象出来,以便 ServerConnectionState 对象始终推送到您提供它们的调度程序.这减少了您必须执行的 Invoke(Action) 调用次数.

Due to the server running as a background process, you have to marshall the event handlers back to the UI thread when accessing the TextBox. You could abstract that piece out a bit as well, so that the Server and ConnectionState objects always push to the dispatcher you provide them. This reduces the number of Invoke(Action) invocations you have to do.

这适用于多个连接.我启动了服务器,然后在几台不同的计算机上建立了两个不同的连接,没有任何问题.您将需要自定义数据处理和向客户端推送消息.这至少应该可以解决您的多连接问题,并减轻 CPU 的压力(不再出现 while 循环!).

This works well with multiple connections. I started the server up, and then spun up two different connections across a couple different computers without any issues. You will need to customize the data processing and pushing messages to the client. This should at least solve your multiple connection issue, and be less stress on the CPU (no more while loops!).

希望这会有所帮助.

这篇关于当另一个客户端加入时,客户端的 Socket.send 无法正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-20 06:51
查看更多