东西都很简单,把敲过的东西记下来,加深印象和理解
前端代码:
using System;
using System.Net.Sockets;
using UnityEngine;
using UnityEngine.UI;
public class Echo : MonoBehaviour
{
Socket socket; // 定义一个套接字 用来收发消息
public InputField inputField;
public Text text;
byte[] readBuff = new byte[1024];
string recvStr = "";
public void Connection()
{
// 第一个参数是地址族 InterNetwork 为 IPv4 第二个参数是 字节流套接字
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//socket.Connect("127.0.0.1", 8888); // connect 是一个阻塞方法,程序会卡住直到服务器回应
socket.BeginConnect("127.0.0.1", 8888, ConnectCallBack, socket); // BeginConnect 是一个异步方法不会阻塞线程
}
// 异步方法的回调
private void ConnectCallBack(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState; // BeginConnect传入的Socket
socket.EndConnect(ar);
Debug.Log("Socket Connect Succ");
socket.BeginReceive(readBuff, 0, 1024, 0, ReceiveCallBack, socket);
}
catch (SocketException ex)
{
Debug.LogError($"Socket Connect fail: {ex.ToString()}");
}
}
// Receive回调
private void ReceiveCallBack(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
int count = socket.EndReceive(ar);
string s = System.Text.Encoding.Default.GetString(readBuff, 0, count);
recvStr = $"{s}\n{recvStr}"; // 显示以前的聊天信息
socket.BeginReceive(readBuff, 0, 1024, 0, ReceiveCallBack, socket);
}
catch (SocketException ex)
{
Debug.LogError($"Socket Receive fail {ex.ToString()}");
}
}
public void Send()
{
// send
string sendStr = inputField.text;
byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
//socket.Send(sendBytes);
// recv
//byte[] readBuff = new byte[1024];
//int count = socket.Receive(readBuff);
//string recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);
//text.text = recvStr;
//socket.Close();
}
// 异步发送回调 此回调只代表把数据放入了发送缓存区不代表发送成功
private void SendCallback(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
int count = socket.EndSend(ar);
Debug.Log($"Socket Send succ: {count}");
}
catch (SocketException ex)
{
Debug.LogError($"Socket Send fail: {ex}");
}
}
// 异步回调是在其他线程里执行的,所以回调只给变量赋值由Update来赋值给UI
private void Update()
{
text.text = recvStr;
}
}
前端所有的代码都在上面,大概流程为:
一、接收消息
1.实例化socket
2.socket异步连接服务器
3.连接成功后开始异步等待接收服务器信息
4.接收消息成功后会重复的等待接收消息做到一个接收循环
二、发送消息
1.文本序列化后用BeginSend放入到发送缓存区,等待发送回调
2.发送回调成功只代表成功放入了发送缓存区不代表发送成功
三、UI显示
1.用u3d的Update方法来刷新显示UI
后端代码:
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
namespace Server
{
class Program
{
static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
static Socket listenfd; // 监听Socket
static void Main(string[] args)
{
Console.WriteLine("Hello word!");
// socket
Socket listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// Bind
IPAddress ipAdr = IPAddress.Parse("127.0.0.1"); // 此为送回地址 一般用于测试
IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
listenfd.Bind(ipEp); // 给套接字绑定接口和端口
// Listen
listenfd.Listen(0); // 等待客户端链接,参数表示最多可容纳等待接收的链接数,0表示不限
Console.WriteLine("服务器启动成功");
// Accpet
listenfd.BeginAccept(AcceptCallBack, listenfd);
// 等待
Console.ReadLine();
//while (true)
//{
// // Accept
// Socket connfd = listenfd.Accept(); // 应答,接收客户端链接 没有客户端链接时不会往下执行,返回一个新的socket对象用于处理客户端数据
// Console.WriteLine("服务器 Accept");
// // Receive
// byte[] readBuff = new byte[1024];
// int count = connfd.Receive(readBuff);
// string readStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);
// Console.WriteLine($"服务器接收: {readStr}");
// string timeStr = System.DateTime.Now.ToString();
// // Send
// byte[] sendBytes = System.Text.Encoding.Default.GetBytes(timeStr);
// connfd.Send(sendBytes);
//}
}
// 处理三件事 1.给新的连接分配ClientState并加入client字典中
// 2.异步接收客户端数据
// 3.再次调用BegingAccept实现循环
private static void AcceptCallBack(IAsyncResult ar)
{
try
{
Console.WriteLine($"服务器 Accept");
Socket listenfd = (Socket)ar.AsyncState;
Socket clientfd = listenfd.EndAccept(ar);
// clients 列表
ClientState state = new ClientState();
state.socket = clientfd;
clients.Add(clientfd, state);
// 接收数据 BeginReceive
clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
// 继续Accept
listenfd.BeginAccept(AcceptCallBack, listenfd);
}
catch (SocketException ex)
{
Console.WriteLine($"Socket Accpet fail: {ex}");
}
}
// 1. 服务端收到消息后,回应客户端
// 2. 如果收到客户端关闭连接的信号 if count == 0 断开连接(小于等于0表示断开连接,但也有特例)
// 3. 继续调用BeginReceive接收下一个数据
private static void ReceiveCallback(IAsyncResult ar)
{
try
{
ClientState state = (ClientState)ar.AsyncState;
Socket clientfd = state.socket;
int count = clientfd.EndReceive(ar);
// 客户端关闭
if (count == 0)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine($"Socket Close");
return;
}
string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
byte[] sendBytes = System.Text.Encoding.Default.GetBytes("echo" + recvStr);
foreach (var client in clients.Values) // 聊天室广播给所有的客户端
{
client.socket.Send(sendBytes);
}
//clientfd.Send(sendBytes); // 减少代码量不用异步
clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
}
catch (SocketException ex)
{
Console.WriteLine($"Socket Receive fail: {ex}");
}
}
}
}
// 代表一个客户端链接
class ClientState
{
public Socket socket;
public byte[] readBuff = new byte[1024];
}
后端代码大概流程:
1.new一个TCPSocket
2.绑定服务器IP和端口
3.socket开始监听
4.BeginAccept异步等待客户端传入
5.AcceptCallBack客户端传入,listenfd.EndAccept()方法处理socket通信
6.BeginReceive开始接收数据
7.BeginAccept继续侦听客户端连接
8.ReceiveCallback接收回调,在这里处理接收的数据并BeginReceive准备再次接收数据