鸽了 鸽了... 之前说要写socket的文章,但是没空啊...
先把练习的Demo贴出来... 最最最最最基本的socket应用,里面没有buff利用的优化,也没有粘包分包的处理...
纯属最基本的学习的代码
先发出来鞭策自己,回头再来添加。
代码很简单:
C#服务端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Server
{
public class MyTestServer
{
static Socket listen;
static Dictionary clients = new Dictionary();
//开启服务器
static void Main(string[] args)
{
Console.WriteLine("Main Start !!! ");
//监听用socket实例
listen = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定本机 ip 以及 端口
IPAddress ipAdr = IPAddress.Parse("192.168.1.115");
IPEndPoint ipEnd = new IPEndPoint(ipAdr, 8888);
listen.Bind(ipEnd);
listen.Listen(0);
Console.WriteLine("服务器启动 !!! ");
listen.BeginAccept(AcceptCallBack, listen);
Console.ReadKey();
}
//接收回调
static void AcceptCallBack(IAsyncResult ar)
{
try
{
//分配state 加入clients列表
Console.WriteLine("Accepted");
Socket listen = (Socket)ar.AsyncState;
Socket client = listen.EndAccept(ar);
ClientState state = new ClientState
{
socket = client
};
clients.Add(client, state);
client.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
}
catch (SocketException ex)
{
Console.WriteLine(ex);
}
}
static void ReceiveCallback(IAsyncResult ar)
{
try
{
ClientState state = (ClientState)ar.AsyncState;
Socket client = state.socket;
int count = client.EndReceive(ar);
if (count == 0)
{
//暂时 为0就断开
client.Close();
clients.Remove(client);
Console.WriteLine("socket close");
return;
}
string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
Console.WriteLine("recv : " + recvStr);
byte[] sendBytes = System.Text.Encoding.Default.GetBytes("echo" + recvStr + recvStr + recvStr + recvStr + recvStr);
client.Send(sendBytes);
client.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);
}
catch (SocketException ex)
{
Console.WriteLine("Socket Receive Fail " + ex.ToString());
}
}
}
class ClientState
{
public Socket socket;
public byte[] readBuff = new byte[1024];
}
}
Unity 客户端:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using UnityEngine;
using UnityEngine.UI;
public class MyTestClient : MonoBehaviour
{
public InputField inputF;
public Text text;
Socket socket;
byte[] readBuff = new byte[1024];
string recvStr = "";
List msgs = new List();
private void Update()
{
if (msgs.Count > 0)
{
for(int i = 0; i < msgs.Count; i++)
{
text.text += msgs[i] + "\r\n";
}
msgs.Clear();
}
}
public void Connection()
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.BeginConnect("192.168.1.115", 8888, ConnectCallback, socket);
}
void ConnectCallback(IAsyncResult ar)
{
Debug.Log("ConnectCallback");
try
{
Socket socket = (Socket)ar.AsyncState;
socket.EndConnect(ar);
Debug.Log("Socket Connect Success ... ");
socket.BeginReceive(readBuff, 0, 1024, SocketFlags.None, ReceiveCallBack, socket);
}
catch (SocketException ex)
{
Debug.LogError(ex);
}
}
void ReceiveCallBack(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
int count = socket.EndReceive(ar);
recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);
msgs.Add(recvStr);
Debug.Log("recvStr: " + msgs);
socket.BeginReceive(readBuff, 0, 1024, SocketFlags.None, ReceiveCallBack, socket);
}
catch (SocketException ex)
{
Debug.LogError(ex);
}
}
public void Send()
{
Debug.Log("Send : " + inputF.text);
byte[] b = System.Text.Encoding.Default.GetBytes(inputF.text);
socket.Send(b);
}
}
最最最最最基本的应用,写的也很鄙陋~ 做个练习就好啦。
实现的功能就是给服务器发个啥,服务器给你回“echo”然后把你发的东西回5遍~
GIT地址:https://github.com/PatrickBoomBoom/SocketTest.git
===============================================================================================
咳咳... 回来了,今天练习一下分包粘包处理 ~ ps:大佬有更好的方法还请斧正~
先跑一下之前的练习项目~
1. 启动C# 服务器~
2. 启动unity客户端~
连接服务器!
服务器收到连接请求,到这tcp就算连上了 ~
3. 给服务器发送一个“a”
服务器收到了“a”
并给你回了5个“a” ~
好了,到这运行良好,可是如果会遇到下面这种情况:
你给服务器发了个“asokl;awjiovjnasl;903t7908asydfioh2nasoidfnaso;dfjizlxdkfj;9 ... ... ”非常长的一个字符串,
服务器以五倍返回给你的时候,鸭!超了容量了,那么socket会把这东西给你分成两个或者多个包返回给你,就像这样:
我们现在只是把byte[]反序列化成 字符串 放到ui上,不会有什么问题,
如果实际工作中传输的是一个自定义的数据结构,当只有一条消息回来时,我们拿着一半的byte[]去反序列化那肯定就会出问题了哇~ 这就是分包问题。
粘包问题 正好反过来,是短时间内两个流 都进入Socket的sendbuff要发送,Socket就把它们当成一条消息一起发出去了。我们接收时要把它们拆出来分别反序列化才可以。
解: 加个头,告诉接收方,你发的这个东西一共多长。搞定~ (因为使用的是tcp所以接受一定是按顺序的,如果是UDP的话,还要加上本次的消息是从哪儿到哪儿的,这样接收方才能拼接出正确完整的 流)
举栗子:服务器加头,客户端来解析(反过来也一样的道理,就不做两遍了)
下面我们来改一下服务器给客户端发送消息处的代码:
之前的sendbytes现在是消息主体;
头部我用的int32转成的byte[] ,支持长度为2,147,483,647的消息主体,发啥玩楞都够用了~ 客户端取前4byte用来读长度。
也有用int16转成的byte[],支持长度为32767,其实一般也差不多够用了~ 客户端取前2byte来读长度。
下面我们来改一下客户端在接收的时候的处理:
第一种方法可以是直接扩展buff的容量,对方给你发1500长度的byte[],那你就用一个2048的长度的buff去接收,简单粗暴~
第二种方法是循环的接收,这条消息没接完,下条消息继续接(适用于比较少的特殊情况下使用,如果一直发特长消息,直接扩容比较好):
这地方逻辑比较恶心心~ 做了个脑图(如有错误大佬勿喷)
代码 先不贴了。。。 回头上传到git 。。。
先这样,待续未完 ....
================================================================================================
回头看看 之前的流程是没啥问题 但是实现起来 就很蠢 代码真的不够优美,因为处理在太多地方都处理过了,代码可读性也很差。
那么那么为什么不把队列引入进来,每次消息中所有的完整的包压进队列,然后每次消息读完之后把队列中所有的包解析了。
ok: 那么新鲜的流程图就出来了~
唉~ 舒服多了~ 嘻嘻