目录
一、TCP(传输控制协议)
- 都是位于传输层的协议
- TCP是面向连接的,UDP是无连接的
- TCP协议有序、安全可靠、不容易丢包
- TCP是可靠的,UDP是不可靠的
- TCP是面向字节流的,UDP是面向数据报文的
- TCP只支持点对点通信,UDP支持一对一,一对多,多对多
- TCP报文首部20个字节,UDP首部8个字节
- TCP有拥塞控制机制,UDP没有
- TCP协议下双方发送接受缓冲区都有,UDP并无实际意义上的发送缓冲区,但是存在接受缓冲区
- 例子:TCP(聊天、传输文件)
二、TCP中服务端和客户端之间发送和接收
1、服务端和客户端一对一发送和接收数据,代码案例:
namespace TCPStudy1
{
internal class Program
{
private static Encoding encoding = Encoding.GetEncoding("gb2312");
private static Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//private static Socket sClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private static Socket sClient = null;
private static Socket c = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
static void Main(string[] args)
{
while (true)
{
Console.WriteLine("ss:启动服务端,sc:启动客户端,cs:关闭服务端,cc:关闭客户端");
string str = Console.ReadLine();
if (str == "ss")
{
//服务端
StartServer();
}
else if (str == "sc")
{
//客户端
StartClient();
}
else if (str == "cs") //关闭服务端
{
s.Close();
if (sClient != null)
{
sClient.Close();
}
}
else if (str == "cc") //关闭客户端
{
c.Close();
}
else
{
Console.WriteLine("该指令不存在");
}
}
Console.ReadLine();
}
/// <summary>
/// 服务端
/// </summary>
public static void StartServer()
{
//Socket:C#提供的一个用于通信协议的类库,其中包含TCP协议的开发
//TCP:服务器端
//实例化一个TCP协议的Socket对象
//Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//给服务器绑定地址和端口:192.168.218.20:6000
IPAddress ip = IPAddress.Parse("192.168.22.1");
IPEndPoint ipPoint = new IPEndPoint(ip, 6000);
//把Ip和端口绑定到Socket里面
s.Bind(ipPoint);
//监听
s.Listen(100);
Console.WriteLine("=====================TCP服务端启动成功===================");
Task.Run(() =>
{
while (true)//循环等待客户端连接
{
//会等待客户端连接,会卡在这里,不往下运行了,直到有一个宽带连接才会继续往下运行
//当有客户端连接的时候,会返回客户端的连接对象
sClient = s.Accept();
Console.WriteLine("客户端请求连接了我,连接成功");
//讲字符串转换成byte数组
byte[] bytes = encoding.GetBytes("你今天吃饭了吗");
//给客户端发送数据
sClient.Send(bytes);
Task.Run(() => //支持多个连接本身
{
try
{
while (true)//循环接收数据
{
//接收数据
byte[] byteGet = new byte[1024];
//返回接收到的数据的大小,接收数据也会卡住,只有接收到数据的时候才会继续往下运行
int length = sClient.Receive(byteGet);
if (length == 0)
{
Console.WriteLine("一个客户端断开了连接");
return;
}
//将接收到的数据转换成字符串
string str = encoding.GetString(byteGet, 0, length);
Console.WriteLine(str);
}
}
catch (Exception ex)
{
Console.WriteLine("出现了异常" + ex.Message);
}
finally
{
//释放客户端资源
sClient.Close();
}
});
}
});
}
/// <summary>
/// 客户端
/// </summary>
public static void StartClient()
{
//Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//服务端的地址和端口
IPEndPoint ip = new IPEndPoint(IPAddress.Parse("192.168.22.1"),6000);
try
{
//连接远程服务端
c.Connect(ip);
Console.WriteLine("=====================客户端连接服务端成功=====================");
}
catch(Exception ex)
{
Console.WriteLine("连接异常:"+ex.ToString());
return;
}
//循环接收数据
Task.Run(() =>
{
try
{
while (true)
{
byte[] bytesGet = new byte[1024];
//等待接收对方发送的数据,返回数据的长度
int length = c.Receive(bytesGet);
string str = encoding.GetString(bytesGet, 0, length);
Console.WriteLine("接收的数据:" + str);
}
}
catch (Exception ex)
{
Console.WriteLine("发生了错误" + ex.Message);
}
finally
{
//关闭连接
c.Close();
}
});
//循环发送数据
Task.Run(() =>
{
try
{
int num = 1;
while (true)
{
//等待一秒种
await Task.Delay(2000);
byte[] bytesGo = encoding.GetBytes("我是客户端" + num);
//发送数据
c.Send(bytesGo);
num++;
}
}
catch (Exception ex)
{
Console.WriteLine("发生了错误" + ex.Message);
}
finally
{
//关闭连接
c.Close();
}
});
}
}
}
2、客户端-服务端-客户端传输数据,代码案例:
开启一个服务端后,可以去程序的bin\Debug目录下,双击程序的.exe文件开启多个客户端。多个客户端之间通过服务端进行收发数据
namespace TcpServer
{
internal class Program
{
private static Encoding encoding = Encoding.GetEncoding("gb2312");
//服务端
private static Socket ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private static Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//客户端A
private static Socket scA = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//客户端B
private static Socket scB = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
还没有确定客户端的连接集合
//private static List<Socket> list = new List<Socket>();
//确定的连接集合
private static Dictionary<string, Socket> tcpDic = new Dictionary<string, Socket>();
public static void Main(string[] args)
{
StartServer();
Console.ReadLine();
}
/// <summary>
/// 服务端
/// </summary>
public static void StartServer()
{
//string str = null;
//Socket ss = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//本机上还有一个回环地址:127.0.0.1
// IPAddress.Any:代表0.0.0.0 IP,可以使用本机所有IP(192.168.22.1、192.168.218.22、127.0.0.1就都可以连接上本服务端)
IPAddress ip = IPAddress.Any;
IPEndPoint IpPoint = new IPEndPoint(ip, 8080);
//把IP和端口绑定到Socket里面
ss.Bind(IpPoint);
//侦听
ss.Listen(100);
Console.WriteLine("服务端开启成功");
Task.Run(() => // 服务端循环给客户端发送数据
{
try
{
while (true)
{
//循环接收连接的客户端
socket = ss.Accept();
//list.Add(socket);
Task.Run(() =>
{
//用来判断身份有没有被确定
bool isIdCard = false;
//本身的唯一号
string idCard = string.Empty;
while (true)
{
//接收客户端发送的数据
byte[] bytes = new byte[1024];
int length = socket.Receive(bytes);
if (length == 0)
{
//关闭客户端时,还需要从集合中删除这个客户端连接
if (tcpDic.ContainsKey(idCard))
{
tcpDic.Remove(idCard);
}
socket.Close();
return;
}
string msg = encoding.GetString(bytes, 0, length);
if (isIdCard) //身份已经确定
{
//解析消息格式
int index = msg.IndexOf("?");
if (index < 0)
{
continue;
}
//客户端身份
string onlyCode = msg.Substring(0, index);
//客户端消息
string strMsg = msg.Substring(index + 1);
//找到接收方的Socket连接
if (tcpDic.ContainsKey(onlyCode))
{
Socket socketReceive = tcpDic[onlyCode];
socketReceive.Send(encoding.GetBytes(idCard + "?" + strMsg));
}
}
else //身份未确定
{
isIdCard = true;
idCard = msg;
//把已经确定身份的客户端添加到确定身份的键值对集合中
tcpDic[msg] = socket;
}
Console.WriteLine("服务端接收到的客户端的消息:" + msg);
}
});
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
});
}
}
}
//=================================客户端================================
{
internal class Program
{
private static Encoding encoding = Encoding.GetEncoding("gb2312");
public static void Main(string[] args)
{
StartCilent();
}
public static void StartCilent()
{
Socket sc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ip = new IPEndPoint(IPAddress.Parse("192.168.22.1"), 9500);
//连接服务端
sc.Connect(ip);
Console.WriteLine("请输入自己的代号:");
string cmd = Console.ReadLine();
//发送自己的代号
sc.Send(encoding.GetBytes(cmd));
//循环接收数据
Task.Run(() =>
{
while (true)
{
byte[] buffer = new byte[1024];
int length = sc.Receive(buffer);
string msg = encoding.GetString(buffer, 0, length);
int index = msg.IndexOf("?");
if (index > 0)
{
string name = msg.Substring(0, index);
string str = msg.Substring(index + 1);
Console.WriteLine($"{name}发送消息过来了,消息内容是:{str}");
}
else
{
//如果没有解析出来,直接显示
Console.WriteLine($"收到消息:{msg}");
}
}
});
//循环发送消息
while (true)
{
string msg = Console.ReadLine();
if (!string.IsNullOrEmpty(msg))
{
sc.Send(encoding.GetBytes(msg));
}
}
}
}
}
3、服务端接收客户端发送的文件,并保存
namespace TcpFile
{
internal class Program
{
static void Main(string[] args)
{
StartServer();
StartClient();
Console.ReadLine();
}
private static Socket socketServer;
private static Socket socketClient;
private static Encoding encoding = Encoding.GetEncoding("gb2312");
/// <summary>
/// 接收客户端发送过来的文件
/// </summary>
private static void StartServer()
{
socketServer = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
socketServer.Bind(new IPEndPoint(IPAddress.Any, 8080));
socketServer.Listen(100);
Console.WriteLine("服务端启动成功");
ServerAccept();
}
//获取服务端与客户端的连接
private static void ServerAccept()
{
Task.Run(() =>
{
while (true)
{
//1.等待客户端连接
Socket socketC = socketServer.Accept();
Console.WriteLine("一个客户端上线了");
//2.接收客户端消息
byte[] buffer = new byte[1024];
int length = socketC.Receive(buffer);
//文件信息的消息
string str = encoding.GetString(buffer,0,length);
string[] strs = str.Split('-');
long fileSize = Convert.ToInt64(strs[0]);
string fileName = strs[1];
//3.给客户端回复:Ok
FileStream stream = File.Create($@"D:\C盘移动的\Desktop\上位机\Day\阮老师讲的\10-TCP协议\TcpFile\bin\Debug\Tcp\\{fileName}");
socketC.Send(encoding.GetBytes("OK"));
//4.循环接收客户端发送的文件(当文件内容大小与fileSize一致时,就认为文件读取结束了)
long nowSize = 0;//当前读取的文件长度的大小
Console.WriteLine("正在接收文件,请稍后...");
//循环接收
while (true)
{
buffer = new byte[1024*10];
length = socketC.Receive(buffer);
stream.Write(buffer, 0, length);
nowSize += length;
if (nowSize == fileSize)
{
break;
}
}
socketC.Close();
stream.Close();
Console.WriteLine("文件接收完毕");
}
});
}
/// <summary>
/// 启动客户端
/// </summary>
private static void StartClient()
{
socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//1.连接服务端
socketClient.Connect(new IPEndPoint(IPAddress.Parse("192.168.22.1"), 8080));
string fullName = "D:\\C盘移动的\\Desktop\\上位机\\Day\\阮老师讲的\\10-TCP协议\\TcpFile\\bin\\Debug\\Tcp.txt";
string fileName = Path.GetFileName(fullName);
FileStream fs = File.OpenRead(fullName);
//2.发送文件名和文件大小(格式:大小-文件名)
socketClient.Send(encoding.GetBytes($"{fs.Length}-{fileName}"));
//3.接收服务端消息:"OK"
byte[] buffer = new byte[100];
int length = socketClient.Receive(buffer);
string str = encoding.GetString(buffer,0,length);
if (str != "OK")
{
fs.Close();
socketClient.Close();
return;
}
Console.WriteLine("正在发送文件,请稍后...");
//4.发送文件
Task.Run(() =>
{
buffer = new byte[1024*10];
while (true)
{
//读取文件流
length = fs.Read(buffer, 0, buffer.Length);
//当读取长度为0时,就说明文件读取完毕
if (length == 0)
{
socketClient.Close();
fs.Close();
Console.WriteLine("发送结束");
break;
}
socketClient.Send(buffer, 0, length, SocketFlags.None);
}
});
}
}
}
4、服务端与客户端以16进制收发数据
namespace Tcp16
{
/// <summary>
///
/// </summary>
internal class Program
{
static void Main(string[] args)
{
StartServer();
StartClient();
Console.ReadLine();
}
/// <summary>
/// 服务端
/// </summary>
private static void StartServer()
{
Socket ss = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
ss.Bind(new IPEndPoint(IPAddress.Any,8080));
ss.Listen(100);
Console.WriteLine("服务端启动成功");
Task.Run(() =>
{
Socket sClient = ss.Accept();
Console.WriteLine("客户端连接");
//接收客户端消息
while (true)
{
byte[] buffer = new byte[1024];
int length = sClient.Receive(buffer);
byte[] date = new byte[length];
string str = string.Empty;
for (int i = 0;i<length;i++)
{
str += $"{buffer[i].ToString("X2")}";
date[i] = (byte)(buffer[i] + 1);
}
Console.WriteLine("服务端接收的消息:"+str);
sClient.Send(date);
}
});
}
/// <summary>
/// 客户端
/// </summary>
private static void StartClient()
{
Socket sc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sc.Connect(new IPEndPoint(IPAddress.Parse("192.168.22.1"), 8080));
Task.Run(() =>
{
while (true)
{
sc.Send(new byte[] {0xAA,0xBB,0xCC });
byte[] bytes = new byte[1024];
int length = sc.Receive(bytes);
string str = string.Empty;
for (int i =0;i<length;i++)
{
str += $"{bytes[i].ToString("X2")}";
}
Console.WriteLine("收到服务端发送的数据:"+str);
Task.Delay(1000).Wait();
}
});
}
}
}
//======================案例练习:发送16进制判断接收湿度还是温度=====================
namespace Tcp16
{
/// <summary>
/// 根据客户端发送过来的数据,判断服务端是给客户端发送温度还是湿度
/// </summary>
internal class WD_SD
{
static Random random = new Random();
static void Main(string[] args)
{
Console.WriteLine("44444");
StartServer();
StartClient();
Console.ReadLine();
}
/// <summary>
/// 服务端
/// </summary>
private static void StartServer()
{
Socket ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ss.Bind(new IPEndPoint(IPAddress.Any, 8080));
ss.Listen(100);
Console.WriteLine("服务端启动成功");
Task.Run(() =>
{
Socket sClient = ss.Accept();
Console.WriteLine("客户端上线");
//接收客户端消息
while (true)
{
byte[] buffer = new byte[1024];
int length = sClient.Receive(buffer);
if (length == 1)
{
byte date = buffer[0];
if (date == 0x0A)
{
//随机生成一个温度
byte tem = (byte)random.Next(0, 50);
sClient.Send(new byte[] {tem});
}
else if(date == 0x0B) //湿度
{
//随机生成一个温度
byte hum = (byte)random.Next(20, 60);
sClient.Send(new byte[] { hum });
}
}
}
});
}
/// <summary>
/// 客户端
/// </summary>
private static void StartClient()
{
Socket sc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sc.Connect(new IPEndPoint(IPAddress.Parse("192.168.22.1"), 8080));
Task.Run(() =>
{
while (true)
{
//byte[] bytes = new byte[1024];
int num = random.Next(0,100);
byte cmd = (byte) (num%2 == 0 ? 0x0A : 0x0B);
sc.Send(new byte[] { cmd});
//接收
byte[] buffer = new byte[1024];
int length = sc.Receive(buffer);
if (length == 1)
{
byte date = buffer[0];
if (cmd == 0x0A)
{
Console.WriteLine("温度0A:"+date);
}
else if (cmd == 0x0B) //湿度
{
Console.WriteLine("湿度0B:" + date);
}
}
Task.Delay(1000).Wait();
}
});
}
}
}
四、UDP (用户数据报协议)
- 面向无连接,可靠的
- 延时小,传输效率高
- 无序,容易丢包
- 一对一、一堆多、多对多数据发送方式
1、单播:代码案例:
static void Main(string[] args)
{
UdpRevice();
UdpSend();
Console.ReadKey();
}
private static Encoding encoding = Encoding.GetEncoding("gb2312");
// 单播---接收
static void UdpRevice()
{
Socket udp = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
IPEndPoint ipP = new IPEndPoint(IPAddress.Any,8080);
udp.Bind(ipP);
Task.Run(() =>
{
byte[] bytes = new byte[1024];
while (true)
{
EndPoint endP = new IPEndPoint(0,0);
int length = udp.ReceiveFrom(bytes,ref endP);
if (length <= 0)
{
udp.Close();
return;
}
string str = encoding.GetString(bytes,0, length);
Console.WriteLine($"接收到{endP}发送过来的数据:"+ str);
}
});
}
//单播---发送
static void UdpSend()
{
Socket udp = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
Task.Run(() =>
{
//接收方IP和端口
EndPoint endP = new IPEndPoint(IPAddress.Parse("192.168.22.1"),8080);
//循环发送
while (true)
{
byte[] bytes = encoding.GetBytes("发送方发送的数据");
udp.SendTo(bytes, endP);
Task.Delay(1000).Wait();
}
});
}
2、组播:代码案例
/// <summary>
/// 组播接收数据
/// </summary>
static void MulRevice()
{
UdpClient udp = new UdpClient(8080);
IPAddress ipAddress = IPAddress.Parse("235.0.0.1");
//添加进组
udp.JoinMulticastGroup(ipAddress);
Task.Run(() =>
{
//发送方的IP和端口
IPEndPoint endP = new IPEndPoint(0, 0);
//循环接收数据
while (true)
{
byte[] buffer = udp.Receive(ref endP);
string str = encoding.GetString(buffer);
Console.WriteLine($"组播:接收到的数据:{str}。发送方的IP和端口:{endP}");
//回消息,发送数据
byte[] bytes = encoding.GetBytes("接收消息后回消息");
udp.Send(bytes,bytes.Length,endP);
}
});
}
/// <summary>
/// 组播发送数据
/// </summary>
static void MulSend()
{
IPAddress ipAddress =IPAddress.Parse("235.0.0.1");
UdpClient udpTo = new UdpClient(8088);//绑定本地的一个端口号
udpTo.JoinMulticastGroup(ipAddress);
Task.Run(() =>
{
//要给哪个组播发数据,获取组播的IP和端口
IPEndPoint endP = new IPEndPoint(ipAddress,8080);
while (true)
{
byte[] bytes = encoding.GetBytes("给组播发的数据2222");
udpTo.Send(bytes,bytes.Length,endP);
byte[] bytes1 = udpTo.Receive(ref endP);
string str = encoding.GetString(bytes1);
Console.WriteLine($"发送之后,在接收一条接收方回应的消息:{str}。接收方的IP和端口是:========={endP}");
Task.Delay(1000).Wait();
}
});
}
3、广播:代码案例
#region 广播
private static void BroadcastReceive()
{
//自己的端口,别人发送数据时发送到这个端口
UdpClient udpClient = new UdpClient(8080);
Task.Run(() => {
IPEndPoint endP = new IPEndPoint(0,0);
//循环接收数据
while (true)
{
//接收数据
byte[] buffer = udpClient.Receive(ref endP);
string str = encoding.GetString(buffer);
//输出
Console.WriteLine($"接收到的广播数据{str},发送数据的广播的IP和端口{endP}")
}
});
}
private static void BroadcastSend()
{
//发送时,不写端口,就会随机一个端口发送
UdpClient udp = new UdpClient();
Task.Run(() =>
{
//循环发送广播数据
while (true)
{
//广播IP:255.255.255.255
IPEndPoint endP = new IPEndPoint(IPAddress.Parse("255.255.255.255"),8080);
//循环发送
int num = 1;
while (true)
{
byte[] bytes = encoding.GetBytes("我是广播"+num);
num++;
udp.Send(bytes,bytes.Length,endP);
Task.Delay(1000).Wait();
}
}
});
}
五、TCP与UDP的区别
它们的区别主要有以下四点:
1、TCP 是面向连接的(三次握手建立连接),UDP 是无连接的即发送数据前不需要先建立链接。
2、TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,不保证可靠交付。 (并且因为 TCP 可靠,面向连接,不会丢失数据因此适合大数据量的交换)
3、TCP 是面向字节流,UDP 面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如 IP 电话和视频会议等)。
4、TCP 只能是 1 对 1 的,UDP 支持 1 对 1,1 对多。
5、TCP 的首部较大为 20 字节,而 UDP 只有 8 字节。
6、TCP 是面向连接的可靠性传输,而 UDP 是不可靠的
图表对比: