所以我正在开发一个服务器 - 客户端应用程序,昨天我解决了这些消息的问题。
(Server side not reading message correctly)
今天,我正面临一个新问题。 我的目标是使用来自客户端的消息,通过他的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 = "";
}
}
答案 0 :(得分:1)
问题是您的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
循环。相反,如my answer in another post所述,您应该将Begin/EndInvoke
模式与Socket
类一起使用。另外,我通常对你所拥有的“全局”变量不满。
您可以创建如下的客户端连接:
我们需要一个EventArgs
类,用于将收到的邮件推送到表单。
public class ServerMessageArgs : EventArgs
{
public ServerMessageArgs(string message)
{
this.ServerMessage = message;
}
public string ServerMessage { get; private set; }
}
此类处理连接到服务器以及处理收到的任何消息。收到数据后,它会引发事件处理程序并将其传递给表单。
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.
}
}
现在表格看起来像:
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应用程序中解除的,使用了异步模式并修剪了while循环的应用程序。
在上面发布客户端连接代码后,我意识到您的问题是关于服务器挂起。我仍然会删除while循环。您可以使用我在上面的链接中提到的异步模式,该模式用于服务器端。为了确保答案是本文的一部分,我也将在这里分享代码。
我们需要一种检查当前服务器状态的方法。这可以像小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
}
接下来,Server类需要引发一些事件,以便在客户端连接时以及客户端向服务器发送消息时可以告知Form
代码隐藏。我们将提供一个名为ConnectedArgs
的类作为我们的事件处理程序参数。这个类将使用我们接下来要创建的包装类来保存对我们实际客户端的引用。
public class ConnectedArgs : EventArgs
{
public ConnectedArgs(ConnectionState state)
{
this.ConnectedClient = state;
}
public ConnectionState ConnectedClient { get; private set; }
}
此类负责保存与连接的客户端关联的Socket
,并处理异步接收客户端消息数据。这使用BeginInvoke
和EndInvoke
异步模式included with the Socket。
此类将有一个事件,用于通知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
连接异步。
此类将有两个Form
将订阅的事件。一个用于客户端连接,另一个用于客户端断开连接。连接的每个客户端都会被分配ConnectionState
并缓存在List<ConnectionState>
的集合中。这使您无需对连接的客户端数量进行脆弱的计数。
您可以选择连接定时修剪List<ConnectionState>
集合的计时器。您可以检查集合中的每个实例是否将其IsConnectionValid
设置为true。如果返回false,则将其从集合中删除。
/// <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的Loading
事件和Button的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循环,你就会越好。实际代码比您发布的代码大很多,但在运行时会在服务器上运行得更好。