13.1 socket(套接字)编程
13.1.1 Tcp
基于Tcp协议的Socket通讯类似于B/S架构,面向连接,但不同的是服务器端可以向客户端主动推送消息。
使用Tcp协议通讯需要具备以下几个条件:
(1).建立一个套接字(Socket)
(2).绑定服务器端IP地址及端口号–服务器端
(3).利用Listen()方法开启监听–服务器端
(4).利用Accept()方法尝试与客户端建立一个连接–服务器端
(5).利用Connect()方法与服务器建立连接–客户端
(6).利用Send()方法向建立连接的主机发送消息
(7).利用Recive()方法接受来自建立连接的主机的消息(可靠连接)
namespace c_sharp_socket编程_TCP协议_服务器端
{
class Program
{
static void Main(string[] args)
{
// 1创建socket
Socket tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 2绑定IP和端口号 192.168.1.101
IPAddress ipAddres = new IPAddress(new byte[] { 192, 168, 1, 101 });
EndPoint point = new IPEndPoint(ipAddres,7788);//IPEndPoint是对IP+端口做了一层封装的类
tcpServer.Bind(point);//向操作系统申请一个可用的IP和端口号,用来通信
// 3开始监听(等待客户端连接)
tcpServer.Listen(100);//参数是最大连接数
Console.WriteLine("开始监听");
Socket clientSocket = tcpServer.Accept();//暂停当前线程,直到有一个客户端链接过来
Console.WriteLine("一个客户端连接成功");
// 4使用返回的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);//这里传递一个byte数组,data数组用来接收数据
//length返回值表示接收了多少字节的数据
string message2 = Encoding.UTF8.GetString(data2, 0, length);
Console.WriteLine("接收到一个客户端发送来的消息:",message2);
Console.ReadKey();
}
}
}
namespace c_sharp_socket编程_ TCP协议_客户端
{
class Program
{
static void Main(string[] args)
{
// 1创建socket
Socket tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 2发起建立连接请求
IPAddress ipAddress = IPAddress.Parse("192.168.1.101");//可以把一个字符串的ip地址转化为IPAddress 的对象
EndPoint point = new IPEndPoint(ipAddress, 7788);
tcpClient.Connect(point);//通过ip:端口号定位一个要连接到的服务器
byte[] data = new byte[1024];
int length = tcpClient.Receive(data);//这里传递一个byte数组,data数组用来接收数据
//length返回值表示接收了多少字节的数据
string message = Encoding.UTF8.GetString(data,0,length);
Console.WriteLine(message);
// 3向服务器端发送消息
string message2 = Console.ReadLine();
tcpClient.Send(Encoding.UTF8.GetBytes(message2));
Console.ReadKey();
}
}
}
13.1.2 Udp
基于Udp协议是无连接模式通讯,占用资源少,响应速度快,延时低。至于可靠性,可通过应用层的控制来满足。(不可靠连接)
(1).建立一个套接字(Socket)
(2).绑定服务器端IP地址及端口号–服务器端
(3).通过SendTo()方法向指定主机发送消息(需提供主机IP地址及端口)
(4).通过ReciveFrom()方法接收指定主机发送的消息(需提供主机IP地址及端口)
namespace c_sharp_practice10_UDP协议_服务器端
{
class Program
{
static Socket udpServer;
static void Main(string[] args)
{
// 1创建Socket
udpServer = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// 2绑定IP:Port
udpServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.1.101"), 7788));
// 3接收数据
new Thread(ReceiveMessage) { IsBackground = true }.Start();
//udpServer.Close();
Console.ReadKey();
}
static void ReceiveMessage()
{
while (true)
{
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
byte[] data = new byte[1024];
int length = udpServer.ReceiveFrom(data, ref remoteEndPoint);//这个方法会把数据的来源放到第二个参数上
string message = Encoding.UTF8.GetString(data);
Console.WriteLine("从" + (remoteEndPoint as IPEndPoint).Address.ToString() + ":" +
(remoteEndPoint as IPEndPoint).Port + "收到了消息");
}
}
}
}
namespace c_sharp_practice10_UDP协议_客户端
{
class Program
{
static void Main(string[] args)
{
// 1创建Socket
Socket udpClient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// 2发送数据
while (true)
{
EndPoint serverPoint = new IPEndPoint(IPAddress.Parse("192.168.1.101"), 7788);
string message = Console.ReadLine();
byte[] data = new byte[1024];
udpClient.SendTo(data, serverPoint);
}
udpClient.Close();
Console.ReadKey();
}
}
}
TCP协议和UDP协议连接过程的区别:
(1)基于连接与无连接;
(2)对系统资源的要求(TCP较多,UDP少);
(3)UDP程序结构较简单;
(4)流模式与数据报模式 ;
(5)TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。
13.1.3 Unity聊天室-TCP协议
namespace c_sharp_聊天室_服务器端
{
//用来与客户端做通信
class Client
{
private Socket clientSocket;
private Thread t;
private byte[] data = new byte[1024];//数据容器
public Client(Socket s)
{
clientSocket = s;
//启动一个线程处理客户端数据的接收
t = new Thread(ReceiveMessage);
t.Start();
}
private void ReceiveMessage()
{
//一直接收客户端的数据
while (true)
{
//安全校验,判断socket连接是否断开
if (clientSocket.Poll(5,SelectMode.SelectRead))
{
clientSocket.Close();
break;
}
int length = clientSocket.Receive(data);
string message = Encoding.UTF8.GetString(data, 0, length);
//服务器接收到数据后,把数据分发到各个客户端
Program.BroadcastMessage(message);
Console.WriteLine("服务器接收到:" + message);
}
}
public void SendMessage(string message)
{
clientSocket.Send(Encoding.UTF8.GetBytes(message));
}
public bool Connected
{
get{ return clientSocket.Connected; }
}
}
}
namespace c_sharp_聊天室_服务器端
{
class Program
{
static List<Client> clientList = new List<Client>();//创建一个客户端对象的数组,管理每一个连接到的客 户端
//服务器端在客户端广播消息
public static void BroadcastMessage(string message)
{
var notConnectedList = new List<Client>();
foreach (var client in clientList)
{
if (client.Connected)
{
client.SendMessage(message);
}
else
{
notConnectedList.Add(client);
}
}
foreach(var notClient in notConnectedList)
{
clientList.Remove(notClient);
}
}
static void Main(string[] args)
{
//创建socket并绑定IP:端口
Socket tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tcpServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.1.101"), 7788));
//监听
tcpServer.Listen(100);
Console.WriteLine("Server Running...");
while (true)
{
Socket clientSocket = tcpServer.Accept();
Console.WriteLine("A client is Connected (U_U)");
Client client = new Client(clientSocket);//把与每个客户端通信的逻辑(收发消息)放到Client类 里处理
clientList.Add(client);
}
}
}
}
Unity客户端
public class ChatManger : MonoBehaviour
{
public string ipAddress = "192.168.1.101";
public int port = 7788;
public UIInput textInput;
public UILabel chatLabel;
private Socket clientSocket;
private Thread t;
private byte[] data = new byte[1024];//数据容器
private string message;//消息容器
// Use this for initialization
void Start()
{
ConnectToServer();
}
// Update is called once per frame
void Update()
{
if (message != null && message != "")
{
chatLabel.text += "\n" + message;
message = "";
}
}
void ConnectToServer()
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//连接服务器端
clientSocket.Connect(new IPEndPoint(IPAddress.Parse(ipAddress), port));
//开启一个线程用来接收消息
t = new Thread(ReceiveMessage);
t.Start();
}
//用来循环接收消息的线程
void ReceiveMessage()
{
while (true)
{
if (clientSocket.Connected == false)
{
break;
}
int length = clientSocket.Receive(data);
message = Encoding.UTF8.GetString(data, 0, length);
//chatLabel.text += "\n" + message;//Unity不允许在单独的线程中处理组件
}
}
void SendMessage(string message)
{
clientSocket.Send(Encoding.UTF8.GetBytes(message));
}
public void OnSendButtonClick()
{
string value = textInput.value;
SendMessage(value);
textInput.value = "";
}
private void OnDestroy()
{
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();//关闭连接
}
}
13.1.4 TcpClient&TcpListener
应用程序可以通过 TCPClient、TCPListener 和 UDPClient 类使用传输控制协议 (TCP) 和用户数据文报协议 (UDP) 服务。这些协议类建立在 System.Net.Sockets.Socket 类的基础之上,负责数据传送的细节。(也就是说TCPClient、TCPListener 和 UDPClient 类是用来简化Socket)
TcpClient 和 TcpListener 使用 NetworkStream 类表示网络。使用 GetStream 方法返回网络流,然后调用该流的 Read 和 Write 方法。NetworkStream 不拥有协议类的基础套接字,因此关闭它并不影响套接字。
UdpClient类使用字节数组保存UDP 数据文报。使用Send方法向网络发送数据,使用Receive 方法接收传入的数据文报。
TcpListener:
TcpListener 类提供一些简单方法,用于在阻止同步模式下侦听和接受传入连接请求。可使用 TcpClient 或 Socket 来连接 TcpListener。可使用 IPEndPoint、本地 IP 地址及端口号或者仅使用端口号,来创建 TcpListener。可以将本地 IP 地址指定为Any,将本地端口号指定为 0(如果希望基础服务提供程序为您分配这些值)。如果选择这样做,可在连接套接字后使用LocalEndpoint 属性来标识已指定的信息。
Start方法用来开始侦听传入的连接请求。Start将对传入连接进行排队,直至调用 Stop 方法或它已经完成 MaxConnections 排队为止。可使用 AcceptSocket 或 AcceptTcpClient 从传入连接请求队列提取连接。这两种方法将阻止。如果要避免阻止,可首先使用 Pending 方法来确定队列中是否有可用的连接请求。
调用 Stop 方法来关闭 TcpListener。
TcpClient:
TcpClient类提供了一些简单的方法,用于在同步阻止模式下通过网络来连接、发送和接收流数据。为使 TcpClient 连接并交换数据,使用 TCP ProtocolType 创建的 TcpListener 或 Socket 必须侦听是否有传入的连接请求。可以使用下面两种方法之一连接到该侦听器:
(1)创建一个 TcpClient,并调用三个可用的 Connect 方法之一。
(2)使用远程主机的主机名和端口号创建 TcpClient。此构造函数将自动尝试一个连接。
给继承者的说明要发送和接收数据,请使用 GetStream 方法来获取一个 NetworkStream。调用 NetworkStream 的 Write 和Read 方法与远程主机之间发送和接收数据。使用 Close 方法释放与 TcpClient 关联的所有资源。
namespace c_sharp_practice10_tcplistener
{
class Program
{
static void Main(string[] args)
{
// 1TcpListener对Socket进行了一层封装,会自己创建Socket对象
TcpListener listener = new TcpListener(IPAddress.Parse("192.168.1.101"), 7788);
// 2开始监听
listener.Start();
// 3等待客户端连接
TcpClient client = listener.AcceptTcpClient();
// 4与客户端通信
NetworkStream stream = client.GetStream();//得到一个网络流,从这个网络流里可以取得客户端发来的数 据
byte[] data = new byte[1024];
while (true)
{
int length = stream.Read(data, 0, 1024);
string message = Encoding.UTF8.GetString(data, 0, length);
Console.WriteLine("服务器端接收到:" + message);
}
stream.Close();
client.Close();
listener.Stop();
Console.ReadKey();
}
}
}
namespace c_sharp_practice10_tcpclient
{
class Program
{
static void Main(string[] args)
{
//创建TcpClient的时候,就会与server建立连接
TcpClient client = new TcpClient("192.168.1.101", 7788);
NetworkStream stream = client.GetStream();//通过网络流进行数据的交换
while (true)
{
string message = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(message);
stream.Write(data, 0, data.Length);
}
stream.Close();
client.Close();
Console.ReadKey();
}
}
}
13.1.5 UdpClient
UdpClient类提供了一些简单的方法,用于在阻止同步模式下发送和接收无连接 UDP 数据报。因为 UDP 是无连接传输协议,所以不需要在发送和接收数据前建立远程主机连接。但可以选择使用下面两种方法之一来建立默认远程主机:
使用远程主机名和端口号作为参数创建 UdpClient 类的实例。
创建 UdpClient 类的实例,然后调用 Connect 方法。
可以使用在 UdpClient 中提供的任何一种发送方法将数据发送到远程设备。使用 Receive 方法可以从远程主机接收数据。
UdpClient 方法还允许发送和接收多路广播数据报。使用 JoinMulticastGroup 方法可以将 UdpClient预订给多路广播组。使用DropMulticastGroup方法可以从多路广播组中取消对 UdpClient的预订。
namespace c_sharp_practice10_udpclient
{
class Program
{
static void Main(string[] args)
{
//创建UdpClient,绑定IP:Port
UdpClient udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse("192.168.1.101"), 7788));
//接收数据
while (true)
{
IPEndPoint point = new IPEndPoint(IPAddress.Any, 0);
byte[] data = udpClient.Receive(ref point);//通过point确定数据来自哪个IP:Port
string message = Encoding.UTF8.GetString(data);
Console.WriteLine("接收到数据" + message);
}
udpClient.Close();
Console.ReadKey();
}
}
}
namespace c_sharp_practice10_udpclient
{
class Program
{
static void Main(string[] args)
{
//创建UdpClient对象
UdpClient client = new UdpClient();
while (true)
{
string message = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(message);
client.Send(data, data.Length, new IPEndPoint(IPAddress.Parse("192.168.1.101"), 7788));
}
client.Close();
Console.ReadKey();
}
}
}