Form1 不启动
Form1 does not start
所以我正在开发一个服务器-客户端应用程序,昨天我解决了一个消息问题。
()
今天,我遇到了一个新问题。
我的 objective 是通过他的 ID 单击网站按钮,使用来自客户的消息。所以我创建了一个迷你 WebBrowser 来显示我想使用的网站。问题是,如果我用迷你 WebBrowser 启动服务器,浏览器本身不会显示,但如果我删除
serverfunction();
它会出现,但服务器不会打开,我很乐意接受任何帮助。 TIA
源代码 C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
serverfunction();
}
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
}
public void serverfunction()
{
int port = 80;
IPAddress localAddr = IPAddress.Parse("192.168.1.68");
TcpListener server = new TcpListener(localAddr, port);
server.Start();
byte[] bytes = new byte[2048];
string data;
while (true)
{
TcpClient client = server.AcceptTcpClient();
NetworkStream stream = client.GetStream();
int i;
i = stream.Read(bytes, 0, bytes.Length);
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Global.message = StripExtended(data);
Console.WriteLine(Global.message);
}
}
static string StripExtended(string arg)
{
StringBuilder buffer = new StringBuilder(arg.Length); //Max length
foreach (char ch in arg)
{
UInt16 num = Convert.ToUInt16(ch);//In .NET, chars are UTF-16
//The basic characters have the same code points as ASCII, and the extended characters are bigger
if ((num >= 32u) && (num <= 126u)) buffer.Append(ch);
}
return buffer.ToString();
}
}
public class Global
{
public static string message = "";
}
}
问题是您的 ServerFunction
调用创建了一个无限循环
while (true)
{
// ...
}
您可以通过将其推送到后台线程轻松解决该问题。
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Task.Run(() => serverfunction());
}
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
}
public void serverfunction()
{
int port = 80;
IPAddress localAddr = IPAddress.Parse("192.168.1.68");
TcpListener server = new TcpListener(localAddr, port);
server.Start();
byte[] bytes = new byte[2048];
string data;
while (true)
{
TcpClient client = server.AcceptTcpClient();
NetworkStream stream = client.GetStream();
int i;
i = stream.Read(bytes, 0, bytes.Length);
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Global.message = StripExtended(data);
Console.WriteLine(Global.message);
}
}
static string StripExtended(string arg)
{
StringBuilder buffer = new StringBuilder(arg.Length); //Max length
foreach (char ch in arg)
{
UInt16 num = Convert.ToUInt16(ch);//In .NET, chars are UTF-16
//The basic characters have the same code points as ASCII, and the extended characters are bigger
if ((num >= 32u) && (num <= 126u)) buffer.Append(ch);
}
return buffer.ToString();
}
}
public class Global
{
public static string message = "";
}
}
您需要确保将任何 UI 交互编组回 UI 线程。
但是我不认为这是最好的解决方案。您应该一起避免 while
循环。相反,如 所述,您应该将 Begin/EndInvoke
模式与 Socket
class 一起使用。此外,我通常不喜欢 "global" 变量,比如你拥有的变量。
您可以像下面这样创建客户端连接:
ServerMessageArgs
我们需要一个 EventArgs
class 用于将收到的消息推送到表单。
public class ServerMessageArgs : EventArgs
{
public ServerMessageArgs(string message)
{
this.ServerMessage = message;
}
public string ServerMessage { get; private set; }
}
客户端连接
此 class 负责连接到服务器,并处理收到的任何消息。收到数据后,它会引发事件处理程序并将其传递给表单。
public class ClientConnection : IDisposable
{
private const int _bufferSize = 1024;
private Socket socket = null;
public event EventHandler Connected;
public event EventHandler<ServerMessageArgs> ServerMessageArrived;
public bool IsConnected { get; private set; }
public void ConnectToServerAsync(string url, int port)
{
var endPoint = this.GetIPEndPointFromHostName(url, port, false);
this.socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
this.socket.BeginConnect(endPoint, new AsyncCallback(this.ConnectedCallback), this.socket);
this.IsConnected = true;
}
public void Dispose()
{
if (this.socket.Connected)
{
this.socket.Disconnect(false);
}
this.socket.Dispose();
}
public void SendMessage(string message)
{
// Strip the ending carriage return and linefeed characters and re-add them
// We do this in the event that only one out of the two were provided, or they
// came in out of order.
message = $"{message.TrimEnd('\r', '\n')}\r\n";
// Convert the string data to byte data using ASCII encoding.
byte[] byteData = Encoding.UTF8.GetBytes(message);
// Begin sending the data to the remote device.
this.socket.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), this.socket);
}
private void SendCallback(IAsyncResult ar)
{
try
{
// Complete sending the data to the remote device.
int bytesSent = this.socket.EndSend(ar);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private void ConnectedCallback(IAsyncResult result)
{
this.socket.EndConnect(result);
var handler = this.Connected;
if (handler == null)
{
return;
}
handler(this, new EventArgs());
this.ReceiveData();
}
private void ReceiveData()
{
var buffer = new byte[_bufferSize];
this.socket.BeginReceive(
buffer,
0,
_bufferSize,
0,
new AsyncCallback(ReceiveCallback),
buffer);
}
private void ReceiveCallback(IAsyncResult result)
{
if (!this.IsConnected)
{
return;
}
byte[] buffer = (byte[])result.AsyncState;
int bytesRead = this.socket.EndReceive(result);
var contentBuilder = new StringBuilder();
string content = string.Empty;
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
contentBuilder.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
foreach (string message in contentBuilder.ToString().Split('\n'))
{
this.OnServerMessageArrived(message);
}
// Get the rest of the data.
this.socket.BeginReceive(
(buffer = new byte[_bufferSize]),
0,
_bufferSize,
0,
new AsyncCallback(ReceiveCallback),
buffer);
}
}
private void OnServerMessageArrived(string content)
{
var handler = this.ServerMessageArrived;
if (handler == null)
{
return;
}
handler(this, new ServerMessageArgs(content));
}
private IPEndPoint GetIPEndPointFromHostName(string hostName, int port, bool throwIfMoreThanOneIP)
{
var addresses = Dns.GetHostAddresses(hostName);
if (addresses.Length == 0)
{
throw new ArgumentException(
"Unable to retrieve address from specified host name.",
"hostName"
);
}
else if (throwIfMoreThanOneIP && addresses.Length > 1)
{
throw new ArgumentException(
"There is more that one IP address to the specified host.",
"hostName"
);
}
return new IPEndPoint(addresses[0], port); // Port gets validated here.
}
}
表格 1
现在表格看起来像:
public partial class Form1 : Form
{
private ClientConnection client;
public Form1()
{
InitializeComponent();
client = new ClientConnection();
}
private void Form1_Load(object sender, EventArgs e)
{
client.ServerMessageArrived += (s, message) =>
{
Console.WriteLine(message);
client.SendMessage($"{message} received by server.");
};
client.Connected += (s, args) => Console.WriteLine("Connected.");
client.ConnectToServerAsync("192.168.1.68", 80);
}
}
我无法测试上面的内容,因为不知道期望返回什么数据,但是这段代码是从我编写的 IRC 应用程序中提取的,使用异步模式和 p运行ing while循环的应用程序。
编辑
post输入上面的客户端连接代码后,我意识到您的问题是关于服务器挂起的。我仍然会删除 while 循环。您可以使用我在上面 link 中提到的用于服务器端的异步模式。为了确保答案是这个post的一部分,我也会在这里分享代码。
服务器状态
我们需要一种检查当前服务器状态的方法。这可以像一个小的 enum
.
一样简单
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
接下来,服务器 class 需要引发一些事件,以便 Form
代码隐藏可以在客户端连接时以及客户端向服务器发送消息时被告知。我们将提供一个名为 ConnectedArgs
的 class 作为我们的事件处理程序参数。这个 class 将保存对我们实际客户端的引用,使用我们接下来将创建的包装器 class。
public class ConnectedArgs : EventArgs
{
public ConnectedArgs(ConnectionState state)
{
this.ConnectedClient = state;
}
public ConnectionState ConnectedClient { get; private set; }
}
连接状态
这个class负责保存与连接的客户端关联的Socket
,并处理异步接收客户端消息数据。这使用 BeginInvoke
和 EndInvoke
异步模式 .
此 class 将有一个事件,用于通知 Form
已收到新消息。请注意,这是从我现有的一个项目中提取的,因此数据解析基本上会检查缓冲区,如果缓冲区不包含 \r\n,则认为它不完整。它缓存它并等待来自客户端的下一个数据块来处理并尝试完成。您需要将 ProcessReceivedData
方法替换为处理接收数据的自定义方法。完成后,只需将结果推送到 OnDataReceived
方法,这样您的 Form
就可以得到它。
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;
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
连接。
此 class 将有两个 Form
将订阅的事件。一个用于客户端连接时,另一个用于客户端断开连接时。每个连接的客户端都被分配了一个 ConnectionState
并缓存在 List<ConnectionState>
的集合中。这消除了您保持连接的客户端数量的脆弱计数器的需要。
您可以选择性地连接一个计时器,定期 p运行es List<ConnectionState>
集合。您可以检查集合中的每个实例是否将其 IsConnectionValid
设置为 true。如果它 returns 为假,则将其从集合中删除。
/// <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);
}
}
表格
现在我们已经将服务器代码放在一起,可以构建表单了。我只是做了一个简单的表单,只有一个用于连接的按钮和一个用于查看数据的多行文本框。
我为表单的 Loading
事件和按钮的 Clicked
事件添加了事件处理程序。
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");
}
}
这为您提供了一种处理服务器端的漂亮、干净的方法。任何时候你都可以避免服务器中的 while 循环,你就越好。实际代码比您 post 编写的代码大很多,但 运行 在 运行 时间内在您的服务器上会好得多。
所以我正在开发一个服务器-客户端应用程序,昨天我解决了一个消息问题。
(
今天,我遇到了一个新问题。 我的 objective 是通过他的 ID 单击网站按钮,使用来自客户的消息。所以我创建了一个迷你 WebBrowser 来显示我想使用的网站。问题是,如果我用迷你 WebBrowser 启动服务器,浏览器本身不会显示,但如果我删除
serverfunction();
它会出现,但服务器不会打开,我很乐意接受任何帮助。 TIA
源代码 C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
serverfunction();
}
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
}
public void serverfunction()
{
int port = 80;
IPAddress localAddr = IPAddress.Parse("192.168.1.68");
TcpListener server = new TcpListener(localAddr, port);
server.Start();
byte[] bytes = new byte[2048];
string data;
while (true)
{
TcpClient client = server.AcceptTcpClient();
NetworkStream stream = client.GetStream();
int i;
i = stream.Read(bytes, 0, bytes.Length);
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Global.message = StripExtended(data);
Console.WriteLine(Global.message);
}
}
static string StripExtended(string arg)
{
StringBuilder buffer = new StringBuilder(arg.Length); //Max length
foreach (char ch in arg)
{
UInt16 num = Convert.ToUInt16(ch);//In .NET, chars are UTF-16
//The basic characters have the same code points as ASCII, and the extended characters are bigger
if ((num >= 32u) && (num <= 126u)) buffer.Append(ch);
}
return buffer.ToString();
}
}
public class Global
{
public static string message = "";
}
}
问题是您的 ServerFunction
调用创建了一个无限循环
while (true)
{
// ...
}
您可以通过将其推送到后台线程轻松解决该问题。
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Task.Run(() => serverfunction());
}
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
}
public void serverfunction()
{
int port = 80;
IPAddress localAddr = IPAddress.Parse("192.168.1.68");
TcpListener server = new TcpListener(localAddr, port);
server.Start();
byte[] bytes = new byte[2048];
string data;
while (true)
{
TcpClient client = server.AcceptTcpClient();
NetworkStream stream = client.GetStream();
int i;
i = stream.Read(bytes, 0, bytes.Length);
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Global.message = StripExtended(data);
Console.WriteLine(Global.message);
}
}
static string StripExtended(string arg)
{
StringBuilder buffer = new StringBuilder(arg.Length); //Max length
foreach (char ch in arg)
{
UInt16 num = Convert.ToUInt16(ch);//In .NET, chars are UTF-16
//The basic characters have the same code points as ASCII, and the extended characters are bigger
if ((num >= 32u) && (num <= 126u)) buffer.Append(ch);
}
return buffer.ToString();
}
}
public class Global
{
public static string message = "";
}
}
您需要确保将任何 UI 交互编组回 UI 线程。
但是我不认为这是最好的解决方案。您应该一起避免 while
循环。相反,如 Begin/EndInvoke
模式与 Socket
class 一起使用。此外,我通常不喜欢 "global" 变量,比如你拥有的变量。
您可以像下面这样创建客户端连接:
ServerMessageArgs
我们需要一个 EventArgs
class 用于将收到的消息推送到表单。
public class ServerMessageArgs : EventArgs
{
public ServerMessageArgs(string message)
{
this.ServerMessage = message;
}
public string ServerMessage { get; private set; }
}
客户端连接
此 class 负责连接到服务器,并处理收到的任何消息。收到数据后,它会引发事件处理程序并将其传递给表单。
public class ClientConnection : IDisposable
{
private const int _bufferSize = 1024;
private Socket socket = null;
public event EventHandler Connected;
public event EventHandler<ServerMessageArgs> ServerMessageArrived;
public bool IsConnected { get; private set; }
public void ConnectToServerAsync(string url, int port)
{
var endPoint = this.GetIPEndPointFromHostName(url, port, false);
this.socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
this.socket.BeginConnect(endPoint, new AsyncCallback(this.ConnectedCallback), this.socket);
this.IsConnected = true;
}
public void Dispose()
{
if (this.socket.Connected)
{
this.socket.Disconnect(false);
}
this.socket.Dispose();
}
public void SendMessage(string message)
{
// Strip the ending carriage return and linefeed characters and re-add them
// We do this in the event that only one out of the two were provided, or they
// came in out of order.
message = $"{message.TrimEnd('\r', '\n')}\r\n";
// Convert the string data to byte data using ASCII encoding.
byte[] byteData = Encoding.UTF8.GetBytes(message);
// Begin sending the data to the remote device.
this.socket.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), this.socket);
}
private void SendCallback(IAsyncResult ar)
{
try
{
// Complete sending the data to the remote device.
int bytesSent = this.socket.EndSend(ar);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private void ConnectedCallback(IAsyncResult result)
{
this.socket.EndConnect(result);
var handler = this.Connected;
if (handler == null)
{
return;
}
handler(this, new EventArgs());
this.ReceiveData();
}
private void ReceiveData()
{
var buffer = new byte[_bufferSize];
this.socket.BeginReceive(
buffer,
0,
_bufferSize,
0,
new AsyncCallback(ReceiveCallback),
buffer);
}
private void ReceiveCallback(IAsyncResult result)
{
if (!this.IsConnected)
{
return;
}
byte[] buffer = (byte[])result.AsyncState;
int bytesRead = this.socket.EndReceive(result);
var contentBuilder = new StringBuilder();
string content = string.Empty;
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
contentBuilder.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
foreach (string message in contentBuilder.ToString().Split('\n'))
{
this.OnServerMessageArrived(message);
}
// Get the rest of the data.
this.socket.BeginReceive(
(buffer = new byte[_bufferSize]),
0,
_bufferSize,
0,
new AsyncCallback(ReceiveCallback),
buffer);
}
}
private void OnServerMessageArrived(string content)
{
var handler = this.ServerMessageArrived;
if (handler == null)
{
return;
}
handler(this, new ServerMessageArgs(content));
}
private IPEndPoint GetIPEndPointFromHostName(string hostName, int port, bool throwIfMoreThanOneIP)
{
var addresses = Dns.GetHostAddresses(hostName);
if (addresses.Length == 0)
{
throw new ArgumentException(
"Unable to retrieve address from specified host name.",
"hostName"
);
}
else if (throwIfMoreThanOneIP && addresses.Length > 1)
{
throw new ArgumentException(
"There is more that one IP address to the specified host.",
"hostName"
);
}
return new IPEndPoint(addresses[0], port); // Port gets validated here.
}
}
表格 1
现在表格看起来像:
public partial class Form1 : Form
{
private ClientConnection client;
public Form1()
{
InitializeComponent();
client = new ClientConnection();
}
private void Form1_Load(object sender, EventArgs e)
{
client.ServerMessageArrived += (s, message) =>
{
Console.WriteLine(message);
client.SendMessage($"{message} received by server.");
};
client.Connected += (s, args) => Console.WriteLine("Connected.");
client.ConnectToServerAsync("192.168.1.68", 80);
}
}
我无法测试上面的内容,因为不知道期望返回什么数据,但是这段代码是从我编写的 IRC 应用程序中提取的,使用异步模式和 p运行ing while循环的应用程序。
编辑
post输入上面的客户端连接代码后,我意识到您的问题是关于服务器挂起的。我仍然会删除 while 循环。您可以使用我在上面 link 中提到的用于服务器端的异步模式。为了确保答案是这个post的一部分,我也会在这里分享代码。
服务器状态
我们需要一种检查当前服务器状态的方法。这可以像一个小的 enum
.
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
接下来,服务器 class 需要引发一些事件,以便 Form
代码隐藏可以在客户端连接时以及客户端向服务器发送消息时被告知。我们将提供一个名为 ConnectedArgs
的 class 作为我们的事件处理程序参数。这个 class 将保存对我们实际客户端的引用,使用我们接下来将创建的包装器 class。
public class ConnectedArgs : EventArgs
{
public ConnectedArgs(ConnectionState state)
{
this.ConnectedClient = state;
}
public ConnectionState ConnectedClient { get; private set; }
}
连接状态
这个class负责保存与连接的客户端关联的Socket
,并处理异步接收客户端消息数据。这使用 BeginInvoke
和 EndInvoke
异步模式
此 class 将有一个事件,用于通知 Form
已收到新消息。请注意,这是从我现有的一个项目中提取的,因此数据解析基本上会检查缓冲区,如果缓冲区不包含 \r\n,则认为它不完整。它缓存它并等待来自客户端的下一个数据块来处理并尝试完成。您需要将 ProcessReceivedData
方法替换为处理接收数据的自定义方法。完成后,只需将结果推送到 OnDataReceived
方法,这样您的 Form
就可以得到它。
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;
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
连接。
此 class 将有两个 Form
将订阅的事件。一个用于客户端连接时,另一个用于客户端断开连接时。每个连接的客户端都被分配了一个 ConnectionState
并缓存在 List<ConnectionState>
的集合中。这消除了您保持连接的客户端数量的脆弱计数器的需要。
您可以选择性地连接一个计时器,定期 p运行es List<ConnectionState>
集合。您可以检查集合中的每个实例是否将其 IsConnectionValid
设置为 true。如果它 returns 为假,则将其从集合中删除。
/// <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);
}
}
表格
现在我们已经将服务器代码放在一起,可以构建表单了。我只是做了一个简单的表单,只有一个用于连接的按钮和一个用于查看数据的多行文本框。
我为表单的 Loading
事件和按钮的 Clicked
事件添加了事件处理程序。
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");
}
}
这为您提供了一种处理服务器端的漂亮、干净的方法。任何时候你都可以避免服务器中的 while 循环,你就越好。实际代码比您 post 编写的代码大很多,但 运行 在 运行 时间内在您的服务器上会好得多。