将之前的通信代码,以winform界面的形式写出来
winform应用程序,与控制台程序的代码没有太大的区别,最大的不同是将代码拆分成一个个小块,即一个个的方法,这样可以通过一个个的控件(按钮button等)去触发事件(方法)。 但是我感觉,不好的是,winform程序容易因为点击按钮的顺序,次数的不同,使程序出错,所以要多多的用try-catch。
客户端代码
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using System.Net; 10 using System.Net.Sockets; 11 using System.Threading; 12 13 namespace WindowsForms_客户端 14 { 15 public partial class Form1 : Form 16 { 17 18 byte[] data; 19 string stringData; 20 //创建服务器端IPEndPoint对象 21 IPEndPoint Ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 125); 22 Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 23 24 public Form1() 25 { 26 InitializeComponent(); 27 } 28 29 /// <summary> 30 /// 与服务器建立连接 31 /// </summary> 32 /// <param name="sender"></param> 33 /// <param name="e"></param> 34 private void buttonConnect_Click(object sender, EventArgs e) 35 { 36 try 37 { 38 server.Connect(Ipep); 39 textState.AppendText("连接成功\n"); 40 } 41 catch 42 { 43 textState.AppendText("连接服务器失败\n"); 44 MessageBox.Show("请先启动服务器,再启动客户端"); 45 return; 46 } 47 Thread th = new Thread(Receive); 48 th.IsBackground = true; 49 th.Start(); 50 } 51 52 /// <summary> 53 /// 断开与服务器的连接 54 /// </summary> 55 /// <param name="sender"></param> 56 /// <param name="e"></param> 57 private void buttonBreak_Click(object sender, EventArgs e) 58 { 59 server.Shutdown(SocketShutdown.Both); 60 server.Close(); 61 textState.AppendText("断开与服务器的连接\n"); 62 } 63 64 65 66 /// <summary> 67 /// 接收消息的方法 用的方法是ReceiveVarMessage 接收变长消息 68 /// </summary> 69 void Receive() 70 { 71 while (true) 72 { 73 data = ReceiveVarMessage(server); 74 if (data.Length == 0) 75 { 76 break; 77 } 78 stringData = Encoding.UTF8.GetString(data, 0, data.Length); 79 textReceive.AppendText(stringData + "\n"); 80 } 81 82 83 //while (true) 84 //{ 85 // //接收数据 86 // int receive = server.Receive(data);//receive是接收到的数据的长度 所以是int型 接收到的信息,存在data这个byte字节数组中 87 // if (receive == 0) 88 // { 89 // break; 90 // } 91 // stringData = Encoding.UTF8.GetString(data, 0, receive); 92 // textReceive.AppendText(stringData + "\n"); 93 //} 94 } 95 96 /// <summary> 97 /// 跨线程访问 98 /// </summary> 99 /// <param name="sender"></param> 100 /// <param name="e"></param> 101 private void Form1_Load(object sender, EventArgs e) 102 { 103 Control.CheckForIllegalCrossThreadCalls = false; 104 textKp.Text = "0.4"; 105 textKi.Text = "0.53"; 106 textKd.Text = "0.1"; 107 textKd.Focus(); 108 109 } 110 111 /// <summary> 112 /// 发送消息到服务器端 113 /// </summary> 114 /// <param name="sender"></param> 115 /// <param name="e"></param> 116 private void buttonSend_Click(object sender, EventArgs e) 117 { 118 string str = textSend.Text; 119 data = System.Text.Encoding.UTF8.GetBytes(str); 120 SendVarMessage(server, data); 121 } 122 123 private void textReceive_TextChanged(object sender, EventArgs e) 124 { 125 126 } 127 128 int i = 0; 129 /// <summary> 130 /// 客户端发送设定速度值 给服务器端 131 /// </summary> 132 /// <param name="sender"></param> 133 /// <param name="e"></param> 134 private void button1_Click(object sender, EventArgs e) 135 { 136 string speed = textSetspeed.Text; 137 try 138 { 139 double dSpeed = Convert.ToDouble(speed); 140 data = System.Text.Encoding.UTF8.GetBytes(speed); 141 textSend.AppendText("第"+(i+1)+"次发送的设定速度值是"+speed+"\n"); 142 SendVarMessage(server, data); 143 //server.Send(data); 144 i++; 145 textSetspeed.Clear(); 146 textSetspeed.Focus(); 147 } 148 catch 149 { 150 MessageBox.Show("输入速度设定值不正确,请重新输入"); 151 textSend.AppendText(speed + "\n"); 152 textSetspeed.Clear(); 153 textSetspeed.Focus(); 154 } 155 } 156 157 /// <summary> 158 /// 发送变长消息方法 159 /// </summary> 160 /// <param name="s"></param> 161 /// <param name="msg"></param> 162 /// <returns></returns> 163 private static void SendVarMessage(Socket s, byte[] msg) 164 { 165 int offset = 0; 166 int sent; 167 int size = msg.Length; 168 int dataleft = size; 169 byte[] msgsize = new byte[2]; 170 171 //将消息的尺寸从整型转换成可以发送的字节型 172 //因为int型是占4个字节 所以msgsize是4个字节 后边是空字节 173 msgsize = BitConverter.GetBytes(size); 174 175 //发送消息的长度信息 176 //之前总是乱码出错 客户端接收到的欢迎消息前两个字节是空 后边的两个字符er传送到第二次接收的字节数组中 177 //因此将er字符转换为int出错 这是因为之前在Send代码中,是将msgsize整个字节数组发送给客户端 所以导致第3 4个空格也发送 178 //导致发送的信息混乱 这两个空格使发送的信息都往后挪了两个位置 从而乱码 179 sent = s.Send(msgsize, 0, 2, SocketFlags.None); 180 while (dataleft > 0) 181 { 182 int sent2 = s.Send(msg, offset, dataleft, SocketFlags.None); 183 //设置偏移量 184 offset += sent2; 185 dataleft -= sent2; 186 } 187 } 188 189 /// <summary> 190 /// 接收变长消息方法 191 /// </summary> 192 /// <param name="s"></param> 193 /// <returns>接收到的信息</returns> 194 private static byte[] ReceiveVarMessage(Socket s)//方法的返回值是字节数组 byte[] 存放的是接受到的信息 195 { 196 int offset = 0; 197 int recv; 198 byte[] msgsize = new byte[2]; 199 200 //接收2个字节大小的长度信息 201 recv = s.Receive(msgsize, 0, 2, 0); 202 203 //将字节数组的消息长度转换为整型 204 int size = BitConverter.ToInt16(msgsize, 0); 205 int dataleft = size; 206 byte[] msg = new byte[size]; 207 while (dataleft > 0) 208 { 209 //接收数据 210 recv = s.Receive(msg, offset, dataleft, 0); 211 if (recv == 0) 212 { 213 break; 214 } 215 offset += recv; 216 dataleft -= recv; 217 } 218 return msg; 219 } 220 221 ///// <summary> 222 ///// 将kp参数的设置,发送给客户端 这里还没有客户端在线修改kp ki kd参数的功能 223 ///// </summary> 224 ///// <param name="sender"></param> 225 ///// <param name="e"></param> 226 private void buttonChange_Click(object sender, EventArgs e) 227 { 228 // data = System.Text.Encoding.UTF8.GetBytes(textKp.Text); 229 // SendVarMessage(server, data); 230 231 // //data = System.Text.Encoding.UTF8.GetBytes(textKi.Text); 232 // //SendVarMessage(server, data); 233 234 // //data = System.Text.Encoding.UTF8.GetBytes(textKd.Text); 235 // //SendVarMessage(server, data); 236 } 237 238 ///// <summary> 239 ///// 修改ki参数 240 ///// </summary> 241 ///// <param name="sender"></param> 242 ///// <param name="e"></param> 243 private void buttonChangeki_Click(object sender, EventArgs e) 244 { 245 // data = System.Text.Encoding.UTF8.GetBytes(textKi.Text); 246 // SendVarMessage(server, data); 247 } 248 249 ///// <summary> 250 ///// 修改kd参数 251 ///// </summary> 252 ///// <param name="sender"></param> 253 ///// <param name="e"></param> 254 private void buttonChangekd_Click(object sender, EventArgs e) 255 { 256 // data = System.Text.Encoding.UTF8.GetBytes(textKd.Text); 257 // SendVarMessage(server, data); 258 } 259 260 261 } 262 }
服务器端代码:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using System.Net; 10 using System.Net.Sockets; 11 using System.Threading; 12 using System.Collections; 13 14 namespace WindowsForms_服务器端 15 { 16 public partial class Form1 : Form 17 { 18 public Form1() 19 { 20 InitializeComponent(); 21 } 22 double[] nums = new double[3];//存储3个偏差值e(k) e(k-1) e(k-2) 23 double actual = 0; 24 double set = 0; 25 double kp; 26 double ki; 27 double kd; 28 //定义一个空字节数组date作为数据缓冲区,用于缓冲流入和流出的信息 29 byte[] date; 30 static Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 31 static Socket client; 32 ArrayList list = new ArrayList();//存储客户端发来的速度设定值 33 ArrayList listOut = new ArrayList();//存储PID计算的实际速度值 34 Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();//创建键值对,存储套接字的ip地址等信息 35 36 Thread thConnect; 37 38 /// <summary> 39 /// 建立与客户端的连接 40 /// </summary> 41 /// <param name="sender"></param> 42 /// <param name="e"></param> 43 private void buttonConnect_Click(object sender, EventArgs e) 44 { 45 try 46 { 47 IPEndPoint Ipep = new IPEndPoint(IPAddress.Any, 125);//指定地址和端口号 48 newsock.Bind(Ipep); 49 newsock.Listen(10);//置于监听状态 50 textState.Text = "等待客户端的连接\n"; 51 thConnect = new Thread(Connect); 52 thConnect.IsBackground = true; 53 thConnect.Start(); 54 //Receive(); 55 } 56 catch 57 { } 58 } 59 60 /// <summary> 61 /// 不断接收从客户端发来消息的方法 62 /// </summary> 63 void Receive() 64 { 65 try 66 { 67 while (true) 68 { 69 date = ReceiveVarMessage(client); 70 if (date.Length == 0) 71 { 72 break; 73 } 74 string str = Encoding.UTF8.GetString(date, 0, date.Length); 75 list.Add(str); 76 textReceive.AppendText(str + "\n"); 77 } 78 } 79 catch 80 { } 81 } 82 83 /// <summary> 84 /// 服务器端与客户端连接的方法 使一个服务器可以与多个客户端连接 85 /// </summary> 86 private void Connect() 87 { 88 //接收来自客户端的接入尝试连接,并返回连接客户端的ip地址 89 while (true) 90 { 91 client = newsock.Accept(); 92 IPEndPoint clientep = (IPEndPoint)client.RemoteEndPoint; 93 //返回客户端的ip地址和端口号 94 textState.AppendText("与" + clientep.Address + "在" + clientep.Port + "端口连接\n"); 95 dicSocket.Add(clientep.ToString(), client);//将连接的客户端的IP地址添加在键值对集合中 96 comboBox.Items.Add(clientep);//将客户端的IP地址显示在下拉栏中 97 //Receive(); 98 Thread thReceive = new Thread(Receive);//创建一个新线程 执行Receive()方法 99 thReceive.IsBackground = true; 100 thReceive.Start(); 101 } 102 } 103 104 /// <summary> 105 /// 发送消息给客户端 需自己选择已连接的其中一个客户端 106 /// </summary> 107 /// <param name="sender"></param> 108 /// <param name="e"></param> 109 private void buttonSend_Click(object sender, EventArgs e) 110 { 111 try 112 { 113 string str = textSend.Text; 114 date = System.Text.Encoding.UTF8.GetBytes(str); //以字节数组的形式 将欢迎信息发送给客户端 注意发送与接收要用相同的编码格式UTF8 否则会乱码 115 try 116 { 117 string ip = comboBox.SelectedItem.ToString(); 118 SendVarMessage(dicSocket[ip], date); 119 } 120 catch 121 { 122 MessageBox.Show("发送失败,请确认已选择一个客户端"); 123 } 124 } 125 catch 126 { } 127 } 128 129 130 /// <summary> 131 /// 断开与客户端的连接 132 /// </summary> 133 /// <param name="sender"></param> 134 /// <param name="e"></param> 135 private void buttonBreak_Click(object sender, EventArgs e) 136 { 137 try 138 { 139 client.Close(); 140 newsock.Close(); 141 textState.AppendText("断开连接\n"); 142 } 143 catch { } 144 } 145 146 /// <summary> 147 /// 跨线程访问 148 /// </summary> 149 /// <param name="sender"></param> 150 /// <param name="e"></param> 151 private void Form1_Load(object sender, EventArgs e) 152 { 153 Control.CheckForIllegalCrossThreadCalls = false; 154 } 155 156 private void textState_TextChanged(object sender, EventArgs e) 157 { 158 159 } 160 161 private void textReceive_TextChanged(object sender, EventArgs e) 162 { 163 164 } 165 166 /// <summary> 167 /// 开始进行PID计算 168 /// </summary> 169 /// <param name="sender"></param> 170 /// <param name="e"></param> 171 private void button1_Click(object sender, EventArgs e) 172 { 173 //Thread thKp = new Thread(GetKp); 174 //thKp.IsBackground = true; 175 //thKp.Start(); 176 ////date=ReceiveVarMessage(client); 177 ////string strKp = Encoding.UTF8.GetString(date,0,date.Length); 178 ////kp = Convert.ToDouble(strKp); 179 180 //Thread thKi = new Thread(GetKi); 181 //thKi.IsBackground = true; 182 //thKi.Start(); 183 ////date = ReceiveVarMessage(client); 184 ////string strKi = Encoding.UTF8.GetString(date, 0, date.Length); 185 ////ki = Convert.ToDouble(strKi); 186 187 //Thread thKd = new Thread(GetKd); 188 //thKd.IsBackground = true; 189 //thKd.Start(); 190 ////date = ReceiveVarMessage(client); 191 ////string strKd = Encoding.UTF8.GetString(date); 192 ////kd = Convert.ToDouble(strKd); 193 194 int n = list.Count; 195 int i = 0; 196 while (i < n) 197 { 198 set = double.Parse(list[i].ToString()); 199 nums[0] = set - actual; 200 double increase = 0.4* (nums[0] - nums[1]) + 0.53 * nums[0] + 0.1 * (nums[0] - 2 * nums[1] + nums[2]); 201 actual += increase; 202 listOut.Add(actual);//将每次的实际速度值 添加到集合中 203 textActualspeed.AppendText("第" + (i + 1) + "次的实际输出速度是" + actual + "\n"); //输出实际的速度值 成功! 204 nums[1] = nums[0]; 205 nums[2] = nums[1]; 206 i++; 207 } 208 } 209 210 ///// <summary> 211 ///// 得到客户端传来的Kp参数 212 ///// </summary> 213 //private void GetKp() 214 //{ 215 // date = ReceiveVarMessage(client); 216 // string strKp = Encoding.UTF8.GetString(date, 0, date.Length); 217 // kp = Convert.ToDouble(strKp); 218 //} 219 220 ///// <summary> 221 ///// 得到客户端传来的Ki参数 222 ///// </summary> 223 //private void GetKi() 224 //{ 225 // date = ReceiveVarMessage(client); 226 // string strKi = Encoding.UTF8.GetString(date, 0, date.Length); 227 // ki = Convert.ToDouble(strKi); 228 //} 229 230 ///// <summary> 231 ///// 得到客户端传来的Kd参数 232 ///// </summary> 233 //private void GetKd() 234 //{ 235 // date = ReceiveVarMessage(client); 236 // string strKd = Encoding.UTF8.GetString(date, 0, date.Length); 237 // kd = Convert.ToDouble(strKd); 238 //} 239 240 /// <summary> 241 /// 发送变长消息方法 242 /// </summary> 243 /// <param name="s"></param> 244 /// <param name="msg"></param> 245 /// <returns></returns> 246 private static void SendVarMessage(Socket s, byte[] msg) 247 { 248 int offset = 0; 249 int sent; 250 int size = msg.Length; 251 int dataleft = size; 252 byte[] msgsize = new byte[2]; 253 254 //将消息的尺寸从整型转换成可以发送的字节型 255 //因为int型是占4个字节 所以msgsize是4个字节 后边是空字节 256 msgsize = BitConverter.GetBytes(size); 257 258 //发送消息的长度信息 259 //之前总是乱码出错 客户端接收到的欢迎消息前两个字节是空 后边的两个字符er传送到第二次接收的字节数组中 260 //因此将er字符转换为int出错 这是因为之前在Send代码中,是将msgsize整个字节数组发送给客户端 所以导致第3 4个空格也发送 261 //导致发送的信息混乱 这两个空格使发送的信息都往后挪了两个位置 从而乱码 262 sent = s.Send(msgsize, 0, 2, SocketFlags.None); 263 while (dataleft > 0) 264 { 265 int sent2 = s.Send(msg, offset, dataleft, SocketFlags.None); 266 //设置偏移量 267 offset += sent2; 268 dataleft -= sent2; 269 } 270 //return dataleft; 271 } 272 273 /// <summary> 274 /// 接收变长消息方法 275 /// </summary> 276 /// <param name="s"></param> 277 /// <returns>接收到的信息</returns> 278 private static byte[] ReceiveVarMessage(object o)//方法的返回值是字节数组 byte[] 存放的是接受到的信息 279 { 280 Socket s = o as Socket; 281 int offset = 0; 282 int recv; 283 byte[] msgsize = new byte[2]; 284 285 //接收2个字节大小的长度信息 286 recv = s.Receive(msgsize, 0, 2, 0); 287 288 //将字节数组的消息长度转换为整型 289 int size = BitConverter.ToInt16(msgsize, 0); 290 int dataleft = size; 291 byte[] msg = new byte[size]; 292 while (dataleft > 0) 293 { 294 //接收数据 295 recv = s.Receive(msg, offset, dataleft, 0); 296 if (recv == 0) 297 { 298 break; 299 } 300 offset += recv; 301 dataleft -= recv; 302 } 303 return msg; 304 } 305 } 306 }
这里还没有实现客户端在线修改kp ki kd参数的功能,pid计算用的参数是初始给定的定值。
接收消息Receive部分的代码,是不断的接收对方发送来的消息,不能分辨其发送的是kp ki参数的设定值(double型),还是速度设定值setSpeed(int或double型),还是欢迎消息welcome to the server(string型),所以,应该需要标记符,放在要发送的消息中,如:字节数组第一位是0,表示发送的是速度设定值,第一位为2表示kp参数设定值……