业务背景
最近因为业务的需求,需要实现串口的透传(简单的TCP/UDP透传),并且显示TCP/UDP实时刷新和显示连接情况或者监听情况。非常简单的功能,但是会存在一些雷点,我会讲述成功实现的做法还有我踩过的坑。
效果如下两张图。
思路
1.使用Sql Server 创建一个数据库并且分离,并且放入程序目录下,并且连接路径改为目录下。
2.建立Linq to sql类。
3.创建串口时,将数据全部插入数据库。(连接状态文字栏根据选用的网络协议进行改变)
4.插入完成后,将数据填充显示。
5.TCP/UDP监听状态下,有客户端连入,则改变连接状态,断开也更新状态。
代码
1,2,3,4步想必大家都会,就不再赘述,关键是第5步。 我们需要在监听部分进行捕捉到客户端的连接,并且及时更新数据库。
首先,我们要获取到数据库的那一条要更新的记录才能做更新操作。作为服务端,其端口是唯一的,所以我们获取到它的端口:
((IPEndPoint)serverSocket.LocalEndPoint).Port.ToString();
serverSocket
:是服务端用来监听的载体。
LocalEndPoint
:可以获取当前服务器的各种信息。但是不可读取。
(IPEndPoint)
:强制转换成IPEndPoint类型,就调用Port方法,拿到了LocalEndPoint
中我们实际使用的端口号了。
之后写一个方法,用拿到的端口号去数据库中查询,并且更新:
private void ReturnendPoint(Socket socket)//根据端口号返回数据库中的对象
{
vspdTableDataContext db = new vspdTableDataContext();//实例化
var res = (from r in db.PortSettingTable
where r.localportnum == ((IPEndPoint)socket.LocalEndPoint).Port.ToString().Trim()//查找端口号是否一致
select r).FirstOrDefault();
res.connetstate = "已经连接";
db.SubmitChanges();
}
复制代码
但是这样的话,还达不到要求,因为只是改成了 “已经连接”,但是没有具体显示连接人数。 所以我们这里新建一个全局变量来存储每个端口号对应的人数,我这里用了字典:
Dictionary<string,int>OnlineNums=new Dictionary<string,int>();
在服务端开启监听的时候,我们就加入客户端的端口号和在线人数(刚刚开始监听,肯定是0): OnlineNums.Add(PortTxt, 0);
然后再改写一下我们上面的方法,我们希望把每个端口对应的在线人数同时传输进去进行更新,像这样:
ReturnendPoint(serverSocket, OnlineNums[port]);
而具体方法就是如下:
private void ReturnendPoint(Socket socket,int index)//根据端口号返回数据库中的对象
{
vspdTableDataContext db = new vspdTableDataContext();//实例化
var res = (from r in db.PortSettingTable
where r.localportnum == ((IPEndPoint)socket.LocalEndPoint).Port.ToString().Trim()//查找端口号是否一致
select r).FirstOrDefault();
if(index>0)
res.connetstate = $"已经连接({index})";
else//如果小于或等于0证明没人了
res.connetstate = "正在监听";
db.SubmitChanges();
}
复制代码
然后我们在监听方法里边调用:
public void AcceptClientConnet(object socket)//接收客户端连接的方法
{
var serverSocket = socket as Socket;//强制转换
string port = ((IPEndPoint)serverSocket.LocalEndPoint).Port.ToString();//获得端口号
while (true)
{
var ProxSocket = serverSocket.Accept();//用户连接
OnlineNums[port]++;//人数+1
ReturnendPoint(serverSocket, pairs[port]);//更新数据库
DataBind();//重新填充,刷新datagirdview
ThreadPool.QueueUserWorkItem(ReceiveData, ProxSocket);//为了反复连接,使用线程池
}
}
复制代码
在客户端断线的时候也是如法炮制:
public void ReceiveData(object socket)//接收客户端消息的方法
{
var ProxSocket = socket as Socket;
byte[] data = new byte[1024 * 1024];//接收消息的缓冲区
string port = ((IPEndPoint)ProxSocket.LocalEndPoint).Port.ToString();
while (true)
{
int len = 0;//记录消息长度
try
{
len = ProxSocket.Receive(data, 0, data.Length, SocketFlags.None);
}
catch (Exception)
{
//异常退出
OnlineNums[port]--;//人数-1
ReturnendPoint(ProxSocket, OnlineNums[port]);//重新填充,刷新datagirdview
DataBind();
return;
}
if (len <= 0)
{
//如果小于0,证明无连接,服务端正常退出
OnlineNums[port]--;//人数-1
ReturnendPoint(ProxSocket, OnlineNums[port]);//重新填充,刷新datagirdview
DataBind();
ps.StopConnet(ProxSocket);
return;//让方法结束,终结当前接收服务端数据的异步线程
}
}
}
}
复制代码
这样的写法有点累赘,可以写成下面的形式:
ReturnendPoint(ProxSocket, OnlineNums[++port]);
ReturnendPoint(ProxSocket, OnlineNums[--port]);
这样就已经实现效果了。
雷点
- 如果用了Linq to Sql去填充DataGridView,就不要试图去直接改写单元格中的Value,这样没用。虽然会能赋值到目标单元格上的Value上,但是不会显示出来。因为数据会被你原本的DataSource覆盖。
- Linq to Sql的DataContext不要做全局变量的实例化。如果你填充数据集的方法和更新数据集的方法是在一个类里面的话,共用一个实例会出各种各样的错误。应该在独立方法体内实例化。但如果两个方法不在一个类中,随便。(天呐,简直是弱智级别的错误啊啊啊啊啊)
- 记得给DataGridView做跨线程处理。