Socket编程-UDP和TCP协议的区别

TCP介绍

TCP运行机制

UDP介绍

UDP运行机制

  • TCP会保证包的顺序,先发的先到达,UDP不保证

TCP和UDP的区别

Socket编程-TCP服务器端+客户端

TCP服务器端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System;
using System.Text;
using System.Net.Sockets;
using System.Net;

namespace SocketNetLearning1
{
class Program
{
static void Main(string[] args)
{
//1.创建Socket(内网、流、TCP协议)
Socket tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//设置SOCKET允许多个SOCKET访问同一个本地IP地址和端口号
tcpServer.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

//2.绑定IP跟端口号(在CMD控制台中,输入ipconfig来查询)
//第一种绑定IP跟端口号的方法
IPAddress ipaddress = new IPAddress(new byte[] { 192, 168, 0, 169 });
//IPEndPoint是对IP和端口做了一层封装的类
EndPoint point = new IPEndPoint(ipaddress, 7788);
//向操作系统申请一个可用的ip跟端口号,用来做通信
tcpServer.Bind(point);

//3.开始监听(等待客户端连接),参数是最大连接数
tcpServer.Listen(100);
Console.WriteLine("开始监听");

//暂停当前线程,直到有一个客户端链接过来,再进行下面的代码
Socket clientSocket = tcpServer.Accept();
Console.WriteLine("一个客户端连接过来了");

//-----------------使用返回Socket跟客户端做通信----------------------
//-----------------给客户端发消息-----------------------------------
string message = "Hello 欢迎你";
//对字符串编码,得到一个字符串的字节数组
byte[] data = Encoding.UTF8.GetBytes(message);
clientSocket.Send(data);
Console.WriteLine("向客户端发送了欢迎消息");

//-----------------创建一个字节数组来当数据容器,来承接客户端发送过来的数据
//-----------------接受客户端的消息-------------------------------
byte[] data2 = new byte[1024];
int length = clientSocket.Receive(data2);
//把字节数据转化成一个字符串
string message2 = Encoding.UTF8.GetString(data2,0,length);
Console.WriteLine(string.Format("接收到的消息:{0}", message2));

Console.ReadKey();
}
}
}

TCP客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace SocketNetTcpClient
{
class TcpClient
{
static void Main(string[] args)
{
//1.创建Socket
Socket tcpClinet = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//2.发起建立连接的请求
//第二种绑定IP跟端口号的方法,把一个字符串的IP地址转化成一个IPAddress对象
IPAddress ipaddress = IPAddress.Parse("192.168.0.169");
EndPoint point = new IPEndPoint(ipaddress, 7788);
//通过IP+端口号,定位一个要连接到的服务器端
tcpClinet.Connect(point);

//-------------接受服务器端传过来的数据--------------
byte[] data = new byte[1024];
//传递一个byte数组,实际上这个data数组用来接收数据
int length = tcpClinet.Receive(data);
//length返回值表示接收了多少字节的数据只把接收到的数据做一个转化
string message = Encoding.UTF8.GetString(data, 0, length);
Console.WriteLine(message);

//-------------向服务器端发送消息-------------------
//读取用户的输入 把输入发送到服务器端
string message2 = Console.ReadLine();
//把字符串转化成字节数组然后发送到服务器端
tcpClinet.Send(Encoding.UTF8.GetBytes(message2));

Console.ReadKey();
}
}
}

运行效果

运行效果

聊天室案例

TCP服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Net;

