一、概述

UDP和TCP是网络通讯常用的两个传输协议,C#一般可以通过Socket来实现UDP和TCP通讯,由于.NET框架通过UdpClient、TcpListener 、TcpClient这几个类对Socket进行了封装,使其使用更加方便, 本文就通过这几个封装过的类讲解一下相关应用。

二、基本应用:连接、发送、接收

服务端建立侦听并等待连接:

TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), );
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", );

发送数据TcpClient对象创建后,发送接收都通过TcpClient对象完成。

发送数据:

                TcpClient tcpClient = new TcpClient();
tcpClient.Connect("127.0.0.1", );
NetworkStream netStream = tcpClient.GetStream(); int Len = ;
byte[] datas = new byte[Len]; netStream.Write(datas, , 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[];
while (true)
{
if (stream.DataAvailable)
{
int len = stream.Read(data, , );
Console.WriteLine($"From:{remote}:Received ({len})");
}
Thread.Sleep();
}

三、 粘包问题

和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"), );
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[];
int len = stream.Read(data, , );
string Name = Encoding.UTF8.GetString(data,,len);
var senddata = Encoding.UTF8.GetBytes("Hello:" + Name);
stream.Write(senddata, , senddata.Length);
} if (!client.IsOnline())
{
Console.WriteLine("Connect Closed.");
break;
} Thread.Sleep();
}
});
} Thread.Sleep();
}
}
} public static class TcpClientEx
{
public static bool IsOnline(this TcpClient client)
{
return !((client.Client.Poll(, SelectMode.SelectRead) && (client.Client.Available == )) || !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(, );
ThreadPool.SetMaxThreads(, ); Parallel.For(, , 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", );
Console.WriteLine("Connected");
NetworkStream netStream = tcpClient.GetStream(); Task.Run(() =>
{
Thread.Sleep();
while (true)
{
if (!tcpClient.Client.Connected)
{
break;
} if (netStream == null)
{
break;
} try
{
if (netStream.DataAvailable)
{
byte[] data = new byte[];
int len = netStream.Read(data, , );
var message = Encoding.UTF8.GetString(data, , len);
Console.WriteLine(message);
}
}
catch
{
break;
} Thread.Sleep();
}
}); for (int i = ; i < ; i++)
{
byte[] datas = Encoding.UTF8.GetBytes(Name);
int Len = datas.Length;
netStream.Write(datas, , Len);
Thread.Sleep();
} netStream.Close();
netStream = null;
tcpClient.Close(); Console.WriteLine("Completed");
});
}
}
}

传送门:

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

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

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

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

C#网络编程入门之TCP的更多相关文章

  1. C#网络编程入门之UDP

    目录: C#网络编程入门系列包括三篇文章: (一)C#网络编程入门之UDP (二)C#网络编程入门之TCP (三)C#网络编程入门之HTTP 一.概述 UDP和TCP是网络通讯常用的两个传输协议,C# ...

  2. 脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

    .引言 网络编程中TCP协议的三次握手和四次挥手的问题,在面试中是最为常见的知识点之一.很多读者都知道“三次”和“四次”,但是如果问深入一点,他们往往都无法作出准确回答. 本篇文章尝试使用动画图片的方 ...

  3. [转帖]脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

    脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手   http://www.52im.net/thread-1729-1-1.html     1.引言 网络编程中TCP协议的三次握手和 ...

  4. 脑残式网络编程入门(六):什么是公网IP和内网IP?NAT转换又是什么鬼?

    本文引用了“帅地”发表于公众号苦逼的码农的技术分享. 1.引言 搞网络通信应用开发的程序员,可能会经常听到外网IP(即互联网IP地址)和内网IP(即局域网IP地址),但他们的区别是什么?又有什么关系呢 ...

  5. 脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?

    本文引用了公众号纯洁的微笑作者奎哥的技术文章,感谢原作者的分享. 1.前言   老于网络编程熟手来说,在测试和部署网络通信应用(比如IM聊天.实时音视频等)时,如果发现网络连接超时,第一时间想到的就是 ...

  6. 脑残式网络编程入门(四):快速理解HTTP/2的服务器推送(Server Push)

    本文原作者阮一峰,作者博客:ruanyifeng.com. 1.前言 新一代HTTP/2 协议的主要目的是为了提高网页性能(有关HTTP/2的介绍,请见<从HTTP/0.9到HTTP/2:一文读 ...

  7. 脑残式网络编程入门(三):HTTP协议必知必会的一些知识

    本文原作者:“竹千代”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.前言 无论是即时通讯应用还是传统的信息系统,Http协议都是我们最常打交 ...

  8. 脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?

    1.引言 本文接上篇<脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手>,继续脑残式的网络编程知识学习 ^_^. 套接字socket是大多数程序员都非常熟悉的概念,它是计算机 ...

  9. [转帖]脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?

    脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?     http://www.52im.net/thread-1732-1-1.html   1.引言 本文接上篇<脑残式网 ...

随机推荐

  1. ajax学习摘抄笔记

    2019独角兽企业重金招聘Python工程师标准>>> AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). A ...

  2. Hadoop学习笔记(二)——插件安装和使用(Hadoop Eclipse)

    1. Hadoop Eclipse @ 配置 需注意 在写Hadoop的根目录时,路径不能有空格 http://blog.sina.com.cn/s/blog_56d8111101014mlg.htm ...

  3. P1466 集合 Subset Sums 搜索+递推+背包三种做法

    题目描述 对于从1到N (1 <= N <= 39) 的连续整数集合,能划分成两个子集合,且保证每个集合的数字和是相等的.举个例子,如果N=3,对于{1,2,3}能划分成两个子集合,每个子 ...

  4. Codeforce 1098-A

    A. Sum in the tree   Mitya has a rooted tree with nn vertices indexed from 11 to nn, where the root ...

  5. python(string 模块)

    1.string 模块下关键字源码定义 whitespace = ' \t\n\r\v\f' ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz' ascii_ ...

  6. mysql查询表内所有字段名和备注

    select distinct column_name as 字段名,column_comment as 字段备注 from information_schema.columns where tabl ...

  7. 给springboot增加XSS跨站脚本攻击防护功能

    XSS原理 xss攻击的原理是利用前后端校验不严格,用户将攻击代码植入到数据中提交到了后台,当这些数据在网页上被其他用户查看的时候触发攻击 举例:用户提交表单时把地址写成:山东省济南市<scri ...

  8. K. Road Widening

    \(考虑每个区域可行的区间\) \(x[1]=s[1]\ \ y[1]=s[1]+g[1]\) \(x[i]=max(x[i-1]-1,s[i]),y[i]=min(y[i-1]+1,s[i]+g[i ...

  9. React Native超简单完整示例-tabs、页面导航、热更新、用户行为分析

    初学React Native,如果没有人指引,会发现好多东西无从下手,但当有人指引后,会发现其实很简单.这也是本人写这篇博客的主要原因,希望能帮到初学者. 本文不会介绍如何搭建开发环境,如果你还没有搭 ...

  10. 物流配送中心管理系统(SSM+MYSQL)

    工程项目视频观看地址:www.toutiao.com/i6804066711… 本文首先对系统所涉及到的基础理论知识进行阐述,并在此基础上进行了系统分析.系统分析是平台开发的一个不可缺少的环节,为了能 ...