C#作为服务器端 MATLAB作为客户端 二者之间进行通信
之前的socket通信,一直是在visual studio的C#平台进行的,服务器端是简单的增量式PID控制器(这个算法,后续应该会再深入学习,然后将算法丰富),可是现在,老师让实现这样一个功能,在另一台计算机上有一搭建好的电机模型,要实时的将电机的速度拿到,作为PID控制器的输入,实时速度与设定值比较,得到的偏差作为PID控制器的输入,PID控制器计算后的输出值再给电机的输入端,从而通过这样的闭环控制,来控制电机(涉及到一个D/A转换,这个现在还没设计。但猜想是不是可以直接通过一个零阶保持器来实现)。
电机的实时速度,需要从simulink模块中提取出来,传给.m文件,从而通过.m文件与C#之间的通信,实现PID控制功能。
C#服务器端代码改变不大,为了方便,直接拿之前做的winform过来,在那基础上改写的,有的代码没用到,也没删掉,核心是Receive( )函数。
C#服务器端winform代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Net; using System.Net.Sockets; using System.Threading; using System.Collections; using System.IO; namespace WindowsForms_服务器端 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } double[] nums = new double[3];//存储3个偏差值e(k) e(k-1) e(k-2) double actual = 0; double set = 0; double kp = 0.4; double ki = 0.53; double kd = 0.1; //定义一个空字节数组date作为数据缓冲区,用于缓冲流入和流出的信息 byte[] date = new byte[100]; static Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); static Socket client; ArrayList list = new ArrayList();//存储客户端发来的速度设定值 ArrayList listOut = new ArrayList();//存储PID计算的实际速度值 Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();//创建键值对,存储套接字的ip地址等信息 Thread thConnect; /// <summary> /// 建立与客户端的连接 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonConnect_Click(object sender, EventArgs e) { try { IPEndPoint Ipep = new IPEndPoint(IPAddress.Any, 125);//指定地址和端口号 newsock.Bind(Ipep); newsock.Listen(10);//置于监听状态 textState.Text = "等待客户端的连接\n"; thConnect = new Thread(Connect); thConnect.IsBackground = true; thConnect.Start(); } catch { } } int i = 0; /// <summary> /// 不断接收从客户端发来消息的方法 /// </summary> void Receive() { try { while (true) { int reci = client.Receive(date); if (date.Length == 0) { break; } string str = Encoding.ASCII.GetString(date,0,reci); //if(Encoding.ASCII.GetString(date,0,date.Length)=="a")//证明MATLAB端已经写入成功实时速度 //else if (str == "a") { FileStream fs = new FileStream(@"C:\Users\neu\Desktop\test1.txt", FileMode.Open); int count = (int)fs.Length; byte[] readData = new byte[count]; fs.Read(readData, 0, count); string strNew = Encoding.ASCII.GetString(readData); fs.Close();//使用完要关闭文件 否则会一直占用资源 //File.Delete(@"C:\Users\neu\Desktop\test1.txt"); actual = double.Parse(strNew); actual = actual * actual; date = System.Text.Encoding.UTF8.GetBytes(actual.ToString()); //在这里,下一步要将PID控制输出的速度值,传给MATLAB端 即计算一个,输出一个到MATLAB端 textSend.AppendText(actual.ToString() + "\n"); FileStream fsWrite = new FileStream(@"C:\Users\neu\Desktop\test1.txt", FileMode.OpenOrCreate); fsWrite.Write(date, 0, date.Length); fsWrite.Close(); //写入成功后 即PID计算完成后 给MATLAB端发送一信号 告诉它可以往下继续执行了 date = System.Text.Encoding.ASCII.GetBytes("1"); client.Send(date, date.Length, SocketFlags.None); } } } catch { } } /// <summary> /// 服务器端与客户端连接的方法 使一个服务器可以与多个客户端连接 /// </summary> private void Connect() { //接收来自客户端的接入尝试连接,并返回连接客户端的ip地址 while (true) { client = newsock.Accept(); IPEndPoint clientep = (IPEndPoint)client.RemoteEndPoint; //返回客户端的ip地址和端口号 textState.AppendText("与" + clientep.Address + "在" + clientep.Port + "端口连接\n"); dicSocket.Add(clientep.ToString(), client);//将连接的客户端的IP地址添加在键值对集合中 comboBox.Items.Add(clientep);//将客户端的IP地址显示在下拉栏中 comboBox.SelectedIndex = 0; Thread thReceive = new Thread(Receive);//创建一个新线程 执行Receive()方法 thReceive.IsBackground = true; thReceive.Start(); } } /// <summary> /// 发送消息给客户端 需自己选择已连接的其中一个客户端 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonSend_Click(object sender, EventArgs e) { try { string str = textSend.Text; date = System.Text.Encoding.UTF8.GetBytes(str); //以字节数组的形式 将欢迎信息发送给客户端 注意发送与接收要用相同的编码格式UTF8 否则会乱码 try { string ip = comboBox.SelectedItem.ToString(); SendVarMessage(dicSocket[ip], date); } catch { MessageBox.Show("发送失败,请确认已选择一个客户端"); } } catch { } } /// <summary> /// 断开与客户端的连接 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonBreak_Click(object sender, EventArgs e) { try { client.Close(); newsock.Close(); textState.AppendText("断开连接\n"); this.Close(); } catch { } } /// <summary> /// 跨线程访问 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Form1_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; } private void textState_TextChanged(object sender, EventArgs e) { } private void textReceive_TextChanged(object sender, EventArgs e) { } double outSpeed = 0;//PID控制器计算后的输出量,以后要传给模型输入端,以控制模型 /// <summary> /// 开始进行PID计算 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { } /// <summary> /// 发送变长消息方法 /// </summary> /// <param name="s"></param> /// <param name="msg"></param> /// <returns></returns> private static void SendVarMessage(Socket s, byte[] msg) { int offset = 0; int sent; int size = msg.Length; int dataleft = size; byte[] msgsize = new byte[2]; //将消息的尺寸从整型转换成可以发送的字节型 //因为int型是占4个字节 所以msgsize是4个字节 后边是空字节 msgsize = BitConverter.GetBytes(size); //发送消息的长度信息 //之前总是乱码出错 客户端接收到的欢迎消息前两个字节是空 后边的两个字符er传送到第二次接收的字节数组中 //因此将er字符转换为int出错 这是因为之前在Send代码中,是将msgsize整个字节数组发送给客户端 所以导致第3 4个空格也发送 //导致发送的信息混乱 这两个空格使发送的信息都往后挪了两个位置 从而乱码 sent = s.Send(msgsize, 0, 2, SocketFlags.None); while (dataleft > 0) { int sent2 = s.Send(msg, offset, dataleft, SocketFlags.None); //设置偏移量 offset += sent2; dataleft -= sent2; } //return dataleft; } /// <summary> /// 接收变长消息方法 /// </summary> /// <param name="s"></param> /// <returns>接收到的信息</returns> private static byte[] ReceiveVarMessage(object o)//方法的返回值是字节数组 byte[] 存放的是接受到的信息 { Socket s = o as Socket; int offset = 0; int recv; byte[] msgsize = new byte[2]; //接收2个字节大小的长度信息 recv = s.Receive(msgsize, 0, 2, 0); //将字节数组的消息长度转换为整型 int size = BitConverter.ToInt16(msgsize, 0); int dataleft = size; byte[] msg = new byte[size]; while (dataleft > 0) { //接收数据 recv = s.Receive(msg, offset, dataleft, 0); if (recv == 0) { break; } offset += recv; dataleft -= recv; } return msg; } private void textSend_TextChanged(object sender, EventArgs e) { } } }
MATLAB .m文件代码:
s = tcpip('127.0.0.1', 125, 'NetworkRole','client'); set(s, 'InputBufferSize', 30); set(s, 'outputBufferSize', 30); set(s,'Timeout',3); fopen(s); b=1; c=1; d=1; a='1.1';%模拟存储电机的实时速度 fid=fopen('C:\Users\neu\Desktop\test1.txt','wb'); fprintf(fid,'%s',a);%将实时速度写在文本文件中,从而服务器端可通过这个文本文件.txt读取到实时速度 fclose(fid); fwrite(s,'a');%发送一个字符(随意的)给PID控制器,表示可以从TXT中读取传入的实时速度值了 pause(1);%暂停一秒 while(b)%while循环是想 只有读到PID控制器计算完成的信号后,再往下执行代码 否则在原地等待 知道读到信号 read=fread(s,1) if read==49 b=0; end end fid2=fopen('C:\Users\neu\Desktop\test1.txt','r');%读取PID控制器输出的计算值 a=fscanf(fid2,'%f')%fscanf读取文本文件(.txt) a是double类型的数据 fclose(fid2);%关闭文件 即释放占用的资源 a='2.2';%模拟存储电机的实时速度 fid=fopen('C:\Users\neu\Desktop\test1.txt','wb'); fprintf(fid,'%s',a);%将实时速度写在文本文件中,从而服务器端可读取到实时速度 fclose(fid); fwrite(s,'a'); pause(1); while(c) read=fread(s,1) if read==49 c=0; end end fid2=fopen('C:\Users\neu\Desktop\test1.txt','r');%读取PID控制器输出的计算值 a=fscanf(fid2,'%f')%fscanf读取文本文件(.txt) a是double类型的数据 fclose(fid2);%关闭文件 即释放占用的资源 a='3.3';%模拟存储电机的实时速度 fid=fopen('C:\Users\neu\Desktop\test1.txt','wb'); fprintf(fid,'%s',a);%将实时速度写在文本文件中,从而服务器端可读取到实时速度 fclose(fid); fwrite(s,'a'); pause(1); while(d) read=fread(s,1) if read==49 d=0; end end fid2=fopen('C:\Users\neu\Desktop\test1.txt','r');%读取PID控制器输出的计算值 a=fscanf(fid2,'%f')%fscanf读取文本文件(.txt) a是double类型的数据 fclose(fid2);%关闭文件 即释放占用的资源 fclose(s)
因为还没实现从simulink仿真模块中拿到模拟电机的实时速度,所以这里是传递了三个给定的参数给C#服务器端,返回的是传递过去数值的平方,即返回值依次是1.21 ,4.84 ,10.89。 从仿真模块中拿到的实时速度,要先存储在一个变量中,然后再发送给服务器端,但是我用fwrite函数,只知道怎样发送一个数值或字符串,怎样将存储数值的变量发送过去还不知道,所以这里是用将实时速度用fprintf函数写入到文本文件.txt中,服务器端再从这个文本文件读数。
下一步,将不必要的代码删掉,缕清楚程序。程序还存在一个问题是,客户端改为MATLAB后,缺少了在线修改参数的模块,因为MATLAB端不是界面,所以,想把之前放在C#客户端的修改参数模块,移至服务器端,服务器端是winform界面,修改很方便,若再编写一个客户端,只是实现修改参数功能,意义不大。