namespace SocketNetLearning1
{
class TcpServer
{
//创建静态的Client集合,方便管理
static List<Client> clientList = new List<Client>();
/// <summary>
/// 广播消息
/// </summary>
/// <param name="message">消息内容</param>
public static void BroadcastMessage(string message)
{
//存放没有连接的客户端
var notConnectedList = new List<Client>();
//遍历clientList列表,去发送消息
foreach (var client in clientList)
{
//如果未断开连接,发送消息
if (client.Connected)
client.SendMessage(message);
else
notConnectedList.Add(client);
}
//移除没有连接的客户端
foreach (var temp in notConnectedList)
{
clientList.Remove(temp);
}
}
static void Main(string[] args)
{
Socket tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tcpServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.0.169"), 7788));
tcpServer.Listen(100);
Console.WriteLine("服务器已开启");
while (true)
{
Socket clientSocket = tcpServer.Accept();
Console.WriteLine("一个用户已连接!");
//把与每个客户端通信的逻辑(收发消息)放到client类里面进行处理
Client client = new Client(clientSocket);
//添加到集合中,可以得到服务器与哪些客户端进行了连接
clientList.Add(client);
}
Console.ReadKey();
}
}
}

Client类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace SocketNetLearning1
{
/// <summary>
/// 用来跟客户端做通信
/// </summary>
class Client
{
private Socket clientSocket;
//创建一个线程
private Thread t;
//这是一个容器
private readonly byte[] data = new byte[1024];
public Client(Socket s)
{
clientSocket = s;
//启动一个线程,处理客户端的数据接收
t = new Thread(ReceiveMessage);
t.Start();
}
private void ReceiveMessage()
{
//一直接收客户端数据,使用死循环
while (true)
{
//在接收数据之前 判断一下socket连接是否断开(安全校检)
//true 如果链接已关闭、重置,或者终止,则返回
if (clientSocket.Poll(10, SelectMode.SelectRead))
{
//关闭当前连接
clientSocket.Close();
break;//跳出循环,终止线程的执行
}
int length = clientSocket.Receive(data);
string message = Encoding.UTF8.GetString(data, 0, length);
//接收到数据的时候 要把这个数据分发到客户端
//广播这个信息
TcpServer.BroadcastMessage(message);
Console.WriteLine("收到了消息:" + message);
}
}

//发送消息
public void SendMessage(string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
clientSocket.Send(data);
}

//判断是否连接
public bool Connected
{
get { return clientSocket.Connected; }
}

}
}

Unity客户端

客户端可输入IP地址,端口号,昵称;获取消息添加了本地的时间节点以及富文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;

public class TCPClientScript : MonoBehaviour
{
//昵称的InputField
public InputField nameInputField;
//IP地址的输入框
public InputField ipAddressInputField;
//端口号的输入框
public InputField portInputField;
//消息输入框
public InputField messageInputField;
public Text textValue;
Socket clientSocket;
Thread t;

//数据容器
readonly byte[] data = new byte[1024];
//消息容器
string message = "";


// Update is called once per frame
void Update()
{
if(message != null && message != "")
{
string[] messages = message.Split(',');
//消息显示格式(富文本)
textValue.text += "\n" + "<color=red>" + messages[0] + "\t" + GetTime() + "</color>" + "\n" + "\t" + messages[1];
//清空消息
message = "";
}
}
public void OnConnectedToServer()
{
string ipAdress = ipAddressInputField.text;
int port = Convert.ToInt32(portInputField.text);
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//跟服务器端连接
clientSocket.Connect(new IPEndPoint(IPAddress.Parse(ipAdress), port));
//创建一个新的线程用来接受消息
t = new Thread(ReceiveMessage);
t.Start();
}
/// <summary>
/// 这个线程方法,用于循环接收信息
/// </summary>
void ReceiveMessage()
{
while (true)
{
if (clientSocket.Connected == false)
break;
//获取得到的数据长度
int length = clientSocket.Receive(data);
//转化成字符串
message = Encoding.UTF8.GetString(data, 0, length);
}
}
void SendAMessage(string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
clientSocket.Send(data);
}
public void OnSendButtonClick()
{
string value = nameInputField.text + "," + messageInputField.text;
SendAMessage(value);
messageInputField.text = "";
}
private void OnDestroy()
{
//既不接收也不发送,设个方法确保socket关闭前所有数据都收发完毕
clientSocket.Shutdown(SocketShutdown.Both);
//关闭连接
clientSocket.Close();
}
//获取本地时间
public string GetTime()
{
System.DateTime currentTime = new DateTime();
currentTime = System.DateTime.Now;
string timeStr = currentTime.Year.ToString() + "/"
+ currentTime.Month.ToString() + "/"
+ currentTime.Day.ToString() + " "
+ currentTime.Hour.ToString() + ":"
+ currentTime.Minute.ToString() + ":"
+ currentTime.Second.ToString() + " ";
return timeStr;
}
}

