在这篇文章中,我们将探讨如何使用 TcpListener
在 C# 中实现一个简单的多用户服务器。通过一个实际例子,我们会了解 TcpListener
的常用属性和方法,并且我们将创建一个基本的服务器应用,可以监听客户端连接和处理客户端消息。
TcpListener
?TcpListener
是 .NET 提供的一个用于监听 TCP 网络连接的类。它能侦听传入的客户端连接请求,并与这些客户端通信。
TcpListener
常用方法TcpListener
以准备侦听传入的连接请求。TcpListener
。以下代码展示了如何使用 TcpListener
创建并运行一个简单的服务器应用。这个服务器能够接受多个客户端的连接,并与这些客户端进行消息交换。
C#using System.Collections.Concurrent;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using System.Windows.Forms;
namespace TcpServer
{
public partial class FrmMain : Form
{
private TcpListener tcpListener;
private bool isServerRunning = false;
private ConcurrentDictionary<string, TcpClient> clients = new ConcurrentDictionary<string, TcpClient>();
public FrmMain()
{
InitializeComponent();
}
private async void btnStart_Click(object sender, EventArgs e)
{
// 检查服务器是否已经在运行
if (!isServerRunning)
{
try
{
// 定义要监听的端口号,确保与客户端尝试连接的端口匹配
int port = 3001;
// 使用IPAddress.Any,服务器将监听所有网络接口上的客户端活动
IPAddress localAddr = IPAddress.Any;
// 初始化TcpListener来监听上述IP地址和端口
tcpListener = new TcpListener(localAddr, port);
// 启动TcpListener开始监听
tcpListener.Start();
// 标记服务器状态为运行中
isServerRunning = true;
// 日志输出,提示服务器已启动
AppendTextToLog("Server started...");
// 服务器启动后禁用启动按钮,防止重复启动
btnStart.Enabled = false;
// 异步接受客户端连接,此方法将持续运行,直到服务器停止
await AcceptClientsAsync(tcpListener);
}
catch (Exception ex)
{
// 如果在启动服务器过程中发生异常,显示错误消息
MessageBox.Show($"Exception occurred: {ex.Message}");
}
}
}
private async Task AcceptClientsAsync(TcpListener listener)
{
// 持续监听并接受客户端,直到服务器停止运行
while (isServerRunning)
{
try
{
// 异步等待并接受一个连接尝试
TcpClient client = await listener.AcceptTcpClientAsync();
// 使用客户端的远程终结点作为唯一标识符
string clientKey = $"{client.Client.RemoteEndPoint}";
// 将新连接的客户端添加到客户端列表中
clients.TryAdd(clientKey, client);
// 更新客户端列表显示(假设这是一个UI操作)
UpdateClientList();
// 记录新客户端的连接
AppendTextToLog("Client connected: " + clientKey);
// 异步处理该客户端的数据交换
HandleClientAsync(client, clientKey);
}
catch (OperationCanceledException)
{
// 操作被取消,可能是因为服务器正在关闭
AppendTextToLog("Server stopping: Accept operation canceled.");
break; // 跳出循环
}
catch (ObjectDisposedException)
{
// TcpListener已经被关闭
AppendTextToLog("Server stopping: Listener has been disposed.");
break; // 跳出循环
}
catch (Exception ex)
{
// 检查异常信息以确定是否是因为操作被取消
if (ex.Message.Contains("The I/O operation has been aborted because of either a thread exit or an application request"))
{
AppendTextToLog("Server stopping: Operation aborted.");
break; // 由于服务器关闭导致的异常,跳出循环
}
else
{
// 如果是其他类型的异常,记录异常信息
AppendTextToLog($"Exception occurred: {ex.Message}");
// 发生异常,将服务器运行状态标记为停止
isServerRunning = false;
}
}
}
}
private async void HandleClientAsync(TcpClient client, string clientKey)
{
try
{
// 获取与客户端关联的网络流
using (NetworkStream stream = client.GetStream())
{
// 分配一个缓冲区用于接收数据
byte[] buffer = new byte[1024];
int bytesRead;
StringBuilder sb = new StringBuilder();
// 持续读取网络流中的数据,直到没有更多数据(即客户端关闭连接)
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
// 将接收到的字节数据转换为字符串
string receivedMessage = Encoding.UTF8.GetString(buffer, 0, bytesRead);
// 记录接收到的消息
AppendTextToLog($"Received: {receivedMessage}");
sb.Append(receivedMessage);
if (receivedMessage.EndsWith("$$$"))
{
string json = sb.ToString().Substring(0, sb.ToString().Length - 3);
sb.Append(json);
MyTcpLib.Message message = Newtonsoft.Json.JsonConvert.DeserializeObject
<MyTcpLib.Message>(json);
if (message != null)
{
if (message.Type == "FILE")
{
// 将Base64字符串转换为字节数组
byte[] fileBytes = Convert.FromBase64String(message.Context);
File.WriteAllBytes("d:\\" + message.FileName, fileBytes);
}
}
sb=new StringBuilder();
}
}
}
}
catch (Exception ex)
{
// 处理过程中发生异常,记录异常信息
AppendTextToLog($"Exception occurred: {ex.Message}");
}
finally
{
// 无论是否成功处理客户端,最终都尝试从客户端列表中移除该客户端
if (clients.TryRemove(clientKey, out _))
{
// 更新客户端列表的显示
UpdateClientList();
}
// 关闭客户端连接
client.Close();
}
}
private void AppendTextToLog(string message)
{
if (InvokeRequired)
{
Invoke(new Action(() => AppendTextToLog(message)));
return;
}
txtLog.AppendText(message + Environment.NewLine);
}
private void UpdateClientList()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateClientList));
return;
}
lstClient.Items.Clear();
foreach (var clientKey in clients.Keys)
{
lstClient.Items.Add(clientKey);
}
}
private void btnStop_Click(object sender, EventArgs e)
{
if (isServerRunning && tcpListener != null)
{
try
{
// 停止TcpListener接受新的连接请求
tcpListener.Stop();
// 标记服务器状态为停止
isServerRunning = false;
// 日志输出,提示服务器已停止
AppendTextToLog("Server stopped...");
// 重新启用启动按钮,允许用户重新启动服务器
btnStart.Enabled = true;
}
catch (Exception ex)
{
}
}
}
private void cmnuMain_SendMsg_Click(object sender, EventArgs e)
{
try
{
var selectClient = clients[lstClient.SelectedItem.ToString()];
frmSendMsg frmSendMsg = new frmSendMsg();
frmSendMsg.SendMessage = (x) =>
{
byte[] data = Encoding.UTF8.GetBytes(x);
var stream = selectClient.GetStream();
stream.WriteAsync(data);
};
frmSendMsg.ShowDialog();
}
catch
{
}
}
private void lstClient_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
cmnuMain.Show(lstClient, new Point(e.X, e.Y));
}
}
}
}
frmSendMsg
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TcpServer
{
public partial class frmSendMsg : Form
{
public Action<string> SendMessage;
public frmSendMsg()
{
InitializeComponent();
}
private void btnSendMsg_Click(object sender, EventArgs e)
{
if(SendMessage != null)
{
SendMessage(txtMsg.Text);
}
this.Close();
}
}
}
使用 TcpListener
创建一个简单的多连接服务器是学习网络编程的良好起点。本篇文章中的示例展示了如何使用它的基本属性和方法,以及如何实现基本的客户端连接管理和消息处理。通过 TcpListener
和异步编程,我们可以构建高效且可扩展的网络应用。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!