UDP和TCP是网络通讯常用的两个传输协议,C#一般可以通过Socket来实现UDP和TCP通讯,由于.NET框架通过UdpClient、TcpListener 、TcpClient这几个类对Socket进行了封装,使其使用更加方便, 本文就通过这几个封装过的类讲解一下相关应用。
服务端建立侦听并等待连接:
TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 9000); tcpListener.Start(); if (tcpListener.Pending()) { TcpClient client = tcpListener.AcceptTcpClient(); Console.WriteLine("Connected"); }
服务端是通过AcceptTcpClient方法获得TcpClient对象,而客户端是直接创建TcpClient对象。
TcpClient tcpClient = new TcpClient(); tcpClient.Connect("127.0.0.1", 9000);
发送数据TcpClient对象创建后,发送接收都通过TcpClient对象完成。
发送数据:
TcpClient tcpClient = new TcpClient(); tcpClient.Connect("127.0.0.1", 9000); NetworkStream netStream = tcpClient.GetStream(); int Len = 1024; byte[] datas = new byte[Len]; netStream.Write(datas, 0, Len); netStream.Close(); tcpClient.Close();
接收数据:
TcpClient client = tcpListener.AcceptTcpClient(); Console.WriteLine("Connected"); NetworkStream stream = client.GetStream(); var remote = client.Client.RemoteEndPoint; byte[] data = new byte[1024]; while (true) { if (stream.DataAvailable) { int len = stream.Read(data, 0, 1024); Console.WriteLine($"From:{remote}:Received ({len})"); } Thread.Sleep(1); }
和UDP不太一样,TCP连接不会丢包,但存在粘包问题。(严格来说粘包这个说法是不严谨的,因为TCP通讯是基于流的,没有包的概念,包只是使用者自己的理解。) 下面分析一下粘包产生的原因及解决办法。
TCP数据通讯是基于流来实现的,类似一个队列,当有数据发送过来时,操作系统就会把发送过来的数据依次放到这个队列中,对发送者而言,数据是一片一片发送的,所以自然会认为存在数据包的概念,但对于接收者而言,如果没有及时去取这些数据,这些数据依次存放在队列中,彼此之间并无明显间隔,自然就粘包了。
还有一种情况粘包是发送端造成的,有时我们调用发送代码时,操作系统可能并不会立即发送,而是放到缓存区,当缓存区达到一定数量时才真正发送。
要解决粘包问题,大致有以下几个方案。
1、 约定数据长度,发送端的数据都是指定长度,比如1024;接收端取数据时也取同样长度,不够长度就等待,保证取到的数据和发送端一致;
2、 接收端取数据的频率远大于发送端,比如发送端每1秒发送一段数据,接收端每0.1秒去取一次数据,这样基本可以保证数据不会粘起来;
以上两个方案都要求发送端需要立即发送,不可缓存数据。而且这两种方案都有缺陷:首先,第一种方案:如果要包大小一致的话,如果约定的包比较大,肯定有较多数据冗余,浪费网络资源,如果包较小,连接就比较频繁,效率不高。
其次,第二种方案:这个方案只能在理想环境下可以实现,当服务端遭遇一段时间的计算压力时可能会出现意外,不能完全保证。
比较完善的解决方案就是对接收到的数据进行预处理:首先通过定义特殊的字符组合作为包头和包尾,如果传输ASCII字符,可以用0x02表示开始(STX),用0x03表示结束(ETX),比如:STX ‘H' ‘e' ‘l' ‘l' ‘o' ETX (二进制数据: 02 48 65 6C 6C 6F 03)。如果数据较长可以在包头留出固定位置存放包长度, 如:
02 00 05 48 65 6C 6C 6F 03
其中02 05 就表示正文长度为5个字节,可以进行校验。
虽然第三种方案比较严谨,但相对复杂,在传输比较可靠、应用比较简单的场景下,也可以采用前面两种解决方案。
服务端:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace TCPServer { class Program { static void Main(string[] args) { TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 9000); tcpListener.Start(); while (true) { if (tcpListener.Pending()) { TcpClient client = tcpListener.AcceptTcpClient(); Console.WriteLine("Connected"); Task.Run(() => { NetworkStream stream = client.GetStream(); var remote = client.Client.RemoteEndPoint; while (true) { if (stream.DataAvailable) { byte[] data = new byte[1024]; int len = stream.Read(data, 0, 1024); string Name = Encoding.UTF8.GetString(data,0,len); var senddata = Encoding.UTF8.GetBytes("Hello:" + Name); stream.Write(senddata, 0, senddata.Length); } if (!client.IsOnline()) { Console.WriteLine("Connect Closed."); break; } Thread.Sleep(1); } }); } Thread.Sleep(1); } } } public static class TcpClientEx { public static bool IsOnline(this TcpClient client) { return !((client.Client.Poll(15000, SelectMode.SelectRead) && (client.Client.Available == 0)) || !client.Client.Connected); } } }
客户端:
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace TCP_Clent { class Program { static void Main(string[] args) { ThreadPool.SetMinThreads(100, 100); ThreadPool.SetMaxThreads(200, 200); Parallel.For(1, 10, x => { SendData("Tom"); }); Console.WriteLine("All Completed!"); Console.ReadKey(); } private static void SendData(string Name) { Task.Run(() => { Console.WriteLine("Start"); TcpClient tcpClient = new TcpClient(); tcpClient.Connect("127.0.0.1", 9000); Console.WriteLine("Connected"); NetworkStream netStream = tcpClient.GetStream(); Task.Run(() => { Thread.Sleep(100); while (true) { if (!tcpClient.Client.Connected) { break; } if (netStream == null) { break; } try { if (netStream.DataAvailable) { byte[] data = new byte[1024]; int len = netStream.Read(data, 0, 1024); var message = Encoding.UTF8.GetString(data, 0, len); Console.WriteLine(message); } } catch { break; } Thread.Sleep(10); } }); for (int i = 0; i < 100; i++) { byte[] datas = Encoding.UTF8.GetBytes(Name); int Len = datas.Length; netStream.Write(datas, 0, Len); Thread.Sleep(1000); } netStream.Close(); netStream = null; tcpClient.Close(); Console.WriteLine("Completed"); }); } } }
C#网络编程入门系列包括三篇文章:
(一)C#网络编程入门之UDP
(二)C#网络编程入门之TCP
(三)C#网络编程入门之HTTP
以上就是c# 网络编程之tcp的详细内容,更多关于c# 网络编程tcp的资料请关注小牛知识库其它相关文章!
本文向大家介绍c# 网络编程之http,包括了c# 网络编程之http的使用技巧和注意事项,需要的朋友参考一下 一、概述 本文目的是通过C#代码提供一个HTTP服务,正常情况下如果我们需要向外界提供HTTP服务,常规做法就是通过ASP.NET来实现,有时我们的应用程序或Windows服务需要向外提供一些简单的HTTP服务就可以自己实现,从而避免部署IIS增加系统复杂性。这里必须强调是一些简单的应用
本文向大家介绍C#网络编程基础之进程和线程详解,包括了C#网络编程基础之进程和线程详解的使用技巧和注意事项,需要的朋友参考一下 在C#的网络编程中,进程和线程是必备的基础知识,同时也是一个重点,所以我们要好好的掌握一下。 一:概念 首先我们要知道什么是”进程”,什么是“线程”,好,查一下baike。 进程:是一个具有一定独立功能的程序关于某个数据集合的一次活动。它是操作系统动态执行的基本单元, 在
1. 网络编程概述 自从互联网诞生以来,现在基本上所有的程序都是网络程序,很少有单机版的程序了。 计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程就是如何在程序中实现两台计算机的通信。 举个例子,当你使用浏览器访问新浪网时,你的计算机就和新浪的某台服务器通过互联网连接起来了,然后,新浪的服务器把网页内容作为数据通过互联网传输到你的电脑上。 由于你的电脑上可能不止浏览器,
Socket Linux Socket 编程(不限 Linux) Socket 中的 read()、write() 函数 ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); read() read 函数是负责从 fd 中读取内容。 当读成功时,rea
网络基础 Socket I/O 模型的演进
Unix/Linux网络编程常用的头文件有: arpa/inet.h netinet/in.h sys/socket.h netdb.h hostent(结构体)表示主机 servent(结构体)表示服务数据库的登记项信息