目录
socket(套接字)
关于Sokect的含义,还有Http等,可以看这篇文章Socket使用大全、Unity高级-Socket
tcp
- 基于tcp协议的socket通讯类似于B/S架构,面向连接,但不同的是服务器端可以向客户端主动推送消息
- 使用tcp协议通讯需要具备以下几个条件:
- 建立一个套接字(socket)
- 绑定服务器端ip地址以及端口号-服务器端
- 利用Listen()方法开启监听-服务器端
- 利用Accept()方法尝试与客户端建立一个连接-服务器端
- 利用Connect方法()与服务器建立连接-客户端
- 利用Send()方法向建立连接的主机发送消息
- 利用Receive()方法接收来自建立连接的主机的消息(可靠连接)
udp
- 基于udp协议是无连接模式通讯,占用资源少,响应速度快,延时低。至于可靠性,可通过应用层的控制来满足(不可靠连接)
- 使用udp协议通讯的步骤:
- 建立一个套接字(socket)
- 绑定服务器端ip地址及端口号-服务器端
- 通过SendTo()方法向指定主机发送消息(需提供主机ip地址及端口号)
- 通过ReciveFrom()方法接收指定主机发送的消息(需提供主机ip地址及端口号)
tcp和udp的区别
TCP协议和UDP协议连接过程的区别:
- 基于连接和无连接
- 对系统资源的要求(TCP较多,UDP少)
- UDP程序结构简单
- TCP采用流模式,UDP采用数据报模式
- TCP保证数据正确性,UDP可能丢包;TCP保证数据顺序(先发先收),UDP不保证
简单的socket-tcp服务器和客户端
服务器端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets; //添加命名空间
using System.Net; //添加命名空间
namespace _041_socket编程_tcp协议_服务器端
{
class Program
{
static void Main(string[] args)
{
//1.创建socket(socket本身就是一个类)
//AddressFamily.InterNetwork 内网(可省略),SocketType.Stream 通信方式,ProtocolType.Tcp 通信协议
Socket tcpServer = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,ProtocolType.Tcp);
//2.绑定ip跟端口号
//通过IP地址找到计算机,通过端口号找到软件(可以在cmd窗口中输入ipconfig获得)
//端口号0-60000,尽量取大点避免端口被占用
IPAddress ipaddress = new IPAddress(new byte[] { 192,168,0,102});//IP地址(因为IP是由4个255组成,所以使用new byte[])
EndPoint point = new IPEndPoint(ipaddress,7788); //ipendpoint是对ip+端口做了一层封装的类
//向操作系统申请一个可用的ip跟端口号用来做通信
tcpServer.Bind(point);
//3.开始监听(等待客户端连接)
tcpServer.Listen(100); //参数为最大连接数
Console.WriteLine("开始监听");
//暂停当前线程,接收客户端连接,之后进行下面的代码
Socket clientSocket = tcpServer.Accept();
Console.WriteLine("一个客户端连接成功");
//4.向客户端发送数据
//使用返回的socket跟客户端进行通信
string message = "已成功连接客户端";
byte[] data = Encoding.UTF8.GetBytes(message);//将字符串以UTF-8的编码格式转换为字节数组
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("从客户端接收消息:" + message2);
Console.ReadKey();
}
}
}
客户端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace socket编程_tcp协议_客户端
{
class Program
{
static void Main(string[] args)
{
//1.创建socket
Socket tcpClient = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,ProtocolType.Tcp);
//2.与服务器端连接(注意IP地址和端口要一致)
//可以把一个字符串的ip地址转化为ipadress的对象
IPAddress ipaddress = IPAddress.Parse("192.168.0.102");
//ip地址+端口号需与服务器端相同
EndPoint point = new IPEndPoint(ipaddress,7788);
//在客户端发起连接,通过ip和端口号定位一个要连接的服务器端
tcpClient.Connect(point);
//3.接收服务器端的数据
byte[] data = new byte[1024];
//data数组作为容器,用来接收数据,并返回一个表示接收了多少字节的长度
int length = tcpClient.Receive(data);
//从0开始,取length个字节的数据(不然会把所有数据转化)
string message = Encoding.UTF8.GetString(data, 0, length);
Console.WriteLine(message);
///
/// 向服务器端发送消息
///
string message2 = Console.ReadLine();
tcpClient.Send(Encoding.UTF8.GetBytes(message2));
Console.ReadKey();
}
}
}
Unity简易聊天室
NGUI插件的使用
- 下载和导入NGUI
- 首先先去网上下载资源包,注意Unity2018的版本是NGUI v3.12.0,可以从这里下地址
- 这时Unity的菜单栏上会出现NGUI
- 使用
- 选择"Open->Prefabs Toolbar"打开UI菜单栏,有很多封装好的UI界面
- 选中第一个拖到场景中,作为背景,并把视图设置为2D
- 选中"UI Root",右键背景创建一个"Label",作为聊天的窗口
- 调整"Label"的参数
- 最终NGUI界面效果
服务器脚本
Client类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//添加下列命名空间
using System.Net.Sockets;
using System.Threading;
namespace _042_聊天室_socket_tcp服务器端
{
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连接是否断开
//1.clientSocket.Connected == false
//2.另外一种方法判断是否连接(响应时间,判断是否能从客户端接收消息)
if (clientSocket.Poll(10, SelectMode.SelectRead))
{
clientSocket.Close();//关闭链接
break; //跳出一直接收数据的循环,终止线程执行
}
else
{
int length = clientSocket.Receive(data);
string message = Encoding.UTF8.GetString(data, 0, length);
//接收到数据的时候,要把这个数据分发到客户端(相当于广播消息)
//因为每个客户端都要看到这条消息嘛
Program.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; }
}
}
}
Program类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//添加下列命名空间
using System.Net.Sockets;
using System.Net;
namespace _042_聊天室_socket_tcp服务器端
{
class Program
{
//1.创建socket,绑定ip和端口
static List<Client> clientList = new List<Client>();
static void Main(string[] args)
{
Socket tcpServer = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
tcpServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.0.104"), 7788));
//2.开始监听
tcpServer.Listen(100);
Console.WriteLine("server is running....");
//3.创建客户端对象
while (true) //使server能够一直接收到客户端的连接
{
Socket clientSocket = tcpServer.Accept();
Console.WriteLine("a client is connected!");
//把每个与客户端通信的逻辑放到client类里进行处理
Client client = new Client(clientSocket);
clientList.Add(client); //创建一个链表,添加连接的客户端
}
}
/// <summary>
/// 广播消息
/// </summary>
/// <param name="message"></param>
//服务器端接收到消息后对所有客户端进行广播
public static void BroadcastMessage(string message)
{
//保存已经断开的连接的客户端
var notConnectedList = new List<Client>();
foreach (var client in clientList)
{
if (client.Connected)//判断连接是否还在,才发送广播
{
client.SendMessage(message);
}
else//断开的话则保存到notConnectedList中
{
notConnectedList.Add(client);
}
}
foreach (var temp in notConnectedList)
{
clientList.Remove(temp);
}
}
}
}
客户端脚本
-
创建空物体"ChatManager",挂载脚本"ChatManager",实现功能“点击按钮,发送的输入语句”
-
按钮绑定事件
-
ChatManager脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System.Net;
using System.Text;
using UnityEngine.UI;
using System.Threading;
public class ChatManager : MonoBehaviour
{
private Socket clientSocket;
private Thread t;
private byte[] data = new byte[1024]; //数据容器
private string message = ""; //消息容器
public string ipaddress = "192.168.0.104"; //IP地址
public int port = 7788; //端口号
public UIInput textInput; //获取按钮组件
public UILabel chatLabel; //获取聊天窗口
// Start is called before the first frame update
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 SendMessage(string message)
{
clientSocket.Send(Encoding.UTF8.GetBytes(message));
}
//事件-发送输入框的值
public void OnSendButtonClick()//UGUI按钮组件的On Click需要绑定这个事件
{
string value = textInput.value; //获取输入框的值
SendMessage(value);
textInput.text = ""; //发送完清空输入框
}
void OnDestroy() //OnDestroy是Unity的生命周期函数,跟Start()一样默认调用
{
clientSocket.Shutdown(SocketShutdown.Both);//关闭数据的发送和接收
clientSocket.Close(); //断开连接
}
/// <summary>
/// 线程:用来循环接收消息
/// </summary>
void ReceiveMessage()
{
while(true)
{
if (clientSocket.Connected == false)
{
break;
}
else
{
int length = clientSocket.Receive(data);
message = Encoding.UTF8.GetString(data, 0, length);
}
//unity不允许在单独的线程里操作Unity里的组件
//所以只能在Unity的生命周期函数里进行组件控制
//chatLabel.text += "\n" + message;
}
}
}
简单的socket-udp服务器和客户端
服务器端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Net.Sockets;
using System.Net;
namespace _043_socket编程_udp协议_服务器端
{
class Program
{
private static Socket udpServer;
static void Main(string[] args)
{
//1.创建socket
//SocketType.Dgrams以数据包形式发送
udpServer = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram,ProtocolType.Udp);
//2.绑定ip和端口号
//与tcp相比,udp少了监听这一步骤
udpServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.0.104"), 7788));
//3.接收数据
//设置为后台线程,当程序关闭则结束线程
new Thread(ReceiveMessage) { IsBackground=true}.Start();
//udpServer.Close(); //关闭连接
Console.ReadKey();
}
static void ReceiveMessage()
{
while(true) //udp不需要判断是否连接
{
byte[] data = new byte[1024];
//不需要指定具体的ip和端口号,因为是要用来赋值的
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
//ref能够改变传入参数的值,用来存放接收到的ip和端口号
//ReceiveFrom会暂停等待参数的传递,直到接收完才运行下面的代码
int length = udpServer.ReceiveFrom(data, ref remoteEndPoint);
string message = Encoding.UTF8.GetString(data, 0, length);
Console.WriteLine("从ip:" + (remoteEndPoint as IPEndPoint).Address
+ ",端口号:" + (remoteEndPoint as IPEndPoint).Port
+ "接收到了数据:" + message);
}
}
}
}
客户端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace _002_socket编程_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.0.104"), 7788);
string message = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(message);
udpClient.SendTo(data, serverPoint);
}
Console.ReadKey();
}
}
}
TcpClient,TcpListener,UdpClient
-
应用程序可以通过TCPClient、TCPListener和UDPClient类使用 传输控制协议(TCP) 和 用户数据文报协议(UDP) 服务。这些协议类建立在System.Net.Sockets类的基础上,负责数据传送的细节。
(就是说TCPClient、TCPListener和UDPClient是用来简化socket的) -
TCPClient和TCPListener使用NetworkStream类表示网格,使用GetStream方法返回网络流,然后调用该流的Read和Write方法。NetWorkStream不拥有协议类的基础套接字,因此关闭它并不影响套接字
-
UdpClient类使用字节数组保存UDP数据文报。使用Send方法向网络发送数据,使用Receive方法接收传入的数据文报
TcpClient和TcpListener的使用
TcpClient类
TcpClient类提供了一些简单的方法,用于在同步阻止模式下通过网络来连接、发送和接收流数据。为使TcpClient连接并交换数据,使用TCP ProtocolType创建的TcpListener或Socket必须监听是否有传入的连接请求。
可以使用下面两种方法之一连接到该侦听器:
- 创建一个TcpClient,并调用三个可用的Connect方法之一
- 使用远程主机的主机名和端口号创建TcpClient。此构造函数将自动创建一个连接
给继承者说明要发送和接收数据,请使用GetStream方法来获取一个NetworkStream。调用NetworkStream的Write和Read方法与远程主机之间发送和接收数据,使用Close方法释放与TcpClient关联的所有资源
TcpListener类
- TcpListener类提供一些简单方法,用于在阻止同步模式下侦听和接收传入连接请求
- 可使用TcpClient或Socket来连接TcpListener。
- 可使用IPEndPoint、本地IP地址及端口号,或者仅使用端口号来创建TcpListener。
- 可以将本地IP地址指定为Any,将本地端口号指定为0(如果希望基础服务提供程序为您分配这些值),如果您选择这样做,可在连接套接字后使用LocalEndpoint属性来标识已指定的信息
- Start方法用来开始侦听传入的连接请求。Start将对传入连接进行排队,直至您调用Stop方法或它已经完成MaxConnections排队为止。
- 可使用AcceptSocket或AcceptTcpClient从传入连接请求队列提取连接。
- 如果要避免阻止,可首先使用Pending方法来确定队列中是否有可用的连接请求
- 调用Stop方法来关闭TcpListener
服务器端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace _044_tcplistener
{
class Program
{
static void Main(string[] args)
{
//1.TcpListener对socket进行了一层封装,这个类里面自己会去创建socket对象
TcpListener tcpListener = new TcpListener(IPAddress.Parse("192.168.0.104"),7788);
//2.开始监听
tcpListener.Start();
//3.等待客户端进行连接
TcpClient client = tcpListener.AcceptTcpClient();
//4.取得客户端发来的数据
//获得一个网络流,从这个网络流可以取得客户端发送过来的数据
NetworkStream stream = client.GetStream();
byte[] data = new byte[1024];//创建一个数据容器,用来装载数据
while (true)
{
//00表示从数组的第几个索引开始存放数据
//1024表示最大可读取的字节数
int length = stream.Read(data, 0, 1024);
string message = Encoding.UTF8.GetString(data, 0, length);
Console.WriteLine("获得的消息为:" + message);
}
//收到消息后,释放程序
stream.Close();
client.Close();
tcpListener.Stop(); //停止监听
Console.ReadKey();
}
}
}
客户端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace _003_tcpclient
{
class Program
{
static void Main(string[] args)
{
//1.当我们创建tcpclient对象的时候,会自动跟server建立连接
TcpClient client = new TcpClient("192.168.0.104",7788);
//2.通过网络流进行数据的交换
NetworkStream stream = client.GetStream();
//3.发送数据
while (true)
{
string message = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(message);
//read用来读取数据(接收数据),write用来写入数据(发送数据)
stream.Write(data, 0, data.Length);
}
stream.Close();
client.Close();
Console.ReadKey();
}
}
}
UdpClient
UdpClient类提供了一些简单的方法,用于在阻止同步模式下发送和接收无连接UDP数据报。因为UDP是无连接传输协议,所以不需要在发送和接收数据前建立远程主机连接。
可以选择使用下面两种方法之一来建立默认远程主机:
- 使用远程主机名和端口号作为参数创建UdpClient类的实例
- 创建UdpClient类的实例,然后调用Connect方法
- 可以使用在UdpClient中提供的任何一种发送方法将数据发送到远程设备,使用Receive方法可以从远程主机接收数据
- UdpClient方法还允许发送和接收多路广播数据报。使用JoinMulticastGroup方法可以将UdpClient预订给多路广播组,使用DropMulticastGroup方法可以从多路广播中取消对UdpClient的预订
服务端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace _045_udpclient
{
class Program
{
static void Main(string[] args)
{
//1.创建udpclient,绑定ip和端口号
UdpClient udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse("192.168.0.105"), 7788));
//2.创建一个IPEndPoint接收数据
IPEndPoint point = new IPEndPoint(IPAddress.Any,0);
while (true)
{
byte[] data = udpClient.Receive(ref point);
string message = Encoding.UTF8.GetString(data);
Console.WriteLine("收到了消息:" + message);
}
udpClient.Close();
Console.ReadKey();
}
}
}
客户端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace _004_udpclient
{
class Program
{
static void Main(string[] args)
{
//1.创建udpclient对象
UdpClient client = new UdpClient();
//2.发送数据
while (true)
{
string message = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(message);
client.Send(data, data.Length, new IPEndPoint(IPAddress.Parse("192.168.0.105"), 7788));
}
client.Close();
Console.ReadKey();
}
}
}