本例中,JT-8290A读写器(其资料在百度云分享中CSharp>捷通开发包目录下)连接到本地路由器之后,可访问读写器的ip地址进行简单配置(类似路由器第一次设置),主要是将其“服务器地址”参数设置为实验的电脑ip地址即可(本机是服务器,读写器是客户,客户可以有多个。服务器和客户必须在同一个子网才可以通信)。
所以Socket通信代码在(http://my.oschina.net/SnifferApache/blog/406563)基础上修改~~
官方文档中有如下描述:
如果应用程序在执行期间只需要一个线程,请使用下面的方法,这些方法适用于同步操作模式。
如果当前使用的是面向连接的协议(如 TCP),则服务器可以使用 Listen 方法侦听连接。 Accept 方法处理任何传入的连接请求,并返回可用于与远程主机进行数据通信的 Socket。 可以使用此返回的 Socket 来调用 Send 或 Receive 方法。 如果要指定本地 IP 地址和端口号,请在调用 Listen 方法之前先调用Bind 方法。 如果您希望基础服务提供程序为您分配可用端口,请使用端口号 0。 如果希望连接到侦听主机,请调用 Connect 方法。 若要进行数据通信,请调用 Send 或 Receive 方法。
如果当前使用的是无连接协议(如 UDP),则根本不需要侦听连接。 调用 ReceiveFrom 方法可接受任何传入的数据报。 使用 SendTo 方法可将数据报发送到远程主机。
在本例中Socket通信流程如下:
①服务器端新建Socket实例server_socket(同时绑定指定的IPEndPoint,将server_socket置于侦听状态);
②主程序启动线程server_thread处理任何传入的连接请求,一旦成功(比如连接到客户R),其返回的Socket实例connSocket便可以看作服务器与客户R之间的桥梁。
③客户R要做的事情比较简单,因为只有一个服务器(不用关心RFID读写器做了什么)。那服务器怎么区分这么多的客户呢?接收和发送可能搞混?
那我们就为每一个客户单独抛出一个线程来接收消息,发送的时候选择指定的客户connSocket就好啦。
比如客户R的ip地址为key,该connSocket为value,添加到Dictionary<string,Socket>实例中,那就可以方便的操作啦。
废话不说,上源码……
1、添加namespace
C#中Socket编程需要如下名称空间
using System.Net;
using System.Net.Sockets;
using System.Threading;
2、源码
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;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
namespace Ex02_wifiServer
{
public partial class MainForm : Form
{
private static int server_port = 8899;
private static string server_ip = "192.168.3.68";
private static int buffer_size = 1024;
private static string data = null;
private static byte[] receiveBytes = new byte[buffer_size];
Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
Dictionary<string, Thread> dicThread = new Dictionary<string, Thread>();
//添加指令列表
Dictionary<string, string> cmds = new Dictionary<string, string>();
public MainForm()
{
InitializeComponent();
this.MaximumSize = this.Size;
this.MinimumSize = this.Size;
Control.CheckForIllegalCrossThreadCalls = false;
label1.Text = "在线读写器数:" + Convert.ToString(cb_readerList.Items.Count);
cb_readerList.Text = "所有读写器";
data = "等待用户连接……";
richTextBox1.AppendText(data);
richTextBox1.Focus();
}
private void MainForm_Load(object sender, EventArgs e)
{
//为cb_cmd添加指令
cmds.Add("设备识别", "A0 03 82 00 DB");
cmds.Add("复位读头", "A0 03 65 00 F8");
cmds.Add("停止工作", "A0 03 50 00 0D");
cmds.Add("关闭继电器", "A0 04 B1 00 00 AB");
cmds.Add("打开继电器", "A0 04 B1 00 01 AB");
foreach (var i in cmds)
{
this.cb_cmd.Items.Add(i.Key);
}
//定义线程开始
Socket server_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipadd = IPAddress.Parse(server_ip);
IPEndPoint ipe = new IPEndPoint(ipadd, server_port);
try
{
server_socket.Bind(ipe);
server_socket.Listen(100);
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
return;
}
Thread a = new Thread(Run);
a.IsBackground = true;
a.Start(server_socket);
}
int count = 0;
private void Run(object o)
{
Socket socket = o as Socket;
while (count < 100)
{
try
{
count++;
Debug.WriteLine("客户连接的次数:" + count);
//创建一个负责通信用的socket 阻塞窗体的运行
Socket connSocket = socket.Accept();
string s = connSocket.RemoteEndPoint.ToString();
data = "\n" + s + "已连接!";
richTextBox1.AppendText(data);
richTextBox1.Focus();
//ShowMsg(s + ":连接成功");
//记录通信用的socket
dicSocket.Add(s, connSocket);
cb_readerList.Items.Add(s);
label1.Text = "在线读写器数:" + Convert.ToString(cb_readerList.Items.Count);
//接收消息
Thread th = new Thread(RecMsg);
th.IsBackground = true;
th.Start(connSocket);
dicThread.Add(s, th);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
break;
}
}
}
void RecMsg(object o)
{
Socket connSocket = o as Socket;
while (true)
{
try
{
//接收客户端发送过来的消息
byte[] buffer = new byte[1024 * 1024];
//num 接收到的实际有效的字节个数
int num = connSocket.Receive(buffer);
byte[] buff = new byte[num];
string str = "";
for (int i = 0; i < num; i++)
{
buff[i] = buffer[i];
}
foreach (byte item in buff) //读取Buff中存的数据,转换成显 示的十六进制数
{
str += item.ToString("X2") + " ";
}
//string s = Encoding.Default.GetString(buffer, 0, num);
if (str != "!!!I want to close!!!")
{
data = "\n" + connSocket.RemoteEndPoint.ToString() + "返回:" + str;
richTextBox1.AppendText(data);
richTextBox1.Focus();
}
else
{
string m = connSocket.RemoteEndPoint.ToString();
MessageBox.Show(m + "已断开");
dicSocket.Remove(str);
cb_readerList.Items.Remove(m);
cb_readerList.Text = "所有读写器";
label1.Text = "在线读写器数:" + Convert.ToString(cb_readerList.Items.Count);
}
}
catch
{
//MessageBox.Show(ex.Message);
connSocket.Close();
break;
}
}
}
//重写关闭窗体程序
protected override void OnClosing(CancelEventArgs e)
{
Environment.Exit(0);
e.Cancel = true;
}
private void btn_send_Click(object sender, EventArgs e)
{
try
{
string s = cb_readerList.Text;
Debug.WriteLine("指令:" + cb_cmd.Text);
byte[] buff = new byte[20];
string[] str = tb_cmd.Text.Split(' ');
//将byte数组转化为16进制
int k = 0;
foreach (string item in str)
{
if (item.Trim() != "")
{
buff[k++] = byte.Parse(item, System.Globalization.NumberStyles.HexNumber);
}
}
Debug.WriteLine("buff的长度:" + k);
//byte[] sendBytes = Encoding.Default.GetBytes(sendStr);
if (s != "所有读写器")
{
dicSocket[s].Send(buff, buff.Length, SocketFlags.None);//向客户端发送信息
data = "\n" + "服务器对" + s + "发送:" + tb_cmd.Text + " " + buff[k].ToString("X2");
Debug.WriteLine("data: " + data);
richTextBox1.AppendText(data);
richTextBox1.Focus();
}
else
if (cb_readerList.Items.Count == 0)
MessageBox.Show("没有读写器连接到!");
else
{
foreach (string i in cb_readerList.Items)
dicSocket[cb_readerList.GetItemText(i)].Send(buff, k, 0);
data = "\n" + "服务器对所有读写器发送:" + tb_cmd.Text;
richTextBox1.AppendText(data);
richTextBox1.Focus();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void cb_cmd_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
this.tb_cmd.Text = cmds[this.cb_cmd.SelectedItem.ToString()];
}
catch (Exception ex)
{
Debug.WriteLine("下拉列表异常:" + ex);
}
}
}
}
3、源码的说明
①绑定下拉列表和TextBox也是使用Dictionary<T1,T2>泛型类;
②Server线程运行Run()方法到Accept()处会等待客户请求,新用户连接之后及时更新用户下拉列表;
③用户看到的和数据库存储的是16进制数字,每个字节两个16进制数字,字节之间空格隔开,发送之前要转成byte数组,byte数组中每个元素有8bit,刚好容纳一个字节,也就是2个16进制数字。
通信协议采用奇偶校验,目前用到的指令最后一个字节就是校验值,不需要再计算校验结果。所以没有实现“添加校验”按钮的功能。
运行结果
4、下一步的工作
②利用Timer实时发送指令,并将软件运行状态实施写入log.txt中
③添加右下角系统托盘及菜单,利用 Limited Edition for Visual Studio发布应用程序