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界面,修改很方便,若再编写一个客户端,只是实现修改参数功能,意义不大。

posted @ 2017-04-25 15:33  念苏苏0504  阅读(845)  评论(0编辑  收藏  举报