营销型网站单页,网页设计与制作教学计划,最近网站改版文章突然不收录,微信小程序开发需要什么技能服务端客户端通信 在与服务端的连接建立以后#xff0c;我们就可以通过此连接来发送和接收数据。端口与端口之间以流#xff08;Stream#xff09;的形式传输数据#xff0c;因为几乎任何对象都可以保存到流中#xff0c;所以实际上可以在客户端与服务端之间传输任何类型的…服务端客户端通信 在与服务端的连接建立以后我们就可以通过此连接来发送和接收数据。端口与端口之间以流Stream的形式传输数据因为几乎任何对象都可以保存到流中所以实际上可以在客户端与服务端之间传输任何类型的数据。对客户端来说往流中写入数据即为向服务器传送数据从流中读取数据即为从服务端接收数据。对服务端来说往流中写入数据即为向客户端发送数据从流中读取数据即为从客户端接收数据。 同步传输字符串 我们现在考虑这样一个任务客户端打印一串字符串然后发往服务端服务端先输出它然后将它改为大写再回发到客户端客户端接收到以后最后再次打印一遍它。我们将它分为两部分1、客户端发送服务端接收并输出2、服务端回发客户端接收并输出。 1.客户端发送服务端接收并输出 1.1服务端程序 我们可以在TcpClient上调用GetStream()方法来获得连接到远程计算机的流。注意这里我用了远程这个词当在客户端调用时它得到连接服务端的流当在服务端调用时它获得连接客户端的流。接下来我们来看一下代码我们先看服务端注意这里没有使用do/while循环 class Server { static void Main(string[] args) { const int BufferSize 8192; // 缓存大小8192字节 Console.WriteLine(Server is running ... ); IPAddress ip new IPAddress(new byte[] { 127, 0, 0, 1 }); TcpListener listener new TcpListener(ip, 8500); listener.Start(); // 开始侦听 Console.WriteLine(Start Listening ...); // 获取一个连接中断方法 TcpClient remoteClient listener.AcceptTcpClient(); // 打印连接到的客户端信息 Console.WriteLine(Client Connected{0} -- {1}, remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint); // 获得流并写入buffer中 NetworkStream streamToClient remoteClient.GetStream(); byte[] buffer new byte[BufferSize]; int bytesRead streamToClient.Read(buffer, 0, BufferSize); Console.WriteLine(Reading data, {0} bytes ..., bytesRead); // 获得请求的字符串 string msg Encoding.Unicode.GetString(buffer, 0, bytesRead); Console.WriteLine(Received: {0}, msg); // 按Q退出 } } 这段程序的上半部分已经很熟悉了我就不再解释。remoteClient.GetStream()方法获取到了连接至客户端的流然后从流中读出数据并保存在了buffer缓存中随后使用Encoding.Unicode.GetString()方法从缓存中获取到了实际的字符串。最后将字符串打印在了控制台上。这段代码有个地方需要注意在能够读取的字符串的总字节数大于BufferSize的时候会出现字符串截断现象因为缓存中的数目总是有限的而对于大对象比如说图片或者其它文件来说则必须采用“分次读取然后转存”这种方式比如这样 // 获取字符串byte[] buffer new byte[BufferSize];int bytesRead; // 读取的字节数MemoryStream msStream new MemoryStream();do { bytesRead streamToClient.Read(buffer, 0, BufferSize); msStream.Write(buffer, 0, bytesRead); } while (bytesRead 0); buffer msStream.GetBuffer();string msg Encoding.Unicode.GetString(buffer); 这里我没有使用这种方法一个是因为不想关注在太多的细节上面一个是因为对于字符串来说8192字节已经很多了我们通常不会传递这么多的文本。当使用Unicode编码时8192字节可以保存4096个汉字和英文字符。使用不同的编码方式占用的字节数有很大的差异在本文最后面有一段小程序可以用来测试Unicode、UTF8、ASCII三种常用编码方式对字符串编码时占用的字节数大小。 现在对客户端不做任何修改然后运行先运行服务端再运行客户端。结果我们会发现这样一件事服务端再打印完“Client Connected127.0.0.1:8500 -- 127.0.0.1:xxxxx”之后再次被阻塞了而没有输出“Reading data, {0} bytes ...”。可见与AcceptTcpClient()方法类似这个Read()方法也是同步的只有当客户端发送数据的时候服务端才会读取数据、运行此方法否则它便会一直等待。 1.2 客户端程序 接下来我们编写客户端向服务器发送字符串的代码与服务端类似它先获取连接服务器端的流将字符串保存到buffer缓存中再将缓存写入流写入流这一过程相当于将消息发往服务端。 class Client { static void Main(string[] args) { Console.WriteLine(Client Running ...); TcpClient client; try { client new TcpClient(); client.Connect(localhost, 8500); // 与服务器连接 } catch (Exception ex) { Console.WriteLine(ex.Message); return; } // 打印连接到的服务端信息 Console.WriteLine(Server Connected{0} -- {1}, client.Client.LocalEndPoint, client.Client.RemoteEndPoint); string msg \Welcome To TraceFact.Net\; NetworkStream streamToServer client.GetStream(); byte[] buffer Encoding.Unicode.GetBytes(msg); // 获得缓存 streamToServer.Write(buffer, 0, buffer.Length); // 发往服务器 Console.WriteLine(Sent: {0}, msg); // 按Q退出 } } 现在再次运行程序得到的输出为 // 服务端 Server is running ... Start Listening ... Client Connected127.0.0.1:8500 -- 127.0.0.1:7847 Reading data, 52 bytes ... Received: Welcome To TraceFact.Net 输入Q键退出。 // 客户端 Client Running ... Server Connected127.0.0.1:7847 -- 127.0.0.1:8500 Sent: Welcome To TraceFact.Net 输入Q键退出。 再继续进行之前我们假设客户端可以发送多条消息而服务端要不断的接收来自客户端发送的消息但是上面的代码只能接收客户端发来的一条消息因为它已经输出了“输入Q键退出”说明程序已经执行完毕无法再进行任何动作。此时如果我们再开启一个客户端那么出现的情况是客户端可以与服务器建立连接也就是netstat-a显示为ESTABLISHED这是操作系统所知道的但是由于服务端的程序已经执行到了最后一步只能输入Q键退出无法再采取任何的动作。 回想一个上面我们需要一个服务器对应多个客户端时对AcceptTcpClient()方法的处理办法将它放在了do/while循环中类似地当我们需要一个服务端对同一个客户端的多次请求服务时可以将Read()方法放入到do/while循环中。 现在我们大致可以得出这样几个结论 如果不使用do/while循环服务端只有一个listener.AcceptTcpClient()方法和一个TcpClient.GetStream().Read()方法则服务端只能处理到同一客户端的一条请求。如果使用一个do/while循环并将listener.AcceptTcpClient()方法和TcpClient.GetStream().Read()方法都放在这个循环以内那么服务端将可以处理多个客户端的一条请求。如果使用一个do/while循环并将listener.AcceptTcpClient()方法放在循环之外将TcpClient.GetStream().Read()方法放在循环以内那么服务端可以处理一个客户端的多条请求。如果使用两个do/while循环对它们进行分别嵌套那么结果是什么呢结果并不是可以处理多个客户端的多条请求。因为里层的do/while循环总是在为一个客户端服务因为它会中断在TcpClient.GetStream().Read()方法的位置而无法执行完毕。即使可以通过某种方式让里层循环退出比如客户端往服务端发去“exit”字符串时服务端也只能挨个对客户端提供服务。如果服务端想执行多个客户端的多个请求那么服务端就需要采用多线程。主线程也就是执行外层do/while循环的线程在收到一个TcpClient之后必须将里层的do/while循环交给新线程去执行然后主线程快速地重新回到listener.AcceptTcpClient()的位置以响应其它的客户端。 对于第四种情况实际上是构建一个服务端更为通常的情况所以需要专门开辟一个章节讨论这里暂且放过。而我们上面所做的即是列出的第一种情况接下来我们再分别看一下第二种和第三种情况。 对于第二种情况我们按照上面的叙述先对服务端进行一下改动 do { // 获取一个连接中断方法 TcpClient remoteClient listener.AcceptTcpClient(); // 打印连接到的客户端信息 Console.WriteLine(Client Connected{0} -- {1}, remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint); // 获得流并写入buffer中 NetworkStream streamToClient remoteClient.GetStream(); byte[] buffer new byte[BufferSize]; int bytesRead streamToClient.Read(buffer, 0, BufferSize); Console.WriteLine(Reading data, {0} bytes ..., bytesRead); // 获得请求的字符串 string msg Encoding.Unicode.GetString(buffer, 0, bytesRead); Console.WriteLine(Received: {0}, msg); } while (true); 然后启动多个客户端在服务端应该可以看到下面的输出客户端没有变化 Server is running ... Start Listening ... Client Connected127.0.0.1:8500 -- 127.0.0.1:8196 Reading data, 52 bytes ... Received: Welcome To TraceFact.Net Client Connected127.0.0.1:8500 -- 127.0.0.1:8199 Reading data, 52 bytes ... Received: Welcome To TraceFact.Net 由第2种情况改为第3种情况只需要将do向下挪动几行就可以了 // 获取一个连接中断方法TcpClient remoteClient listener.AcceptTcpClient();// 打印连接到的客户端信息Console.WriteLine(Client Connected{0} -- {1}, remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);// 获得流并写入buffer中NetworkStream streamToClient remoteClient.GetStream();do { byte[] buffer new byte[BufferSize]; int bytesRead streamToClient.Read(buffer, 0, BufferSize); Console.WriteLine(Reading data, {0} bytes ..., bytesRead); // 获得请求的字符串 string msg Encoding.Unicode.GetString(buffer, 0, bytesRead); Console.WriteLine(Received: {0}, msg); } while (true); 然后我们再改动一下客户端让它发送多个请求。当我们按下S的时候可以输入一行字符串然后将这行字符串发送到服务端当我们输入X的时候则退出循环 NetworkStream streamToServer client.GetStream();ConsoleKey key;Console.WriteLine(Menu: S - Send, X - Exit);do { key Console.ReadKey(true).Key; if (key ConsoleKey.S) { // 获取输入的字符串 Console.Write(Input the message: ); string msg Console.ReadLine(); byte[] buffer Encoding.Unicode.GetBytes(msg); // 获得缓存 streamToServer.Write(buffer, 0, buffer.Length); // 发往服务器 Console.WriteLine(Sent: {0}, msg); } } while (key ! ConsoleKey.X); 接下来我们先运行服务端然后再运行客户端输入一些字符串来进行测试应该能够看到下面的输出结果 // 服务端 Server is running ... Start Listening ... Client Connected127.0.0.1:8500 -- 127.0.0.1:11004 Reading data, 44 bytes ... Received: 欢迎访问我的博客TraceFact.NetReading data, 14 bytes ... Received: 我们一起进步 //客户端 Client Running ... Server Connected127.0.0.1:11004 -- 127.0.0.1:8500 Menu: S - Send, X - Exit Input the message: 欢迎访问我的博客TraceFact.Net Sent: 欢迎访问我的博客TraceFact.Net Input the message: 我们一起进步 Sent: 我们一起进步 这里还需要注意一点当客户端在TcpClient实例上调用Close()方法或者在流上调用Dispose()方法服务端的streamToClient.Read()方法会持续地返回0但是不抛出异常所以会产生一个无限循环而如果直接关闭掉客户端或者客户端执行完毕但没有调用stream.Dispose()或者TcpClient.Close()如果服务器端此时仍阻塞在Read()方法处则会在服务器端抛出异常“远程主机强制关闭了一个现有连接”。因此我们将服务端的streamToClient.Read()方法需要写在一个try/catch中。同理如果在服务端已经连接到客户端之后服务端调用remoteClient.Close()则客户端会得到异常“无法将数据写入传输连接: 您的主机中的软件放弃了一个已建立的连接。”而如果服务端直接关闭程序的话则客户端会得到异常“无法将数据写入传输连接: 远程主机强迫关闭了一个现有的连接。”。因此它们的读写操作必须都放入到try/catch块中。 2.服务端回发客户端接收并输出 2.2服务端程序 我们接着再进行进一步处理服务端将收到的字符串改为大写然后回发客户端接收后打印。此时它们的角色和上面完全进行了一下对调对于服务端来说就好像刚才的客户端一样将字符串写入到流中而客户端则同服务端一样接收并打印。除此以外我们最好对流的读写操作加上lock现在我们直接看代码首先看服务端 class Server { static void Main(string[] args) { const int BufferSize 8192; // 缓存大小8192Bytes ConsoleKey key; Console.WriteLine(Server is running ... ); IPAddress ip new IPAddress(new byte[] { 127, 0, 0, 1 }); TcpListener listener new TcpListener(ip, 8500); listener.Start(); // 开始侦听 Console.WriteLine(Start Listening ...); // 获取一个连接同步方法在此处中断 TcpClient remoteClient listener.AcceptTcpClient(); // 打印连接到的客户端信息 Console.WriteLine(Client Connected{0} -- {1}, remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint); // 获得流 NetworkStream streamToClient remoteClient.GetStream(); do { // 写入buffer中 byte[] buffer new byte[BufferSize]; int bytesRead; try { lock(streamToClient){ bytesRead streamToClient.Read(buffer, 0, BufferSize); } if (bytesRead 0) throw new Exception(读取到0字节); Console.WriteLine(Reading data, {0} bytes ..., bytesRead); // 获得请求的字符串 string msg Encoding.Unicode.GetString(buffer, 0, bytesRead); Console.WriteLine(Received: {0}, msg); // 转换成大写并发送 msg msg.ToUpper(); buffer Encoding.Unicode.GetBytes(msg); lock(streamToClient){ streamToClient.Write(buffer, 0, buffer.Length); } Console.WriteLine(Sent: {0}, msg); } catch (Exception ex) { Console.WriteLine(ex.Message); break; } } while (true); streamToClient.Dispose(); remoteClient.Close(); Console.WriteLine(\n\n输入\Q\键退出。); do { key Console.ReadKey(true).Key; } while (key ! ConsoleKey.Q); } } 接下来是客户端 class Client { static void Main(string[] args) { Console.WriteLine(Client Running ...); TcpClient client; ConsoleKey key; const int BufferSize 8192; try { client new TcpClient(); client.Connect(localhost, 8500); // 与服务器连接 } catch (Exception ex) { Console.WriteLine(ex.Message); return; } // 打印连接到的服务端信息 Console.WriteLine(Server Connected{0} -- {1}, client.Client.LocalEndPoint, client.Client.RemoteEndPoint); NetworkStream streamToServer client.GetStream(); Console.WriteLine(Menu: S - Send, X - Exit); do { key Console.ReadKey(true).Key; if (key ConsoleKey.S) { // 获取输入的字符串 Console.Write(Input the message: ); string msg Console.ReadLine(); byte[] buffer Encoding.Unicode.GetBytes(msg); // 获得缓存 try { lock(streamToServer){ streamToServer.Write(buffer, 0, buffer.Length); // 发往服务器 } Console.WriteLine(Sent: {0}, msg); int bytesRead; buffer new byte[BufferSize]; lock(streamToServer){ bytesRead streamToServer.Read(buffer, 0, BufferSize); } msg Encoding.Unicode.GetString(buffer, 0, bytesRead); Console.WriteLine(Received: {0}, msg); } catch (Exception ex) { Console.WriteLine(ex.Message); break; } } } while (key ! ConsoleKey.X); streamToServer.Dispose(); client.Close(); Console.WriteLine(\n\n输入\Q\键退出。); do { key Console.ReadKey(true).Key; } while (key ! ConsoleKey.Q); } } 最后我们运行程序然后输入一串英文字符串然后看一下输出 // 客户端 Client is running ... Server Connected127.0.0.1:12662 -- 127.0.0.1:8500 Menu: S - Send, X - Exit Input the message: Hello, Im jimmy zhang. Sent: Hello, Im jimmy zhang. Received: HELLO, IM JIMMY ZHANG. // 服务端 Server is running ... Start Listening ... Client Connected127.0.0.1:8500 -- 127.0.0.1:12662 Reading data, 46 bytes ... Received: Hello, Im jimmy zhang. Sent: HELLO, IM JIMMY ZHANG. 看到这里我想你应该对使用TcpClient和TcpListener进行C#网络编程有了一个初步的认识可以说是刚刚入门了后面的路还很长。本章的所有操作都是同步操作像上面的代码也只是作为一个入门的范例实际当中一个服务端只能为一个客户端提供服务的情况是不存在的下面就让我们来看看上面所说的第四种情况如何进行异步的服务端编程。 附录ASCII、UTF8、Uncicode编码下的中英文字符大小 private static void ShowCode() { string[] strArray { b, abcd, 乙, 甲乙丙丁 }; byte[] buffer; string mode, back; foreach (string str in strArray) { for (int i 0; i 2; i) { if (i 0) { buffer Encoding.ASCII.GetBytes(str); back Encoding.ASCII.GetString(buffer, 0, buffer.Length); mode ASCII; } else if (i 1) { buffer Encoding.UTF8.GetBytes(str); back Encoding.UTF8.GetString(buffer, 0, buffer.Length); mode UTF8; } else { buffer Encoding.Unicode.GetBytes(str); back Encoding.Unicode.GetString(buffer, 0, buffer.Length); mode Unicode; } Console.WriteLine(Mode: {0}, String: {1}, Buffer.Length: {2}, mode, str, buffer.Length); Console.WriteLine(Buffer:); for (int j 0; j buffer.Length - 1; j) { Console.Write(buffer[j] ); } Console.WriteLine(\nRetrived: {0}\n, back); } } } 输出为 Mode: ASCII, String: b, Buffer.Length: 1 Buffer: 98 Retrived: b Mode: UTF8, String: b, Buffer.Length: 1 Buffer: 98 Retrived: b Mode: Unicode, String: b, Buffer.Length: 2 Buffer: 98 0 Retrived: b Mode: ASCII, String: abcd, Buffer.Length: 4 Buffer: 97 98 99 100 Retrived: abcd Mode: UTF8, String: abcd, Buffer.Length: 4 Buffer: 97 98 99 100 Retrived: abcd Mode: Unicode, String: abcd, Buffer.Length: 8 Buffer: 97 0 98 0 99 0 100 0 Retrived: abcd Mode: ASCII, String: 乙, Buffer.Length: 1 Buffer: 63 Retrived: ? Mode: UTF8, String: 乙, Buffer.Length: 3 Buffer: 228 185 153 Retrived: 乙 Mode: Unicode, String: 乙, Buffer.Length: 2 Buffer: 89 78 Retrived: 乙 Mode: ASCII, String: 甲乙丙丁, Buffer.Length: 4 Buffer: 63 63 63 63 Retrived: ???? Mode: UTF8, String: 甲乙丙丁, Buffer.Length: 12 Buffer: 231 148 178 228 185 153 228 184 153 228 184 129 Retrived: 甲乙丙丁 Mode: Unicode, String: 甲乙丙丁, Buffer.Length: 8 Buffer: 50 117 89 78 25 78 1 78 Retrived: 甲乙丙丁 大体上可以得出这么几个结论 ASCII不能保存中文(貌似谁都知道_-)。UTF8是变长编码。在对ASCII字符编码时UTF更省空间只占1个字节与ASCII编码方式和长度相同Unicode在对ASCII字符编码时占用2个字节且第2个字节补零。UTF8在对中文编码时需要占用3个字节Unicode对中文编码则只需要2个字节。 转载于:https://www.cnblogs.com/starspace/archive/2009/01/19/1378552.html