简单UI排布

脚本引用

运行结果

Socket编程-UDP服务器端与客户端

UDP服务器端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace UDPLearning
{
class UdpServer
{
private static Socket udpServer;
static void Main(string[] args)
{
//创建Socket
udpServer = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//绑定IP跟端口号
udpServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.0.169"), 7788));
//接收数据
new Thread(ReceiveMessage) { IsBackground = true }.Start();
Console.ReadKey();
}
static void ReceiveMessage()
{
while (true)
{
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
byte[] data = new byte[1024];
//这个方法会把数据的来源(IP:Port)放到第二个参数上
int length = udpServer.ReceiveFrom(data, ref remoteEndPoint);
string message = Encoding.UTF8.GetString(data, 0, length);
Console.WriteLine("从IP:" + (remoteEndPoint as IPEndPoint).Address.ToString() + ":" + (remoteEndPoint as IPEndPoint).Port + "收到了数据:" + message);
}
}
}
}

UDP客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace UDPLearningClient
{
class UdpClient
{
static void Main(string[] args)
{
//创建Socket
Socket udpClient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
while (true)
{
//发送数据
EndPoint serverPoint = new IPEndPoint(IPAddress.Parse("192.168.0.169"), 7788);
string message = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(message);
udpClient.SendTo(data, serverPoint);
}
}
}
}

UDP传输的电报文Dgram

TcpListener与TcpcLient

socket - TcpClient,TcpListener,UdpClient

应用程序可以通过TcpClientTcpListenerUdpClient类使用传输控制协议(TCP)和用户数据文报协议(UDP)服务。这些协议类建立在System.Net.Sockets.Socket类的基础之上,负责数据传送的细节(也就是说TcpClientTcpListenerUdpClient类是用来简化Socket)

TcpClientTcpListener使用NetworkStream类表示网络。使用GetStream方法返回网络流,然后调用该流的ReadWrite方法。NetworkStream不拥有协议类的基础套接字,因此关闭它并不影响套接字。

UdpClient类使用字节数组保存UDP数据文报。使用Send方法向网络发送数据,使用Receive方法接收传入的数据文报

TcpClient

TcpClient类提供了一些简单的方法,用于在同步阻止模式下通过网络来连接、发送和接收流数据。为使TcpClient连接并交换数据,使用TCP Protocoltype创建的TcpListener或Socket必须侦听是否有传入的连接请求。可以使用下面两种方法之一连接到该侦听器:

  1. 创建一个TcpClient,并调用三个可用的Connect方法之一。
  2. 使用远程主机的主机名和端口号创建TcpClient。此构造函数将自动尝试一个连接。

给继承者的说明要发送和接收的数据,请使用GetStream方法来获取一个NetworkStream。调用NetworkStreamWriteRead方法与远程主机之间发送和接收数据。使用Close方法释放与TcpClient关联的所有资源。

TcpListener

TcpListener类提供一些简单方法,用于在阻止同步模式下侦听和接受传入连接请求。可使用TcpClient或Socket来连接TcpListener。可使用IPEndPoint、本地IP地址以及端口号或者仅使用端口号,来创建TcpListener。可以将本地IP地址指定为Any,将本地端口号指定为0(如果希望基础服务提供程序为您分配这些值)。如果您选择这样做,可以在套接字后使用LocalEndPoint属性来标记已指定的信息。

