什么是socket
Socket(套接字)是应用层与TCP/IP协议族通信的中间软件抽象层,简单来说它是一组接口,也就是一套供应用程序调用的API。
Socket把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,只需要使用Socket提供的API接口就可以了,至于说如何让数据变成符合指定的网络协议的传输要求,Socket会自行完成。
个人理解
因为对Socket通信的流程仍然比较陌生,因此我搜索了网上的资料sh简单的socket通信流程。
学习自C#版 Socket编程(最简单的Socket通信功能)
服务端脚本
//Server.cs
using System.Net;
using System.Net.Sockets;
using System.Text;
public class Server
{
public static void Main(string[] args)
{
int port = 2000;
string host="127.0.0.1";
///创建终结点
IPAddress ip = IPAddress.Parse(host); //将字符串IP地址解析成IPAddress实例
IPEndPoint ipe = new IPEndPoint(ip, port); //用指定的端口和ip初始化IPEndPoint类的新实例
///创建Socket并开始监听
#region
//AddressFamily.InterNetwork表示套接字使用IPv4,InterNetworkV6使用IPv6
//SocketType.Stream流套接字, 对应TCP协议. SocketType.Dgram对应udp
#endregion
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.Bind(ipe); //绑定EndPoint对象(2000端口和IP地址)
s.Listen(0); //开始监听
Console.WriteLine("等待客户端连接");
///接收client连接,为此连接建立新的socket,并接收信息
Socket temp = s.Accept();
Console.WriteLine("建立连接");
string recvStr = "";
byte[] recvBytes = new byte[1024];
int bytes;
bytes = temp.Receive(recvBytes, recvBytes.Length, 0);//返回值表示接收到的数据量
recvStr += Encoding.ASCII.GetString(recvBytes, 0, bytes);
Console.WriteLine("server get message:{0}", recvStr);
///给client端返回信息
string sendStr = "Ok!Client send message successful(Server)";
byte[] bs = Encoding.ASCII.GetBytes(sendStr);
temp.Send(bs, bs.Length, 0);
///释放socket连接
temp.Close();
s.Close();
Console.ReadLine();
}
}
客户端脚本
//Client.cs
using System.Net;
using System.Net.Sockets;
using System.Text;
public class Client
{
public static void Main(string[] args)
{
try
{
int port = 2000;
string host = "127.0.0.1";
///创建终结点EndPoint
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port); //把ip和端口转化为IPEndPoint实例
///创建socket并连接到服务器
Socket c = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Console.WriteLine("Conecting...");
c.Connect(ipe);
///向服务端发送信息
string sendStr = "Hello!This is a socket test(Client)";
byte[] bs = Encoding.ASCII.GetBytes(sendStr); //将字符串转换为 ASCII 编码的字节数组。
Console.WriteLine("Send Messege");
c.Send(bs, bs.Length, 0);
///接受从服务端返回的信息
string recvStr = "";
byte[] recvBytes = new byte[1024];
int bytes;
bytes = c.Receive(recvBytes, recvBytes.Length, 0);
recvStr += Encoding.ASCII.GetString(recvBytes, 0, bytes);
Console.WriteLine("client get message:{0}", recvStr);
///记得用完Socket要关闭
c.Close();
}
catch(ArgumentException e)//表示方法参数为空引发的异常
{
Console.WriteLine("argumentNullException:{0}",e);
}
catch(SocketException e) //server端未启动
{
Console.WriteLine("SocketException:{0}", e);
}
Console.WriteLine("Press Enter to Exit");
Console.ReadLine();
}
}
运行截图
在ET中的使用
流程和上面差不多。
我是这样理解的:
- 在
accountSession = zoneScene.GetComponent<NetKcpComponent>().Create(NetworkHelper.ToIPEndPoint(address));
首先创建一个IPEndPoint对象
public static IPEndPoint ToIPEndPoint(string address)
{
int index = address.LastIndexOf(':');
string host = address.Substring(0, index);//获取IP地址
string p = address.Substring(index + 1); //获取端口号
int port = int.Parse(p);
return ToIPEndPoint(host, port);
}
- 然后建立与服务器端的会话
//建立一个会话
public static Session Create(this NetKcpComponent self, IPEndPoint realIPEndPoint)
{
long channelId = RandomHelper.RandInt64();
Session session = self.AddChildWithId<Session, AService>(channelId, self.Service);
session.RemoteAddress = realIPEndPoint;
session.AddComponent<SessionIdleCheckerComponent, int>(NetThreadComponent.checkInteral);
//会创建一个TChannel对象,TChannel会建立一个和服务器端连接的socket
//通过sessionId找到TChannel,继而找到socket
//因此可以通过session向socket写入数据
self.Service.GetOrCreate(session.Id, realIPEndPoint);
return session;
}
可以看到在TChannel的构造函数中创建了一个socket实例。
public TChannel(long id, IPEndPoint ipEndPoint, TService service)
{
this.ChannelType = ChannelType.Connect;
this.Id = id;
this.Service = service;
this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.socket.NoDelay = true;
this.parser = new PacketParser(this.recvBuffer, this.Service);
this.innArgs.Completed += this.OnComplete;
this.outArgs.Completed += this.OnComplete;
this.RemoteAddress = ipEndPoint;
this.isConnected = false;
this.isSending = false;
//将回调push到主线程处理
this.Service.ThreadSynchronizationContext.PostNext(this.ConnectAsync);
}
- 建立会话后,客户端向服务器端发送与一条请求。
a2C_LoginAccount = (A2C_LoginAccount)await accountSession.Call(new C2A_LoginAccount() { AccountName = account, Password = password });
- 服务器端处理客户端发送的请求。
[FriendClass(typeof(Account))]
public class C2A_LoginAccountHandler : AMRpcHandler<C2A_LoginAccount, A2C_LoginAccount>
{
protected override async ETTask Run(Session session, C2A_LoginAccount request, A2C_LoginAccount response, Action reply)
{
if (session.DomainScene().SceneType != SceneType.Account)
{
Log.Error($"请求的scene错误,当前scene为{session.DomainScene().SceneType}");
session?.Dispose(); //请求的游戏服务器进程就是错误的,直接dispose
return;
}
//如果不移除,session将会断开
session.RemoveComponent<SessionAcceptTimeoutComponent>(); //移除检测组件
===以下省略一大坨处理逻辑===
await ETTask.CompletedTask;
}
}
}
- 客户端处理服务器端返回的数据
a2C_LoginAccount = (A2C_LoginAccount)await accountSession.Call(new C2A_LoginAccount() { AccountName = account, Password = password });
//登录成功
if (a2C_LoginAccount.Error != ErrorCode.ERR_Success)
{
accountSession?.Dispose();
return a2C_LoginAccount.Error;
}
//登录失败
else{
.......
}