c# 网络编程之tcp

作者:seabluescn 时间:2022-07-24 03:27:27 

一、概述

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

来源:https://www.cnblogs.com/seabluescn/p/12972632.html

标签:c#,网络编程,tcp
0
投稿

猜你喜欢

  • Android打开GPS导航并获取位置信息返回null解决方案

    2021-08-31 09:21:19
  • Java中使用fileupload组件实现文件上传功能的实例代码

    2021-11-17 09:20:33
  • Java使用 try-with-resources 实现自动关闭资源的方法

    2022-01-09 06:54:46
  • Java String、StringBuffer与StringBuilder的区别

    2022-08-29 23:29:55
  • Android数据类型之间相互转换系统介绍

    2023-10-15 22:36:39
  • C#操作txt文件,进行清空添加操作的小例子

    2023-05-24 14:06:07
  • springboot 多数据源的实现(最简单的整合方式)

    2022-08-20 02:30:11
  • 用c#实现简易的计算器功能实例代码

    2022-05-09 19:28:51
  • 一起来了解Java的File类和IO流

    2022-04-12 09:23:15
  • C#获取CPU编号的方法

    2022-01-05 10:57:37
  • C#操作注册表的方法

    2022-04-14 03:20:36
  • 简单了解JavaBean作用及常用操作

    2023-09-08 19:59:29
  • WPF弹出自定义窗口的方法

    2023-10-18 20:52:04
  • C#生成防伪码的思路及源码分享

    2022-10-27 03:53:41
  • Java List的remove()方法踩坑

    2021-05-27 05:17:58
  • 浅谈java常用的几种线程池比较

    2021-07-24 01:28:23
  • Java JVM内存区域详解

    2021-05-25 18:44:43
  • C#实现猜数字游戏

    2021-11-16 07:29:58
  • Mybatis打印替换占位符后的完整Sql教程

    2023-11-08 22:56:13
  • Android中的Intent Filter匹配规则简介

    2021-07-29 16:02:30
  • asp之家 软件编程 m.aspxhome.com