Start方法用来开始侦听传入的连接请求。Start将对传入连接进行排队,直至您调用Stop方法或它已经完成MaxConnections排队为止。可使用AcceptSocketAcceptTcpClient从传入连接请求队列提取连接。这两种方法将阻止。如果要避免阻止,可首先使用Pending方法来确定队列中是否有可用的连接请求。

调用Stop方法来关闭TcpListener

TcpListener服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace TcplistenerLearning
{
class TcpListenerProgram
{
static void Main(string[] args)
{
//1.TcpListener对socket进行了一层封装,这个类里面自己会去创建socket对象
TcpListener listener = new TcpListener(IPAddress.Parse("192.168.0.169"), 7788);
//2.开始进行监听
listener.Start();
//3.等待客户端连接过来
TcpClient client = listener.AcceptTcpClient();
//4.取得客户端发送过来的数据
//得到了一个网络流 从这个网络流可以取得客户端发送来的数据
NetworkStream stream = client.GetStream();
//创建一个数据的容器,用来承接数据
byte[] data = new byte[1024];
while (true)
{
//读取数据
//0表示从数组的哪个索引开始存放数据
//1024表示最大读取的字节数
//length表示实际储存的数据
int length = stream.Read(data, 0, 1024);
string message = Encoding.UTF8.GetString(data, 0, length);
Console.WriteLine("收到了消息" + message);
}
}
}
}

TcpClient客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Net.Sockets;
using System.Text;

namespace TcpClientLearning
{
class TcpClientProgram
{
static void Main(string[] args)
{
//当我们创建TcpClient对象的时候,就会跟server去连接
TcpClient client = new TcpClient("192.168.0.169", 7788);
NetworkStream stream = client.GetStream();//通过网络进行数据的交换
//read用来读取数据,write用来写入数据其实就是发送数据
//利用一个死循环,重发向服务器端发送数据
while (true)
{
string message = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(message);
stream.Write(data, 0, data.Length);
}

}
}
}

运行结果

UdpClient

UDP协议不需要做连接,客户端是服务器端,服务器端也是客户端

UdpClient类提供了一些简单的方法,用于在阻止同步模式下发送和接收无连接UDP数据报。因为UDP是无连接传输协议,所以不需要在发送和接收数据前建立远程主机连接。但您可以选择使用下面两种方法之一类建立默认远程主机:

  1. 使用远程主机名和端口号作为参数创建UdpClient类的实例。
  2. 创建UdpClient类的实例,然后调用Connect方法。

可以使用在UdpClient中提供的任何一种发送方法将数据发送到远程设备。使用Receive方法可以从远程主机接收数据。

UdpClient方法还允许发送和接收多路广播数据报。使用JoinMulticastGroup方法可以将UdpClient预订给多路广播组。使用DropMulticastGroup方法可以从多路广播组中取消对UdpClient的预订。

Udp服务器端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace UdpClientApp1
{
class UdpClientServer
{
static void Main(string[] args)
{
//创建udpClient 绑定IP跟端口号
UdpClient udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse("192.168.0.169"), 7788));
while (true)
{
//接收数据
IPEndPoint point = new IPEndPoint(IPAddress.Any, 0);
//通过point确定数据来自哪个IP哪个端口号
byte[] data = udpClient.Receive(ref point);
string message = Encoding.UTF8.GetString(data);
Console.WriteLine("收到了消息" + message);
}
}
}
}

Udp客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace UdpClientApp2
{
class UdpClientClient
{
static void Main(string[] args)
{
//创建udpClient对象
UdpClient client = new UdpClient();
UTF8Encoding uTF8Encoding = new UTF8Encoding();
while (true)
{
string message = Console.ReadLine();
byte[] data = uTF8Encoding.GetBytes(message);
client.Send(data, data.Length, new IPEndPoint(IPAddress.Parse("192.168.0.169"), 7788));
}
}
}
}

运行效果