SuperSocket使用起始标记、结束标记过滤消息
编程语言:C#
框架:.net framework 4.0
IDE:Visual Studio 2013
一、新建window Forms应用程序TestFilterSocket
【忽略设计器自动生成的代码】
新建窗体FormServer,注意绑定窗体的Load事件,按钮的Click事件
新建窗体FormClient,绑定按钮的Click事件。“连接”:btnConnect_Click,“断开”:btnDisconnect_Click,“发送”:btnSend_Click
二、添加supersocket类库引用
添加log4net.dll、SuperSocket.Common.dll、SuperSocket.SocketBase.dll、
SuperSocket.SocketEngine.dll这些引用【可以通过官网supersocket下载】。
添加对System.Configuration类库的引用。
三、添加新建类HansAppServer.cs
填写如下代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
/*
* SuperSocket自定义通讯协议,支持开始标记、结束标记【没有标记按string.Empty处理】
* 2018-11-07
*/
namespace TestFilterSocket
{
///
/// 第一步、定义一个和协议合适的数据对象
/// 该对象由开始标记、结束标记、消息主体等组成
///
public class HansReceiveData
{
///
/// 开始标记 如:"!Start"
///
public string StartMark { get; set; }
///
/// 结束标记 如:"$End"
///
public string EndMark { get; set; }
///
/// 备用字段 暂时用GUID
///
public string Key { get; set; }
///
/// 消息主体 消息数据流【除去开始标记、结束标记】
///
public byte[] BodyBuffer { get; set; }
///
/// 消息主体 消息内容【除去开始标记、结束标记】
///
public string BodyString { get; set; }
}
///
/// 第二步、请求(RequestInfo) RequestInfo 是表示来自客户端请求的实体类。 每个来自客户端的请求都能应该被实例化为 RequestInfo 类型。
///
public class HansRequestInfo : SuperSocket.SocketBase.Protocol.RequestInfo<HansReceiveData>
{
public HansRequestInfo(string key, HansReceiveData myData)
{
//如果需要使用命令行协议的话,那么key与命令类名称myData相同
Initialize(key, myData);
}
}
///
/// 第三步、接收过滤器(ReceiveFilter) 接收过滤器(ReceiveFilter)用于将接收到的二进制数据转化成请求实例(RequestInfo)。
/// 需要实现接口 IReceiveFilter
///
public class HansReceiveFilter : SuperSocket.SocketBase.Protocol.IReceiveFilter<HansRequestInfo>
{
///
/// 该接收过滤器已缓存数据的长度:这里用过滤后的数据长度【BodyBuffer.Length】
///
public int leftBufferSize;
///
/// 字符编码
///
public Encoding Encoder = Encoding.GetEncoding("gbk");
///
/// 开始标记 如:"!Start"
///
public string StartMark = "!Start";
///
/// 结束标记 如:"$End"
///
public string EndMark = "$End";
public HansReceiveFilter(Encoding encoder, string startMark, string endMark)
{
Encoder = encoder;
StartMark = startMark;
EndMark = endMark;
}
///
/// 该方法将会在 SuperSocket 收到一块二进制数据时被执行,接收到的数据在 readBuffer 中从 offset 开始, 长度为 length 的部分。
/// 接收收到的数据流,对数据流进行过滤处理
///
/// 接收缓冲区, 接收到的数据存放在此数组里【缓冲区默认大小:100KB】
/// 接收到的数据在接收缓冲区的起始位置
/// 本轮接收到的数据的长度
/// 表示当你想缓存接收到的数据时,是否需要为接收到的数据重新创建一个备份而不是直接使用接收缓冲区
/// 这是一个输出参数, 它应该被设置为当解析到一个为政的请求后,接收缓冲区还剩余多少数据未被解析
///
public HansRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest)
{
//因接收的数据如果不匹配内容,则舍弃之,因此都为0
rest = 0;
byte[] startMarkBuffer = Encoder.GetBytes(StartMark);
byte[] endMarkBuffer = Encoder.GetBytes(EndMark);
if (length < startMarkBuffer.Length + endMarkBuffer.Length)//没有数据
return null;
byte[] data = new byte[length];
Buffer.BlockCopy(readBuffer, offset, data, 0, length);
string receiveStartMark = Encoder.GetString(data, 0, startMarkBuffer.Length);
string receiveEndMark = Encoder.GetString(data, length - endMarkBuffer.Length, endMarkBuffer.Length);
HansReceiveData receiveData = new HansReceiveData();
receiveData.StartMark = StartMark;
receiveData.Key = Guid.NewGuid().ToString("B");
receiveData.BodyBuffer = new byte[length - startMarkBuffer.Length - endMarkBuffer.Length];
Buffer.BlockCopy(data, startMarkBuffer.Length, receiveData.BodyBuffer, 0, length - startMarkBuffer.Length - endMarkBuffer.Length);
receiveData.EndMark = EndMark;
receiveData.BodyString = Encoder.GetString(receiveData.BodyBuffer);
leftBufferSize = length - startMarkBuffer.Length - endMarkBuffer.Length;
//如果开始标记 或者 结束标记 不匹配设定值,则返回null
if (!receiveStartMark.Equals(StartMark) || !receiveEndMark.Equals(EndMark))
return null;
return new HansRequestInfo(receiveData.Key, receiveData);
}
///
/// 该接收过滤器已缓存数据的长度
///
public int LeftBufferSize
{
get { return leftBufferSize; }
}
///
/// 当下一块数据收到时,用于处理数据的接收过滤器实例;
///
public SuperSocket.SocketBase.Protocol.IReceiveFilter<HansRequestInfo> NextReceiveFilter
{
get { return this; }
}
///
/// 重设接收过滤器实例到初始状态
///
public void Reset()
{
}
public SuperSocket.SocketBase.Protocol.FilterState State
{
get;
private set;
}
}
///
/// 第四步、接收过滤器工厂(ReceiveFilterFactory)
/// 接收过滤器工厂(ReceiveFilterFactory)用于为每个会话创建接收过滤器. 定义一个过滤器工厂(ReceiveFilterFactory)类型, 你必须实现接口 IReceiveFilterFactory. 类型参数 "TRequestInfo" 是你要在整个程序中使用的请求类型
///
public class HansReceiveFilterFactory : SuperSocket.SocketBase.Protocol.IReceiveFilterFactory<HansRequestInfo>
{
///
/// 字符编码
///
public Encoding Encoder = Encoding.GetEncoding("gbk");
///
/// 开始标记 如:"!Start"
///
public string StartMark = "!Start";
///
/// 结束标记 如:"$End"
///
public string EndMark = "$End";
public HansReceiveFilterFactory(Encoding encoder, string startMark, string endMark)
{
Encoder = encoder;
StartMark = startMark;
EndMark = endMark;
}
public SuperSocket.SocketBase.Protocol.IReceiveFilter<HansRequestInfo> CreateFilter(SuperSocket.SocketBase.IAppServer appServer, SuperSocket.SocketBase.IAppSession appSession, System.Net.IPEndPoint remoteEndPoint)
{
return new HansReceiveFilter(Encoder, StartMark, EndMark);
}
}
///
/// 第五步、定义一个用于通信的会话对象【类似于客户端】
///
public class HansAppSession : SuperSocket.SocketBase.AppSession<HansAppSession, HansRequestInfo>
{
public uint DeviceUDID;
protected override void HandleException(Exception e)
{
}
}
///
/// 第六步、定义一个服务对象【类似于服务端】
///
public class HansAppServer : SuperSocket.SocketBase.AppServer<HansAppSession, HansRequestInfo>
{
///
/// 字符编码
///
public static Encoding Encoder = Encoding.GetEncoding("gbk");
///
/// 开始标记 如:"!Start"
/// 如果不需要开始标记,请将 开始标记设置为string.Empty。
///
public static string StartMark = "!Start";
///
/// 结束标记 如:"$End"
/// 如果不需要结束标记,请将 结束标记设置为string.Empty。
///
public static string EndMark = "$End";
///
/// 按照默认的字符编码、开始标记、结束标记实例化服务对象
///
public HansAppServer()
: base(new HansReceiveFilterFactory(Encoder, StartMark, EndMark))
{
}
///
/// 按照设置的字符编码、开始标记、结束标记实例化服务对象
/// 【如果不需要开始标记,请将 开始标记设置为string.Empty。如果不需要结束标记,请将 结束标记设置为string.Empty。】
///
///
///
///
public HansAppServer(Encoding encoder, string startMark, string endMark)
: base(new HansReceiveFilterFactory(encoder, startMark, endMark))
{
HansAppServer.Encoder = encoder;
HansAppServer.StartMark = startMark;
HansAppServer.EndMark = endMark;
}
}
///
/// 测试服务端代码辅助类
///
public class ServerDemoUtil
{
static Encoding encoding = Encoding.GetEncoding("gbk");
HansAppServer myServer = new HansAppServer(encoding, "#", "$");
public void Listen()
{
//Setup the appServer
if (!myServer.Setup(1990)) //Setup with listening port
{
MessageBox.Show("Failed to setup!");
return;
}
//Try to start the appServer
if (!myServer.Start())
{
MessageBox.Show("Failed to start!");
return;
}
myServer.NewSessionConnected += MyServer_NewSessionConnected;
myServer.NewRequestReceived += MyServer_NewRequestReceived;
myServer.SessionClosed += myServer_SessionClosed;
}
private void MyServer_NewRequestReceived(HansAppSession session, HansRequestInfo requestInfo)
{
string msg = requestInfo.Body.BodyString;//encoding.GetString(requestInfo.Body.BodyBuffer);
MessageBox.Show(msg);
}
private void MyServer_NewSessionConnected(HansAppSession session)
{
session.Send("Welcome to SuperSocket Telnet Server");
}
void myServer_SessionClosed(HansAppSession session, SuperSocket.SocketBase.CloseReason value)
{
throw new NotImplementedException();
}
}
}
四、在FormServer.cs中添加如下代码:
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Config;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
using SuperSocket.SocketBase.Protocol;
namespace TestFilterSocket
{
public partial class FormServer : Form
{
SortedList<string, HansAppSession> list = new SortedList<string, HansAppSession>();
HansAppServer appServer;
public FormServer()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
HansAppServer.Encoder = Encoding.GetEncoding("gbk");
HansAppServer.StartMark = txtStartMark.Text;
HansAppServer.EndMark = txtEndMark.Text;
appServer = new HansAppServer();
ServerConfig serverConfig = new ServerConfig();
System.Net.IPHostEntry ipHost = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName());
System.Net.IPAddress[] ipCollection = ipHost.AddressList;
string ip = "";
for (int i = 0; i < ipCollection.Length; i++)
{
if (ipCollection[i].AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
ip = ipCollection[i].ToString();
break;
}
}
//如果注释下一行代码,则默认编码格式为ASCII
serverConfig.TextEncoding = HansAppServer.Encoder.BodyName;// "gbk";//gb2312
serverConfig.Ip = ip;
serverConfig.Port = 2018;
if (!appServer.Setup(serverConfig))
{
MessageBox.Show("开启监听端口失败");
return;
}
if (!appServer.Start())
{
MessageBox.Show("服务器开启监听失败");
return;
}
appServer.NewSessionConnected -= AppServer_NewSessionConnected;
appServer.NewRequestReceived -= AppServer_NewRequestReceived;
appServer.SessionClosed -= AppServer_SessionClosed;
//客户端连接事件
appServer.NewSessionConnected += AppServer_NewSessionConnected;
//接收事件
appServer.NewRequestReceived += AppServer_NewRequestReceived;
//客户端已关闭事件
appServer.SessionClosed += AppServer_SessionClosed;
DisplayContent(string.Format("服务器启动监听成功,服务器IP:{0},端口:{1}\n", appServer.Config.Ip, appServer.Config.Port));
//MessageBox.Show("服务器启动监听成功");
}
private void AppServer_SessionClosed(HansAppSession session, SuperSocket.SocketBase.CloseReason value)
{
//MessageBox.Show("客户端已关闭:" + value);
string sessionIp = string.Format("{0}:{1}", session.RemoteEndPoint.Address, session.RemoteEndPoint.Port);
list.Remove(sessionIp);
this.Invoke(new MethodInvoker(()=>
{
DisplayContent(string.Format("客户端已关闭:{0},端口:{1},原因:{2}\n", session.RemoteEndPoint.Address, session.RemoteEndPoint.Port, value));
cboClientList.Items.Remove(sessionIp);
}));
}
///
/// 接收来自客户端的内容,客户端发送的内容以\r\n作为结束符
///
///
///
private void AppServer_NewRequestReceived(HansAppSession session, HansRequestInfo requestInfo)
{
string body = session.Charset.GetString(requestInfo.Body.BodyBuffer);
string charSet = session.Charset.BodyName;
DateTime dt = session.LastActiveTime;
string key = session.CurrentCommand;
StringBuilder sb = new StringBuilder(key);
if (body.Length > 0)
{
sb.Append(":" + body);
}
sb.Append("\n发送时间:" + dt.ToString("yyyy-MM-dd HH:mm:ss"));
sb.Append(",字符编码:" + charSet);
sb.Append("\n");
DisplayContent(sb.ToString());
//session.Send("abc123你好");
}
///
/// 异步显示内容
///
///
public void DisplayContent(string addContent)
{
if (this.InvokeRequired)
{
Action<string> actionUpd = DisplayContentEx;
actionUpd.BeginInvoke(addContent, null, null);
}
else
{
DisplayContentEx(addContent);
}
}
public void DisplayContentEx(string addContent)
{
this.Invoke(new MethodInvoker(() =>
{
if (rtxtDisplay.TextLength >= 10240)
{
rtxtDisplay.Clear();
}
rtxtDisplay.AppendText(addContent);
rtxtDisplay.ScrollToCaret();
}));
}
private void AppServer_NewSessionConnected(HansAppSession session)
{
string sessionIp = string.Format("{0}:{1}", session.RemoteEndPoint.Address, session.RemoteEndPoint.Port);
list.Add(sessionIp, session);
this.Invoke(new MethodInvoker(() =>
{
DisplayContent(string.Format("客户端已连接:{0},端口:{1}\n", session.RemoteEndPoint.Address, session.RemoteEndPoint.Port));
cboClientList.Items.Add(sessionIp);
}));
session.Send("Welcome");
}
private void btnSend_Click(object sender, EventArgs e)
{
if (cboClientList.SelectedIndex == -1)
{
MessageBox.Show("请选择一个客户端对象");
return;
}
if (rtxtSendMessage.Text.Length == 0)
{
MessageBox.Show("请输入发送内容!", "提示");
rtxtSendMessage.Focus();
return;
}
string sessionIp = cboClientList.Items[cboClientList.SelectedIndex].ToString();
HansAppSession sessionTo = list[sessionIp];
sessionTo.Send(rtxtSendMessage.Text);
}
private void btnClear_Click(object sender, EventArgs e)
{
rtxtDisplay.Clear();
}
}
}
五、在FormClient.cs中填写如下代码:
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.Windows.Forms;
namespace TestFilterSocket
{
public partial class FormClient : Form
{
///
/// 委托接收服务端发送的数据
///
///
public delegate void ReceiveHandle(byte[] receiveBuffer);
///
/// 接收服务端发送的数据事件
///
public event ReceiveHandle ReceiveDataEvent;
///
/// 客户端连接
///
TcpClient tcpClient;
ManualResetEvent manualWait = new ManualResetEvent(false);
///
/// 是否已连接
///
bool isConnect = false;
///
/// 服务端与客户端send、receive所使用的流
///
NetworkStream networkStream;
///
/// 接收线程
///
Thread receiveThread;
///
/// 服务器地址和端口 如 192.168.0.23:3456
///
string serverIpAndPort = string.Empty;
public FormClient()
{
InitializeComponent();
}
private void btnConnect_Click(object sender, EventArgs e)
{
if (isConnect)
{
MessageBox.Show("已连接到服务端");
return;
}
string ipAddress = txtIP.Text;
int port = int.Parse(txtPort.Text);
tcpClient = new TcpClient();
try
{
serverIpAndPort = string.Empty;
bool connectSuccess = ConnectServer(ipAddress, port);
if (connectSuccess)
{
rtxtInfo.AppendText(string.Format("已连接到服务器,服务器IP:{0},端口:{1}\n", ipAddress, port));
//订阅服务端发送过来的数据的事件
ReceiveDataEvent -= FormClient_ReceiveDataEvent;
ReceiveDataEvent += FormClient_ReceiveDataEvent;
receiveThread = new Thread(ReceiveProcess);
receiveThread.IsBackground = true;
receiveThread.Start();
}
else
{
//MessageBox.Show("连接服务端失败");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "连接失败");
}
}
int count = 0;
private void FormClient_ReceiveDataEvent(byte[] buffer)
{
count++;
string recieveContent = Encoding.GetEncoding("gbk").GetString(buffer);
string message = serverIpAndPort + "说 " + recieveContent;
this.Invoke(new MethodInvoker(() =>
{
rtxtInfo.AppendText(message + "\n");
}));
if (count >= 100)
{
count = 0;
}
}
///
/// 异步连接服务端
///
/// 服务端IP地址
/// 服务端端口
///
private bool ConnectServer(string serverIP, int port)
{
isConnect = false;
manualWait.Reset();
tcpClient.BeginConnect(serverIP, port, new AsyncCallback(RequestCallback), tcpClient);
if (manualWait.WaitOne(2000, false))
{
if (isConnect)
{
serverIpAndPort = string.Format("{0}:{1}", serverIP, port);
networkStream = tcpClient.GetStream();
return true;
}
return false;
}
else
{
return false;
//throw new Exception("回调函数未正常返回,请确认超时参数正确");
}
}
///
/// 异步连接回调函数
///
///
private void RequestCallback(IAsyncResult asyncResult)
{
isConnect = false;
try
{
TcpClient client = asyncResult.AsyncState as TcpClient;
if (client != null)
{
client.EndConnect(asyncResult);
isConnect = true;
}
}
catch (Exception ex)
{
isConnect = false;
MessageBox.Show("连接服务器出现异常:" + ex.Message, "尝试连接服务器");
}
finally
{
manualWait.Set();
}
}
private void ReceiveProcess()
{
try
{
while (isConnect)
{
if (networkStream!=null && networkStream.CanRead)
{
//冲刷,强制刷新缓冲区,如果不调用Flush函数,接收的内容将一直停留在缓冲区。Flush函数就是从缓冲区拿到数据
networkStream.Flush();
byte[] buffer = new byte[10240];
//networkStream.ReadTimeout = 10000;
int byteCount = networkStream.Read(buffer, 0, buffer.Length);
if (byteCount > 0)
{
byte[] destBuffer = new byte[byteCount];
Array.Copy(buffer, destBuffer, byteCount);
if (ReceiveDataEvent != null)
{
ReceiveDataEvent(destBuffer);
}
}
}
Thread.Sleep(1);
}
}
catch (Exception ex)
{
isConnect = false;
MessageBox.Show(ex.Message, "读取接收数据出错");
}
}
private void btnDisconnect_Click(object sender, EventArgs e)
{
isConnect = false;
try
{
if (tcpClient != null)
{
tcpClient.Close();
}
if (networkStream != null)
{
networkStream.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "关闭连接出错");
}
}
private void btnSend_Click(object sender, EventArgs e)
{
if (!isConnect)
{
MessageBox.Show("请先连接服务端!", "提示");
return;
}
if (rtxtSendMessage.Text.Length == 0)
{
MessageBox.Show("请输入发送内容!", "提示");
rtxtSendMessage.Focus();
return;
}
//SuperSocket默认发送约束为结尾是: \r\n
byte[] buffer = Encoding.GetEncoding("gbk").GetBytes(rtxtSendMessage.Text);
//Array.Copy(buffer)
try
{
networkStream.WriteTimeout = 100;
if (networkStream.CanWrite)
{
networkStream.Write(buffer, 0, buffer.Length);
}
}
catch (Exception ex)
{
isConnect = false;
MessageBox.Show(ex.Message, "发送数据出错");
}
}
}
}
更改默认的Program.cs,更新代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace TestFilterSocket
{
static class Program
{
///
/// 应用程序的主入口点。
///
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
DialogResult dialog = MessageBox.Show("当前作为服务端选择\"是\",作为客户端选择\"否\"", "提示", MessageBoxButtons.YesNo);
if (dialog == DialogResult.Yes)
{
Application.Run(new FormServer());
}
else
{
Application.Run(new FormClient());
}
}
}
}
六、编译运行后效果如图:
将过滤不是按照#开始 $结束的消息
客户端一:
客户端二: