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