C#做爲服務器端 MATLAB做爲客戶端 兩者之間進行通訊

    以前的socket通訊,一直是在visual studio的C#平臺進行的,服務器端是簡單的增量式PID控制器(這個算法,後續應該會再深刻學習,而後將算法豐富),但是如今,老師讓實現這樣一個功能,在另外一臺計算機上有一搭建好的電機模型,要實時的將電機的速度拿到,做爲PID控制器的輸入,實時速度與設定值比較,獲得的誤差做爲PID控制器的輸入,PID控制器計算後的輸出值再給電機的輸入端,從而經過這樣的閉環控制,來控制電機(涉及到一個D/A轉換,這個如今還沒設計。但猜測是否是能夠直接經過一個零階保持器來實現)。 算法

    電機的實時速度,須要從simulink模塊中提取出來,傳給.m文件,從而經過.m文件與C#之間的通訊,實現PID控制功能。數組

    C#服務器端代碼改變不大,爲了方便,直接拿以前作的winform過來,在那基礎上改寫的,有的代碼沒用到,也沒刪掉,核心是Receive( )函數。服務器

 

C#服務器端winform代碼:socket

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文件代碼:tcp

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界面,修改很方便,若再編寫一個客戶端,只是實現修改參數功能,意義不大。學習

相關文章
相關標籤/搜索