• C#网络编程入门之TCP


    一、概述

    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);
            }
        }
    }
    View Code

    客户端:

    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");
                });
            }       
        }
    }
    View Code

    传送门:

    C#网络编程入门系列包括三篇文章:

    (一)C#网络编程入门之UDP

    (二)C#网络编程入门之TCP

    (三)C#网络编程入门之HTTP

  • 相关阅读:
    Mybatis——逆向工程
    Mybatis——Spring整合
    Mybatis——缓存机制
    Mbatis——动态SQL
    Mybatis_映射文件配置
    Mybatis XML 配置文件
    渣渣小本求职复习之路每天一博客系列——数据结构与常用算法(2)
    渣渣小本求职复习之路每天一博客系列——数据结构与常用算法(1)
    渣渣小本求职复习之路每天一博客系列——数据库基础(MySQL)(5)
    渣渣小本求职复习之路每天一博客系列——数据库基础(MySQL)(4)
  • 原文地址:https://www.cnblogs.com/seabluescn/p/12972632.html
Copyright © 2020-2023  润新知