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#泛型的用法,有助于读者深入理解C#泛型的原理,具体分析如下:首先需要明白什么时候使用泛型:当针对不同的数据类型,采用
- 前言在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似ke
- 方法一:简单的方法就是改变picturebox 控件的borderstyle样式 currentSelectPicBox.Bord
- 最近发现线上监控有个SQL调用量很大,但是方法的调用量不是很大,查看接口实现,发现接口是做了缓存操作的,使用Spring cache缓存注解
- 最近在开发的过程中,一个列表的查询,涉及到了多表的关联查询,由于持久层使用的是mongodb,对这个非关系型数据使用的不是很多,所以在实现此
- Required String parameter xxx is not present类型异常异常报错学习Spring Boot的时候做一
- Swing包的介绍Java基础类数据库(Java Foundation Class)给java应用程序增加了图形界面、丰富的功能性以及与用户
- 1.抽象类与抽象方法:(1)使用关键字abstract修饰的类,称为抽象类.(2)抽象类只是用到一个类所具有的行为,不能单独通过创建对象来使
- 一、背景项目中新建module之后,要在该目录下新增java Class文件,右键——》New发现无Java Class选项。二、办法Fil
- 这篇文章主要介绍了SpringBoot 使用Mybatis分页插件实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参
- 实现效果图:下面是具体代码,可直接复制:package com.lcw.rabbit.widget;import android.anima
- 本文实例展示了DevExpress获取TreeList可视区域节点集合的实现方法,是比较有实用价值的技巧。分享给大家供大家参考。具体实现方法
- 1,通过Handler机制主线程中定义Handler,子线程发消息,通知Handler完成UI更新,Handler对象必须定义在主线程中,如
- 本文实例讲述了Android开发实现模仿微信小窗口功能。分享给大家供大家参考,具体如下:运用方法:将显示窗口的风格 设置为对话框风格即可具体
- 在程序运行中经常需要对数据进行对比显示,其中使用柱状图显示非常直观,可以更显著的比较出数据量的走势。下面介绍在C#中柱状图的制作方法:1、方
- 一、为什么要学习并发编程对于 “我们为什么要学习并发编程?” 这个问题,就好比 “我们为什么要学习政治?” 一样,我们(至少作为学生党是这样
- 在上篇文章给大家介绍了Android Studio 3.6 正式版终于发布了,快来围观,需要的朋友可以点击查看,今天给大家分享我安装Andr
- 伤害数字显示HUD游戏中收到伤害掉血,会有飘动的伤害数值;可以使用OnGUI中GUI.Label来实现;可自定义字体,颜色,大小等;如果需要
- 本文较为详细的讲述了在WCF数据访问中使用缓存提高Winform字段中文显示速度的方法,分享给大家供大家参考之用。具体方法如下:在我们开发基
- 界面开发中,经常使用观察者设计模式来实现文档/视图模式,当文档内容改变时,作为观察者的用户视图必须相应作出调整以向用户呈现文档的状态。由于语