?
?
?
1、什么是Socket?
Socket套接字是应用层(用户进程)与运输层(tcp和udp)之间的软件抽象层,可以用来实现不同计算机之间的通信。具有实时性、
长连接的特点,在设计模式中相当于门面模式。
2、Socket的使用流程?
见图
3、什么是Tcp/Udp?
就是一个运输层传输协议
4、Tcp和Udp的区别?
1、Tcp是面向连接的协议,也就是在发送数据前,必须和对方建立可靠的连接。
一个Tcp的链接必须经过三次握手才能连接,四次挥手关闭连接。
2、Udp是一个非连接的协议,传输数据之前双方不建立连接。当它想传送时就简单的抓取来自
应用程序的数据,并尽可能快的把它扔到网络上。
3、Tcp保证数据的正确性,Udp可能丢包。
4、Tcp保证数据的传输顺序,UdP不保证。
5、Tcp的三次握手和四次挥手?
三次握手:
客户端向服务器发送连接请求。
服务器收到消息响应,向客户端进行确认。
客户端在向服务器表示确认。
这个过程是在客户端的connect()和服务器的Accept()函数中的。
四次挥手:
客户端发送中断请求。
服务器得到消息,继续处理未处理完的资源。
服务器处理完毕通知客户端可以断开。
客户端收到确认信息,表示断开。
6、Tcp粘包分包问题的出现?如何解决?
粘包:
所谓粘包就是通讯的一端一次性连续发送多条数据包,tcp协议会将多个数据包打包成一个报文发
送出去。
产生原因:
1、要发送的数据小于Tcp发送缓冲区的大小。
2、数据发送过快,导致数据包堆积多个数据后才一次性发送。
解决方法:
1、客户端在发送数据包的时候采用包头+包体的方式,包头表示上包体的长度,服务器接收后
根据长度来解析包体。
2、将每个数据包封装成固定的长度,不够的用0填充。
3、可以在数据包中设置特殊符号,接收端通过特殊符号来拆分
拆包:
就是通讯的一端发送的数据包超过tcp报文所能传输的最大值,就会将一个数据包拆分成多个数据
包分开传输。
产生原因:
1、传输数据大于tcp传输最大值
解决方法:
1、也是传输数据包的时候加上数据的长度,,服务端接收到数据后,先读取四个字节的int
值,然后再读取对应长度的字节数。
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using Frame.Utility;
namespace NetFrame
{
public class NetFrame
{
public class ServerFram : Singleton<ServerFram>
{
//服务端套接字对象
public Socket serverSocket;
//服务端回调
private Action<string> serverCallback;
//服务端消息缓存
private byte[] serverBuffer;
public ServerFram()
{
}
/// <summary>
/// 启动服务器
/// </summary>
/// <param name="servrCallback"></param>
public void StartServer(Action<string> servrCallback)
{
//接收回调
this.serverCallback = servrCallback;
//实例化套接字
//1.地址族 ipv4地址
//2.套接字类型 读写流
//3.协议类型 TCP
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverCallback("服务器套接字创建成功");
serverCallback("da");
//创建网络节点
EndPoint endPoint = new IPEndPoint(IPAddress.Any, 23456);
//绑定网络节点
serverSocket.Bind(endPoint);
serverCallback("服务器套节点绑定成功");
//设置监听队列长度
serverSocket.Listen(30);
//开启异步接受
serverSocket.BeginAccept(ServerAccept, serverSocket);
}
/// <summary>
/// 服务器接收信息
/// </summary>
/// <param name="ar"></param>
private void ServerAccept(IAsyncResult ar)
{
//获取到结果中的套接字对象
Socket workingSocket = ar.AsyncState as Socket;
//完成接受
Socket acceptedSocket = workingSocket.EndAccept(ar);
serverCallback("完成客户端连接:" + acceptedSocket.RemoteEndPoint);
serverCallback("开启异步接受消息");
serverBuffer = new byte[1024];
//直接开始异步接收消息
acceptedSocket.BeginReceive(
serverBuffer, //1.消息缓存
0, //2.起始位
serverBuffer.Length, //3.读取长度
SocketFlags.None, //4.不做特殊标记
ServerReceive, //5.收到信息的回调
acceptedSocket); //6.状态
//开启尾递归
serverSocket.BeginAccept(ServerAccept, serverSocket);
}
/// <summary>
/// 收到信息
/// </summary>
/// <param name="ar"></param>
private void ServerReceive(IAsyncResult ar)
{
Socket workingSocket = ar.AsyncState as Socket;
//结束接收,返回长度
int count = workingSocket.EndReceive(ar);
serverCallback($"接收到{count}个字节的消息");
//将消息编码形成字符串
string msg = UTF8Encoding.UTF8.GetString(serverBuffer);
serverCallback($"收到的消息是{msg}");
//清空缓存
serverBuffer = new byte[1024];
//尾递归
workingSocket.BeginReceive(serverBuffer, 0, serverBuffer.Length, SocketFlags.None,
ServerReceive, workingSocket);
}
/// <summary>
/// 关闭服务器
/// </summary>
public void ServerShutDown()
{
//关闭套接字
try
{
serverSocket.Shutdown(SocketShutdown.Both);
}
finally
{
serverSocket.Close();
}
}
}
public class ClientFrame : Singleton<ClientFrame>
{
private ClientFrame()
{
}
private Socket clientSocket;
private byte[] clientBuffer;
private Action<string> clientCallback;
/// <summary>
/// 连接服务器
/// </summary>
/// <param name="serverIp"></param>
/// <param name="serverPort"></param>
/// <param name="clientCallBack"></param>
public void ConnectToServer(string serverIp, int serverPort, Action<string> clientCallBack)
{
//赋值回调
this.clientCallback = clientCallBack;
//实例化客户端套接字
clientSocket = new Socket(
AddressFamily.InterNetwork, //地址族
SocketType. Stream,//读写流
ProtocolType.Tcp); //TCP
clientCallback("客户端套接字创建成功!");
//连接服务器
clientSocket.BeginConnect(IPAddress.Parse(serverIp), serverPort,
ClientConnect, clientCallBack);
}
/// <summary>
/// 客户端连接
/// </summary>
/// <param name="ar"></param>
private void ClientConnect(IAsyncResult ar)
{
Socket workingSocket = ar.AsyncState as Socket;
//连接成功
workingSocket.EndConnect(ar);
clientCallback("连接服务器成功!");
}
/// <summary>
/// 客户端发送信息
/// </summary>
/// <param name="msg"></param>
public void ClientSendMsg(string msg)
{
//将字符串消息转换到buffer
clientBuffer = UTF8Encoding.UTF8.GetBytes(msg);
clientCallback("客户端正在发送消息:" + msg);
//异步发送
clientSocket.BeginSend(clientBuffer, 0, clientBuffer.Length,
SocketFlags.None, ClientSend, clientSocket);
}
/// <summary>
/// 客户端发送
/// </summary>
/// <param name="ar"></param>
private void ClientSend(IAsyncResult ar)
{
Socket workingSocket = ar.AsyncState as Socket;
int count = workingSocket.EndSend(ar);
clientCallback($"客户端发送消息成功「{count}」");
}
/// <summary>
/// 关闭客户端
/// </summary>
public void CloseClient()
{
try
{
clientSocket.Shutdown(SocketShutdown.Both);
}
finally
{
clientSocket.Close();
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NetFrameTest : MonoBehaviour
{
public GUISkin skin;
//0:未联网,1:服务器端,2:客户端
private int netState = 0;
private void OnGUI()
{
GUI.skin = skin;
switch (netState)
{
case 0:
OriginState();
break;
case 1:
ServerState();
break;
case 2:
ClientState();
break;
}
}
private string serverLog,clientLog;
private string ipInput = "127.0.0.1", portInput = "23456";
/// <summary>
/// 初始状态
/// </summary>
private void OriginState()
{
GUILayout.Label("服务器端IP:");
ipInput = GUILayout.TextField(ipInput);
GUILayout.Label("服务器端Port:");
portInput = GUILayout.TextField(portInput);
if (GUILayout.Button("创建服务器"))
{
NetFrame.NetFrame.ServerFram.
GetInstance().StartServer(
(msg) =>
{
serverLog += msg + "\n";
});
netState = 1;
}
if (GUILayout.Button("连接服务器"))
{
NetFrame.NetFrame.ClientFrame.
GetInstance().ConnectToServer(
ipInput,int.Parse(portInput),
(msg) =>
{
clientLog += msg + "\n";
});
netState = 2;
}
}
Vector2 scrollViewValue = Vector2.zero;
/// <summary>
/// 服务状态
/// </summary>
private void ServerState()
{
scrollViewValue = GUILayout.BeginScrollView(scrollViewValue);
GUILayout.Label(serverLog);
GUILayout.EndScrollView();
}
private string clientSendMsg = "";
/// <summary>
/// 客户端状态
/// </summary>
private void ClientState()
{
GUILayout.Label("请输入要发送的消息:");
clientSendMsg = GUILayout.TextField(clientSendMsg);
if (GUILayout.Button("发送"))
{
NetFrame.NetFrame.ClientFrame.GetInstance().
ClientSendMsg(clientSendMsg);
}
scrollViewValue = GUILayout.BeginScrollView(scrollViewValue);
GUILayout.Label(clientLog);
GUILayout.EndScrollView();
}
private void OnApplicationQuit()
{
if(netState == 1)
NetFrame.NetFrame.ServerFram.GetInstance().ServerShutDown();
else if(netState == 2)
NetFrame.NetFrame.ClientFrame.GetInstance().CloseClient();
}
}
|