406 lines
11 KiB
C#
406 lines
11 KiB
C#
using HexcalMC.Base;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Net;
|
||
using System.Net.Sockets;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Windows.Forms;
|
||
using Timer = System.Windows.Forms.Timer;
|
||
|
||
namespace HexcalMC.Hexcal
|
||
{
|
||
public class TcpIpServer
|
||
{
|
||
public delegate void EventHandlerRaisedMessage(string clientIp, string msg);
|
||
|
||
public delegate void EventHandlerRaisedStatus(EnumTcpIpServer type, string msg);
|
||
|
||
public enum EnumTcpIpServer
|
||
{
|
||
StartListen = 1,
|
||
ClientConnect = 2,
|
||
SocketException = 8,
|
||
ConnectException = 9,
|
||
Exception = -1
|
||
}
|
||
|
||
private readonly Dictionary<string, Socket> _dictSocket = new Dictionary<string, Socket>();
|
||
private readonly Dictionary<string, Thread> _dictThread = new Dictionary<string, Thread>();
|
||
|
||
private readonly string _strServerIp = "127.0.0.1"; //服务器的IP地址
|
||
private readonly string _strServerPort = "8080"; //端口号
|
||
private Socket _mWatchSocket;
|
||
|
||
private Thread _mWatchThread; // 负责监听客户端连接请求的线程;
|
||
|
||
/// <summary>
|
||
/// 使用模式,默认=1,接收任意数据显示;2=前两个字节为数据长度
|
||
/// </summary>
|
||
public int UseMode = 1;
|
||
|
||
//=========================================================================
|
||
public TcpIpServer(string serverIp, string serverPort)
|
||
{
|
||
_strServerIp = serverIp;
|
||
_strServerPort = serverPort;
|
||
}
|
||
|
||
public string[] SocketNames => _dictSocket.Keys.ToArray();
|
||
|
||
public bool ConnectStatus
|
||
{
|
||
get
|
||
{
|
||
if (_mWatchSocket == null)
|
||
return false;
|
||
return _mWatchSocket.Connected && _mWatchThread != null;
|
||
}
|
||
}
|
||
|
||
public bool WatchStatus { get; private set; }
|
||
|
||
public bool StartListen()
|
||
{
|
||
try
|
||
{
|
||
//_serverTimer.Tick += ServerTimerLoop; //510,增加时钟,判断是否有断掉的连接。
|
||
//_serverTimer.Start();
|
||
|
||
_mWatchSocket =
|
||
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //创建负责监听的套接字,注意其中的参数;
|
||
IPAddress mIpAddr = IPAddress.Parse(_strServerIp); //获得文本框中的IP对象;
|
||
IPEndPoint mEndPoint = new IPEndPoint(mIpAddr, int.Parse(_strServerPort)); //创建包含ip和端口号的网络节点对象;
|
||
try
|
||
{
|
||
_mWatchSocket.Bind(mEndPoint); // 将负责监听的套接字绑定到唯一的ip和端口上;
|
||
}
|
||
catch (SocketException se)
|
||
{
|
||
MessageBox.Show("异常:" + se.Message);
|
||
return false;
|
||
}
|
||
|
||
WatchStatus = true;
|
||
_mWatchSocket.Listen(10); // 设置监听队列的长度
|
||
_mWatchThread = new Thread(WatchThread); // 创建负责监听的线程
|
||
_mWatchThread.IsBackground = true;
|
||
_mWatchThread.Start();
|
||
RaisedStatus(EnumTcpIpServer.StartListen, "服务器启动监听成功!IP=" + _strServerIp + ", Port=" + _strServerPort);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show("启动服务器监听失败:" + ex.Message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public void StopListen()
|
||
{
|
||
try
|
||
{
|
||
WatchStatus = false;
|
||
if (_mWatchSocket != null)
|
||
{
|
||
_mWatchSocket.Close();
|
||
_mWatchSocket.Dispose();
|
||
_mWatchSocket = null;
|
||
}
|
||
|
||
_dictThread.Clear();
|
||
_dictSocket.Clear();
|
||
}
|
||
catch
|
||
{
|
||
throw new Exception("TCP /IP 服务器关闭失败!");
|
||
}
|
||
}
|
||
|
||
//监听线程
|
||
private void WatchThread()
|
||
{
|
||
try
|
||
{
|
||
while (WatchStatus) // 持续不断的监听客户端的连接请求;
|
||
{
|
||
// 开始监听客户端连接请求,Accept方法会阻断当前的线程;
|
||
Socket sokClient = _mWatchSocket.Accept(); // 一旦监听到一个客户端的请求,就返回一个与该客户端通信的套接字;
|
||
|
||
#region 在新建连接时,判断以前的连接是否正常存在。
|
||
|
||
if (_dictSocket != null)
|
||
{
|
||
if (_dictSocket.Count != 0)
|
||
{
|
||
_dictSocket.Values.ToArray();
|
||
for (int i = _dictSocket.Values.ToArray().Length - 1; i >= 0; i--)
|
||
{
|
||
if (_dictSocket.Values.ToArray()[i].Poll(10, SelectMode.SelectRead))
|
||
{
|
||
RemoveSocketClient(_dictSocket.Keys.ToArray()[i]);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion 在新建连接时,判断以前的连接是否正常存在。
|
||
|
||
_dictSocket.Add(sokClient.RemoteEndPoint.ToString(), sokClient); // 将与客户端连接的套接字对象添加到集合中;
|
||
RaisedStatus(EnumTcpIpServer.ClientConnect,
|
||
"客户端连接成功!:" + sokClient.RemoteEndPoint);
|
||
|
||
Thread thread = new Thread(ReceiveThread);
|
||
thread.IsBackground = true;
|
||
thread.Start(sokClient);
|
||
|
||
_dictThread.Add(sokClient.RemoteEndPoint.ToString(), thread); // 将新建的线程 添加 到线程的集合中去。
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
//throw new Exception("WatchThread异常" + e);
|
||
}
|
||
}
|
||
|
||
public event EventHandler<byte[]> DataReceived;
|
||
|
||
protected virtual void OnDataReceivedByte(byte[] data)
|
||
{
|
||
// 触发事件
|
||
DataReceived?.Invoke(this, data);
|
||
}
|
||
|
||
//接收线程
|
||
private void ReceiveThread(object sokObj)
|
||
{
|
||
Socket sokClient = sokObj as Socket;
|
||
while (WatchStatus)
|
||
{
|
||
// 定义一个2M的缓存区;
|
||
byte[] arrMsgRec = new byte[sokClient.Available];
|
||
// 将接受到的数据存入到输入 arrMsgRec中;
|
||
int length = -1;
|
||
try
|
||
{
|
||
length = sokClient.Receive(arrMsgRec); // 接收数据,并返回数据的长度;
|
||
}
|
||
catch (SocketException se)
|
||
{
|
||
RaisedStatus(EnumTcpIpServer.SocketException, "异常:SocketException=" + se.Message);
|
||
RemoveSocketClient(sokClient.RemoteEndPoint.ToString());
|
||
return;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
RaisedStatus(EnumTcpIpServer.ConnectException, "异常:Exception=" + ex.Message);
|
||
RemoveSocketClient(sokClient.RemoteEndPoint.ToString());
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
switch (UseMode)
|
||
{
|
||
case 1:
|
||
if (length > 0)
|
||
{
|
||
string strData = Encoding.Default.GetString(arrMsgRec); // 将接受到的字节数据转化成字符串;
|
||
//strData = strData.Substring(0, length);
|
||
//RaisedMessage(sokClient.RemoteEndPoint.ToString(), strData.Replace("\0", "<0>"));
|
||
RaisedMessage(sokClient.RemoteEndPoint.ToString(), strData.Replace("\0", "."));
|
||
}
|
||
|
||
;
|
||
break;
|
||
|
||
case 2:
|
||
if (length > 0 && arrMsgRec.Length > 2)
|
||
{
|
||
string strData =
|
||
Encoding.Default.GetString(arrMsgRec, 2,
|
||
arrMsgRec.Length - 2); // 将接受到的字节数据转化成字符串;
|
||
|
||
RaisedMessage(sokClient.RemoteEndPoint.ToString(), strData);
|
||
}
|
||
|
||
break;
|
||
|
||
case 3:
|
||
if (length > 0)
|
||
{
|
||
OnDataReceivedByte(arrMsgRec);
|
||
}
|
||
|
||
break;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
RaisedStatus(EnumTcpIpServer.Exception, "获取Socket数据异常:ex=" + ex.Message);
|
||
}
|
||
}
|
||
}
|
||
|
||
//删除客户端对象
|
||
private void RemoveSocketClient(string strRemoteEndPoint)
|
||
{
|
||
// 从通信套接字集合中 删除被中断连接的通信套接字;
|
||
_dictSocket.Remove(strRemoteEndPoint);
|
||
|
||
// 从通信线程集合中 删除被中断连接的通信线程对象;
|
||
_dictThread.Remove(strRemoteEndPoint);
|
||
}
|
||
|
||
//发送消息
|
||
public void SendMessage(string strSocketKey, string strMsg)
|
||
{
|
||
byte[] arrMsg = Encoding.Default.GetBytes(strMsg);
|
||
_dictSocket[strSocketKey].Send(arrMsg);
|
||
}
|
||
|
||
public void SendMessage(string strSocketKey, byte[] arrMsg)
|
||
{
|
||
_dictSocket[strSocketKey].Send(arrMsg);
|
||
}
|
||
|
||
//发送数据函数(字符串, 自动添加长度(byte格式))
|
||
public string SendMessage2(string strSocketKey, string strMsg)
|
||
{
|
||
try
|
||
{
|
||
if (_dictSocket[strSocketKey].Connected)
|
||
{
|
||
byte[] arrLength = new byte[2];
|
||
arrLength = BitConverter.GetBytes(Convert.ToInt16(strMsg.Length));
|
||
byte[] writeBuffer = Encoding.Default.GetBytes(strMsg);
|
||
writeBuffer = arrLength.Concat(writeBuffer).ToArray();
|
||
|
||
_dictSocket[strSocketKey].Send(writeBuffer);
|
||
return "";
|
||
}
|
||
|
||
return "NotConnect";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return "error:" + ex.Message;
|
||
}
|
||
}
|
||
|
||
public bool TrySendData(Socket socket, byte[] data)
|
||
{
|
||
int sentBytes = socket.Send(data);
|
||
return sentBytes == data.Length; // 检查是否完全发送成功
|
||
}
|
||
|
||
public void SendMessageToAllClients(string strMsg)
|
||
{
|
||
byte[] arrMsg = Encoding.Default.GetBytes(strMsg);
|
||
foreach (Socket soc in _dictSocket.Values)
|
||
{
|
||
bool success = TrySendData(soc, arrMsg);
|
||
if (!success)
|
||
{
|
||
// 如果发送失败,进行重发逻辑
|
||
int attemptCount = 0;
|
||
const int maxAttempts = 3;
|
||
while (!success && attemptCount < maxAttempts)
|
||
{
|
||
success = TrySendData(soc, arrMsg);
|
||
attemptCount++;
|
||
DebugDfn.AddLogText("正在重发");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public void SendMessageToAllClients(byte[] arrMsg)
|
||
{
|
||
foreach (Socket soc in _dictSocket.Values)
|
||
{
|
||
soc.Send(arrMsg);
|
||
}
|
||
}
|
||
|
||
public event EventHandlerRaisedStatus OnRaisedStatus;
|
||
|
||
// 异步或同步触发自定义事件,并在目标控件是 Windows Forms 控件的情况下添加到目标控件的消息队列中。它主要的目的是使得自定义事件处理程序在UI线程上执行,以避免线程上的卡顿或UI更新问题。
|
||
private void RaisedStatus(EnumTcpIpServer returnType, string msg)
|
||
{
|
||
try
|
||
{
|
||
if (OnRaisedStatus != null)
|
||
{
|
||
if (OnRaisedStatus.Target is Control)
|
||
{
|
||
Control targetForm = OnRaisedStatus.Target as Control;
|
||
targetForm.BeginInvoke(OnRaisedStatus, returnType, msg);
|
||
}
|
||
else
|
||
{
|
||
OnRaisedStatus(returnType, msg);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception)
|
||
{
|
||
}
|
||
}
|
||
|
||
public event EventHandlerRaisedMessage OnRaisedMessage;
|
||
|
||
private void RaisedMessage(string clientIp, string msg)
|
||
{
|
||
try
|
||
{
|
||
if (OnRaisedMessage != null)
|
||
{
|
||
if (OnRaisedMessage.Target is Control)
|
||
{
|
||
Control targetForm = OnRaisedMessage.Target as Control;
|
||
targetForm.BeginInvoke(OnRaisedMessage, clientIp, msg);
|
||
}
|
||
else
|
||
{
|
||
OnRaisedMessage(clientIp, msg);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception)
|
||
{
|
||
}
|
||
}
|
||
|
||
#region 增加时钟,判断是否有断掉的连接。
|
||
|
||
private readonly Timer _serverTimer = new Timer();
|
||
|
||
private void ServerTimerLoop(object sender, EventArgs e)
|
||
{
|
||
_serverTimer.Interval = 20000; //监听timer的间隔
|
||
if (_dictSocket != null)
|
||
{
|
||
if (_dictSocket.Count != 0)
|
||
{
|
||
_dictSocket.Values.ToArray();
|
||
for (int i = _dictSocket.Values.ToArray().Length - 1; i >= 0; i--)
|
||
{
|
||
if (_dictSocket.Values.ToArray()[i]
|
||
.Poll(100, SelectMode.SelectRead)) //10毫秒,检查套接字状态, SelectMode 参数指定要监视的套接字的类别。
|
||
{
|
||
// DictSocket.Remove(DictSocket.Keys.ToArray()[i]);
|
||
|
||
RaisedStatus(EnumTcpIpServer.ConnectException,
|
||
"连接已断开:" + _dictSocket.Keys.ToArray()[i]); //刷新界面显示,触发一个连接异常状态枚举,并将消息传递为异常的状态
|
||
RemoveSocketClient(_dictSocket.Keys.ToArray()[i]);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion 增加时钟,判断是否有断掉的连接。
|
||
}
|
||
} |