Tcp同步连接
Tcp同步连接介绍
Tcp协议作为稳定协议,在消息发送前必须完成客户端连接,且客户端连接在Tcp协议中只能是一对一的,即如果有ABC三个连接,那个A连接与B连接如果相互连接,则A与C之间则无法互相通信,只能由A接受到消息时创建出额外的D连接,然后由D与C相互通信
同步作为与异步区分的概念,同步即线程执行到发送或等待接受消息的指令时会进入阻塞状态,即暂停执行,直到接收到消息时,线程才会再次开始工作
Tcp客户端
tcp客户端介绍
客户端的概念是作为客机向主机连接的通信通道,客户端不需要绑定IP和端口,客户端在创建之后直接向服务器发送连接申请,成功连接后即可作为单向的消息的收发通道,客户端的代码中不需要存在自己的IP及端口号,只需要存在需要连接的主机和端口号即可
创建Tcp客户端
cs
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
代码的功能为创建一个Tcp协议下的客户端
构造函数的第一个参数为采用IPv4的方式进行网络传输,保持不变即可,偶尔可用IPv6方式
构造函数的第二个参数为采用流的方式传递数据,保持不变即可
构造函数的第三个参数为使用tcp网络传输协议,保持不变即可
Tcp客户端连接服务器
cs
socket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345));
代码的功能为连接服务器主机,这个方法只需要在客户端执行,服务器中不需要使用Connect连接功能,需要注意的是连接的Socket必须要是服务器中已经调用Bind和Listen的Socket
传入的参数为IPEndPoint,声明IpEndPoint则需要传入IpAddress和端口号,IpAddress代表Ip地址,IpAddress.Parse(string)方法为获取指定字符串所指向的IP地址,连接时必须指定特定IP,不能使用IpAddress,Any或其他,IpAddress和开放端口号组成IpEndPoint
Tcp客户端接收消息
cs
byte [] message = new byte[1024];
socket.Receive(message);
string str = Encoding.UTF8.GetString(message);
连接服务器完成后,即可调用Receive方法开始接受,需要注意的是执行的Receive语句时,无论是主线程或是分支线程,都会开始阻塞(暂停执行),直到收到服务器消息为止,所以Receive语句需要写到线程当中,然后在主线程中开启分支线程,在分支线程中执行Receive语句
Receive方法中的参数为byte数组,使用前需要先声明并赋予任意初始值,在Receive方法执行完后byte数组中将包含从服务器中接受到的数据流,需要通过下面的Encoding.UTF8.GetString方法将byte数组传入并返回由数据流转换好的字符串
Tcp客户端发送消息
cs
socket.Send(Encoding.UTF8.GetBytes("客户端发送内容"));
Send方法的参数类型为byte数组,功能是将参数中的byte数组数据流发送到服务器,Encoding.UTF8.GetBytes方法为将传入的String类型的参数专函为byte数组并返回,在这里直接将方法的返回值传入的Send方法的参数中
Tcp服务器
Tcp服务器介绍
tcp服务器并不需要特殊的系统或特殊的硬件
服务器相当于不主动发送连接请求而被动接受连接请求的主机端
与客户端不同的是客户端向服务器申请连接,而服务器则是被动等待客户端的申请
服务器必须要在声明时绑定固定的Ip地址和端口号,这样才能被客户端寻找到
服务器在接收到新的连接申请时,并不会由服务器与其进行连接,而是会生成一个本地客户端与访问服务器的客户端进行一对一连接,客户端发送消息时将发送到服务器生成的本地客户端上
创建Tcp服务器
cs
Socket mainSocket;
mainSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
mainSocket.Bind(new IPEndPoint(IPAddress.Any,12345));
tcp服务器的声明与客户端的声明相同,但是区别于客户端的方式是服务器必须调用Bind方法绑定固定的IP地址和端口,服务器在声明时IP地址可以传入IPAddress.Any,等同于传入"127.0.0.1"或者自己的Ipv4地址,12345代表服务器部署的端口
Tcp服务器开启监听连接请求
cs
mainSocket.Listen(10);
服务器必须调用Listen代码之后,才能收到来自客户端的连接申请,Listen方法的参数为同时最多可连接到服务器的客户端数量,超出数量的客户端发送连接请求时将返回连接失败
Tcp监听连接请求并生成本地客户端
cs
Socket socket = mainSocket.Accept();
服务器调用Accept方法后,无论是主线程还是分支线程,都将进入阻塞状态(暂停执行),直到收到连接请求后才会继续执行,所以需要将Accept方法写入到分支线程中,然后在主线程开启的分支线程中调用Accept方法.
Accept方法调用后,服务器将为接下来第一个连接进来的客户端生成一个一对一专属的通信客户端作为方法的返回值返回,客户端发送消息时将会由通信客户端进行通信,与服务器将无关
所以通过Accept生成的客户端需要利用List或Dictionary等方式储存起来,供发送信息时使用
Accept方法接受到连接申请并创建本地客户端后,需要新开线程,在线程中用Receive方法队本地客户端进行消息监听处理
客户端示例代码
代码仅供学习演示,未进行异常处理,仅供展示方法功能
cs
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class SocketScript : MonoBehaviour
{
Socket mainSocket;
/// <summary>
/// 声明客户端并连接服务器
/// </summary>
void Start()
{
mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
mainSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345));
Thread thread = new Thread(Listen);
thread.Start();
}
byte [] message = new byte[1024];
/// <summary>
/// 监听服务器消息并处理
/// </summary>
void Listen()
{
while (true)
{
mainSocket.Receive(message);
Debug.Log(Encoding.UTF8.GetString(message));
}
}
/// <summary>
/// 客户端向服务器发送消息
/// </summary>
/// <param name="str"></param>
void Send(string str)
{
mainSocket.Send(Encoding.UTF8.GetBytes(str));
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Send("客户端发送测试数据");
}
}
}
服务器示例代码
代码仅供学习演示,未进行异常处理,仅供展示方法功能
cs
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
public class SeverSocketScript : MonoBehaviour
{
Dictionary<string,Socket> connectSockets = new Dictionary<string,Socket>();
Socket mainSocket;
/// <summary>
/// 声明服务器并开启接受监听线程
/// </summary>
void Start()
{
mainSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
mainSocket.Bind(new IPEndPoint(IPAddress.Any,12345));
mainSocket.Listen(10);
Thread thread = new Thread(CreatConnectSocket);
thread.Start();
}
/// <summary>
/// 接受监听后保存生成的通信客户端,并开启线程监听通信客户端消息
/// </summary>
void CreatConnectSocket()
{
while (true)
{
Socket socket = mainSocket.Accept();
if (!connectSockets.ContainsKey(socket.RemoteEndPoint.ToString()))
{
connectSockets.Add(socket.RemoteEndPoint.ToString(), socket);
Thread thrad = new Thread(() => { ListenNewConnectSocket(socket); });
thrad.Start();
}
}
}
byte[] bytes = new byte[1024];
/// <summary>
/// 接受通信客户端消息并对消息进行处理
/// </summary>
/// <param name="socket"></param>
void ListenNewConnectSocket(Socket socket)
{
while (true)
{
socket.Receive(bytes);
Debug.Log("接受消息" + Encoding.UTF8.GetString(bytes));
socket.Send(Encoding.UTF8.GetBytes("服务器收到测试数据"));
Debug.Log("发送消息回执");
}
}
/// <summary>
/// 由服务器向全部客户端进行数据广播
/// </summary>
void Send()
{
foreach (var socket in connectSockets)
{
socket.Value.Send(Encoding.UTF8.GetBytes("服务器发送测试数据"));
Debug.Log("服务器向" + socket.Key + "发送测试数据");
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Send();
}
}
}