c# 网络编程之tcp

 更新时间:2021年02月19日 10:46:56   作者:seabluescn  
这篇文章主要介绍了c# 网络编程之tcp的的相关资料,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下

一、概述

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#实现窗体与子线程的交互的方法

    C#实现窗体与子线程的交互的方法

    这篇文章主要介绍了C#实现窗体与子线程的交互的方法,对初学者有一定的学习借鉴价值,需要的朋友可以参考下
    2014-08-08
  • Unity实现桌面反弹的示例代码

    Unity实现桌面反弹的示例代码

    反弹球是小时候都玩过的网页小游戏,但是很多人都不知道怎样实现,本文就来介绍一下Unity实现桌面反弹的示例代码,感兴趣的可以了解一下
    2021-05-05
  • C#实现读取被进程占用的文件实现方法

    C#实现读取被进程占用的文件实现方法

    这篇文章主要介绍了C#实现读取被进程占用的文件实现方法,涉及C#进程操作及文件读取的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-08-08
  • C# 元组和值元组的具体使用

    C# 元组和值元组的具体使用

    这篇文章主要介绍了C# 元组和值元组的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • c#之获取本机主机名的四种方式总结

    c#之获取本机主机名的四种方式总结

    这篇文章主要介绍了c#之获取本机主机名的四种方式总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • C#实现txt定位指定行完整实例

    C#实现txt定位指定行完整实例

    这篇文章主要介绍了C#实现txt定位指定行的方法,涉及C#针对文本文件进行光标定位的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-08-08
  • C#实现上位机与欧姆龙PLC通讯(FINS)

    C#实现上位机与欧姆龙PLC通讯(FINS)

    这篇文章主要介绍了C#实现上位机与欧姆龙PLC通讯(FINS)方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • C#使用WMI实现监听进程的启动和关闭

    C#使用WMI实现监听进程的启动和关闭

    Windows Management Instrumentation(WMI)是用于管理基于 Windows 操作系统的数据和操作的基础结构,本文将使用WMI实现监听进程的启动和关闭,感兴趣的可以了解下
    2024-01-01
  • C# Lambda 知识回顾

    C# Lambda 知识回顾

    本文主要介绍了C#中Lambda的相关知识。具有一定的参考价值,下面跟着小编一起来看下吧
    2017-01-01
  • C# Winform 调用系统接口操作 INI 配置文件的代码

    C# Winform 调用系统接口操作 INI 配置文件的代码

    封装了一小段代码, 调用系统接口, 操作配置文件. 一般用于 .ini 文件, 或者其它键值对格式的配置文件
    2011-05-05

最新评论