本例中,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>实例中,那就可以方便的操作啦。
废话不说,上源码……
C#中Socket编程需要如下名称空间
using System.Net;
using System.Net.Sockets;
using System.Threading;
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); } } } }
①绑定下拉列表和TextBox也是使用Dictionary<T1,T2>泛型类;
②Server线程运行Run()方法到Accept()处会等待客户请求,新用户连接之后及时更新用户下拉列表;
③用户看到的和数据库存储的是16进制数字,每个字节两个16进制数字,字节之间空格隔开,发送之前要转成byte数组,byte数组中每个元素有8bit,刚好容纳一个字节,也就是2个16进制数字。
通信协议采用奇偶校验,目前用到的指令最后一个字节就是校验值,不需要再计算校验结果。所以没有实现“添加校验”按钮的功能。
①将识别到的内容添加到数据库中
②利用Timer实时发送指令,并将软件运行状态实施写入log.txt中
③添加右下角系统托盘及菜单,利用 Limited Edition for Visual Studio发布应用程序