1.一些概念
2. IP地址、端口号、mac地址
3.数据通信
4.OSI模型 !
5.TCP/IP协议
6.网络通信方案
7.IP地址和通信目标
IPAddress 和 IPEndPoint类
//默认主机ip是127.0.0.1
//Ip信息
IPAddress ipaddress = IPAddress.Parse("192.125.225.10");
//通信目标(ip+端口号)
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("192.125.225.10"), 8800);
8.域名解析
域名(类似 www.baidu.com这种形式)解析成ip地址等信息
IPHostEntry类 一般当作返回值来使用
Dns类 域名解析方法
public void Test()
{
//域名解析
//IPHostEntry 返回值
//Dns类
//获取本地主机名
//print(Dns.GetHostName());
//获取域名指定的IP信息
IPHostEntry iphost = Dns.GetHostEntry("www.baidu.com");
//ip地址
for (int i = 0; i < iphost.AddressList.Length; i++)
{
print(iphost.AddressList[i]);
}
//主机别名
for (int i = 0; i < iphost.Aliases.Length; i++)
{
print(iphost.Aliases[i]);
}
//DNS
print("DNS服务器名称:" + iphost.HostName);
//异步
LoadAsynd();
}
public async void LoadAsynd()
{
Task<IPHostEntry>tk= Dns.GetHostEntryAsync("www.baidu.com");
await tk;
//ip地址
for (int i = 0; i < tk.Result.AddressList.Length; i++)
{
print(tk.Result.AddressList[i]);
}
//主机别名
for (int i = 0; i < tk.Result.Aliases.Length; i++)
{
print(tk.Result.Aliases[i]);
}
//DNS
print("DNS服务器名称:" + tk.Result.HostName);
}
9.Socket套接字
tcp和udp的通信套接字
10.服务端
//创建tcp套接字
Socket sockettcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
//绑定本地地址
sockettcp.Bind(endPoint);
}
catch (Exception e)
{
Console.WriteLine("创建套接字失败:"+e.Message);
}
//Listen监听
sockettcp.Listen(10);
Console.WriteLine("监听结束");
//Accept等待客户端连接 返回新的套接字
//这里要等待客户端连接之后才能执行之后的代码
Socket socketClient= sockettcp.Accept();
Console.WriteLine("等待连接");
//Send Receieve方法 收发数据
socketClient.Send(Encoding.UTF8.GetBytes("欢迎连接服务端"));
byte[] result = new byte[10];
int receieveNum = socketClient.Receive(result);
Console.WriteLine("接收到了来自{0}的消息{1}", socketClient.RemoteEndPoint.ToString(),
Encoding.UTF8.GetString(result, 0, receieveNum));
//释放连接
socketClient.Shutdown(SocketShutdown.Both);
//关闭套接字
socketClient.Close();
11.客户端
//Socket套接字
Socket socketTcp=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
//写的是要连接的服务端的ip和端口号
IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
try
{
//Connect连接
socketTcp.Connect(iPEndPoint);
}
catch (SocketException e)
{
if(e.ErrorCode==10061)
{
Console.WriteLine("服务端拒绝了连接");
}
else
{
Console.WriteLine("连接服务端失败 "+e.ErrorCode);
}
}
//Send 发送消息
socketTcp.Send(Encoding.UTF8.GetBytes("你好,这里是cl的客户端"));
//Receive 发送消息
byte[] result = new byte[1024];
int num=socketTcp.Receive(result);
print("收到服务端发来的消息"+Encoding.UTF8.GetString(result,0,num));
socketTcp.Shutdown(SocketShutdown.Both);
socketTcp.Close();
12.区分消息类型
自定义的数据结构在客户端和服务端传递
定义一个规则,前面四个字节表示ID,根据ID来区分不同的消息类型。
BaseMgs类
public class BaseMsg : BaseData
{
public override int GetBytesNum()
{
throw new System.NotImplementedException();
}
public override int Reading(byte[] bytes, int beginIndex = 0)
{
throw new System.NotImplementedException();
}
public override byte[] Writing()
{
throw new System.NotImplementedException();
}
//以上都是一些快捷的读取和写入方法
public virtual int GetID()
{
return 0;
}
}
PlayerMsg数据类 继承了BaseMgs类
public class PlayerMsg : BaseMsg
{
public int playerID;
public playData playData;
public override int GetBytesNum()
{
return 4//消息id长度
+4//playerID
+playData.GetBytesNum();
}
public override int Reading(byte[] bytes, int beginIndex = 0)
{
int index = beginIndex;
playerID=ReadInt(bytes,ref index);
playData=ReadData<playData>(bytes,ref index);
return index-beginIndex;
}
public override byte[] Writing()
{
int index = 0;
byte[] bytes = new byte[GetBytesNum()];
WriteInt(bytes,GetID(),ref index);
WriteInt(bytes, playerID, ref index);
WriteData(bytes, playData, ref index);
return bytes;
}
public override int GetID()
{
return 1001;
}
}
服务端处理
客户端处理
13.分包 粘包
//处理接受消息 分包、黏包问题的方法
private void HandleReceiveMsg(byte[] receiveBytes, int receiveNum)
{
int msgID = 0;
int msgLength = 0;
int nowIndex = 0;
//收到消息时 应该看看 之前有没有缓存的 如果有的话 我们直接拼接到后面
receiveBytes.CopyTo(cacheBytes, cacheNum);
cacheNum += receiveNum;
while (true)
{
//每次将长度设置为-1 是避免上一次解析的数据 影响这一次的判断
msgLength = -1;
//处理解析一条消息
if (cacheNum - nowIndex >= 8)
{
//解析ID
msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
//解析长度
msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
}
if (cacheNum - nowIndex >= msgLength && msgLength != -1)
{
//解析消息体
BaseMsg baseMsg = null;
switch (msgID)
{
case 1001:
baseMsg = new PlayerMsg();
baseMsg.Reading(cacheBytes, nowIndex);
break;
case 1003:
baseMsg = new QuitMsg();
//由于该消息没有消息体 所以都不用反序列化
break;
case 999:
baseMsg = new HeartMsg();
//由于该消息没有消息体 所以都不用反序列化
break;
}
if (baseMsg != null)
ThreadPool.QueueUserWorkItem(MsgHandle, baseMsg);
nowIndex += msgLength;
if (nowIndex == cacheNum)
{
cacheNum = 0;
break;
}
}
else
{
//如果不满足 证明有分包
//那么我们需要把当前收到的内容 记录下来
//有待下次接受到消息后 再做处理
//receiveBytes.CopyTo(cacheBytes, 0);
//cacheNum = receiveNum;
//如果进行了 id和长度的解析 但是 没有成功解析消息体 那么我们需要减去nowIndex移动的位置
if (msgLength != -1)
nowIndex -= 8;
//就是把剩余没有解析的字节数组内容 移到前面来 用于缓存下次继续解析
Array.Copy(cacheBytes, nowIndex, cacheBytes, 0, cacheNum - nowIndex);
cacheNum = cacheNum - nowIndex;
break;
}
}
}
14.客户端主动断开连接
定义了一个断开消息的类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuitMsg : BaseMsg
{
//消息类型和消息id
public override int GetBytesNum()
{
return 8;
}
public override int Reading(byte[] bytes, int beginIndex = 0)
{
return 0;
}
public override byte[] Writing()
{
int index = 0;
byte[] bytes = new byte[GetBytesNum()];
WriteInt(bytes, GetID(), ref index);
WriteInt(bytes, 0, ref index);
return bytes;
}
public override int GetID()
{
return 1003;
}
}
15.实现心跳消息
所谓心跳消息就是在长连接中,客户端和服务端之间定期发送的一种特殊的数据包。通知对方我还在线,确保长连接的有效性。
客户端
服务端
实现一个计时,系统时间减去上一次收到心跳消息的时间大于多少就算客户端已经断开连接了。这个判断在一个循环里去检测。
16.服务端(异步通信--begin)
三个脚本
第一个是服务端接收到的客户端
主程序
服务端
17.Tcp客户端 服务端总结
18.Tcp,Udp客户端 服务端的简单实现
客户端
void Start()
{
//创建套接字
Socket socket=new Socket(AddressFamily.InterNetwork,SocketType.Dgram, ProtocolType.Udp);
//绑定地址
IPEndPoint ippoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
socket.Bind(ippoint);
//发消息 SendTo
IPEndPoint Remoteippoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
socket.SendTo(Encoding.UTF8.GetBytes("cl的udp客户端"), Remoteippoint);
//接消息 ReceiveFrom
//传入的是一个ref
//外部得到从哪接收到的消息赋值给ref 变量
//通过这个ref 变量 得到ip和端口等信息
byte[] bytes = new byte[512];
EndPoint refPoint=new IPEndPoint(IPAddress.Any, 0);
int receiveNum= socket.ReceiveFrom(bytes, ref refPoint);
print("收到ip:" + (refPoint as IPEndPoint).Address + "端口:" + (refPoint as IPEndPoint).Port
+ "发来的:" + Encoding.UTF8.GetString(bytes, 0, receiveNum));
//关闭
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
服务端
//创建套接字
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//绑定地址
IPEndPoint ippoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
socket.Bind(ippoint);
Console.WriteLine("服务器开启成功");
//接消息
//先接了消息 就能得到发送消息的那边的ip和端口
byte[] bytes = new byte[512];
EndPoint refPoint = new IPEndPoint(IPAddress.Any, 0);
int receiveNum = socket.ReceiveFrom(bytes, ref refPoint);
Console.WriteLine("收到ip:" + (refPoint as IPEndPoint).Address + "端口:" + (refPoint as IPEndPoint).Port
+ "发来的:" + Encoding.UTF8.GetString(bytes, 0, receiveNum));
//发消息
//可以直接使用发送消息的那边的ip和端口 refPoint
socket.SendTo(Encoding.UTF8.GetBytes("服务器的一条消息"), refPoint);
//关闭
socket.Shutdown(SocketShutdown.Both);
socket.Close();
19.UDP服务器综合&&客户端综合
服务器:
客户端网络:
20.Udp的异步 接收和发送消息
21.FTP
FTP操作相关的三个类
22.FTP的上传和下载
23.认识Http
24.HTTP下载数据
Head操作:
Get操作:
....下面就是把服务器的资源写入到创建的空资源中去
25.WWW类
可以通过该类来下载和上传一些数据,主要配合http,ftp,本地加载这三种一起使用。与协程一起使用。
26.UnityWebRequest类
得到数据和上传数据一般都是配合协程食用。
得到数据
上传数据
27.UnityWebRequest类的高级操作
自定义类继承 DownloadHandlerScript
public class DivClassSubHandleScript:DownloadHandlerScript
{
private string savepath;
private byte[] cacheBytes;
private int cacheIndex;
public DivClassSubHandleScript() : base() { }
public DivClassSubHandleScript(byte[] data) : base(data) { }
public DivClassSubHandleScript(string path) : base()
{
savepath = path;
}
protected override byte[] GetData()
{
return cacheBytes;
}
//从网络接收到数据之后 每一帧都会自动调用
protected override bool ReceiveData(byte[] data, int dataLength)
{
print("接收的字节数组的长度:"+data.Length);
print("参数dataLength:" + dataLength);
data.CopyTo(cacheBytes, cacheIndex);
cacheIndex += dataLength;
return true;
}
//从网络接收到数据之后 每一帧都会自动调用
protected override void ReceiveContentLengthHeader(ulong contentLength)
{
print("收到的数据长度:" + contentLength);
cacheBytes= new byte[contentLength];
}
//从网络接收到数据之后 每一帧都会自动调用
protected override void CompleteContent()
{
print("接收完成");
File.WriteAllBytes(savepath, cacheBytes);
}
}
高级操作--获取数据总结
28.初识Protobuf
Protobuf规则:
1.版本号:syntax="proto3" 不写默认proto2
2.命名空间:package +名字
3.消息类:message 类名{
字段声明
}
4.成员类型和唯一编号(每个变量都是唯一的,用于序列化和反序列化。注意两个不同的消息,里面的编码是不相关的)、变长编码(会根据数字的大小,来使用对应的字节数来存储):
5.特殊标识
数组 repeated、字典 map
6.默认值和c#差不多、允许嵌套
7.保留字段 reserved(保留唯一编号或者变量(需要"") 等等)
8.导入定义(使用另一个文件或者同文件的Protobuf文件)
import "文件路径"
29.Protobuf生成cs脚本文件
将生成的cs文件导入unity中,在任意脚本引用Protobuf文件中的命名空间即可正常使用配置的消息类,变量等信息了。
30.大小端模式、影响、相互转换
为什么有大小端模式?
计算机电路先处理低位字节的效率比较高。大端字节序比较符合我们的读写习惯。因此除了计算机的内部处理是小端字节序,其他场合几乎都是大端字节序,比如网络传输和文件传输。
影响?不同语言在网络通讯时前后端语言大小端不一致
大小端的相互转换
通用的转换(就是翻转字节数组)用的比较多(支持的类型多)。
31.加密
异或加密
32.基于自定义收发消息的优化
工具生成Handler
//生成消息处理类
string msgHandlestr = $"namespace {namespaceStr}\r\n" + "\t\t{\r\n" + $"\t\t public class {classNameStr}Handle : BaseHandle\r\n"
+ "\t\t{\r\n" + "\t\t\t public override void HandleMsg()\r\n" + "\t\t\t{\r\n"
+ $"\t\t\t{classNameStr} msg = message as {classNameStr} ;\r\n" + "\t\t\t}\r\n" + "\t\t}\r\n" + "\t\t}\r\n";
string handlepath = SAVE_PATH + namespaceStr + "/Handle/";
if (File.Exists(handlepath))
{
continue;
}
File.WriteAllText(path + classNameStr + "Handle.cs", msgHandlestr);
33.状态同步模式和帧同步模式
34.总结
以上教程源自唐老狮