游戏客户端与服务器通讯协议,Jtro的技术分享:游戏客户端与服务器(c#)通讯_异步Socket...

首先跟大家道个歉,上一个同步Socket文章里用的不是Markdown编写的,所以代码看起来不是很清爽,我用的鹅厂的浏览器,终于发现是浏览器的锅,图片拖不上去 -_-|| , 真的是很失败啊,现在好了,已经下载了火狐浏览器,编辑什么的都很好用,向大家推荐一下。不知道火狐浏览器能不把鹅厂的书签迁移过来呢。。。。

————————————————————————————分割线——————————————————————————-

在同步模式中,服务器使用Accpet接收连接请求,客户端使用Connect连接服务器。同步模式中,如果没有客户端连接的话,它会卡在accpet处,而异步就很好的避免了此类问题。接下来,我通过查找资料和询问大神,实现了一个聊天室的功能,接下来将具体的来实现这个功能。

首先,用到新的函数: BeginAccept 、新的类:Conn

在服务器端程序中添加Conn的类:

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

{

class Conn

{

public const int BUFFER_SIZE = 1024; //常量

public Socket socket; //socket

public bool isUse; //是否使用

public byte[] readBuff = new byte[BUFFER_SIZE]; // Buff

public int buffCount = 0;

///

/// 构造函数

///

public Conn()

{

readBuff = new byte[BUFFER_SIZE];

}

///

/// 初始化

/// 初始化方法,在启用一个连接的时候会调用该方法,从而给一些变量赋值

///

///

public void Init(Socket socket)

{

this.socket = socket;

isUse = true;

buffCount = 0;

}

///

/// 缓冲区剩余的字节数

///

public int BuffRemain()

{

return BUFFER_SIZE - buffCount;

}

///

/// 获得客户端地址

/// 调用socket.RemoteEndPoint获取客户端的IP地址和端口

///

///

public string GetAdress()

{

if (!isUse)

{

return "无法获得地址";

}

else

{

return socket.RemoteEndPoint.ToString();

}

}

///

/// 关闭

/// 调用socket.Close()关闭这条连接

///

public void Close()

{

if (!isUse)

return;

Console.WriteLine("[ 断开连接 ]"+GetAdress ());

socket.Close();

isUse = false;

}

}

}

修改服务器端程序:

/*

* 脚本功能:服务器

* 作者 :张曙光

* 日期 :2017.11.15

*/

using System;

using System.Net; //引入命名空间

using System.Net.Sockets; //引入命名空间

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace Server

{

class Serv

{

public Socket listenfd; //监听套接字

public Conn[] conns; //客户端连接

public int maxconn = 50; //最大连接数

///

/// 获取连接池索引,返回负数就表示获取失败

///

///

public int NewIndex()

{

if (conns == null)

{

return -1;

}

for (int i = 0; i < conns .Length; i++)

{

if (conns[i] == null)

{

conns[i] = new Conn();

return i;

}

else if (conns [i].isUse == false)

{

return i;

}

}

return -1;

}

public void Start(string host ,int port)

{

conns = new Conn[maxconn]; //连接池

for (int i = 0; i < maxconn; i++)

{

conns[i] = new Conn();

}

listenfd = new Socket(AddressFamily .InterNetwork, //Socket

SocketType .Stream ,ProtocolType.Tcp);

IPAddress ipAdr = IPAddress.Parse(host); //Start Bind

IPEndPoint ipEp = new IPEndPoint(ipAdr ,port );

listenfd.Bind(ipEp); //End Bind

listenfd.Listen(maxconn ); //Listen

listenfd.BeginAccept(AcceptCb,null);

Console.WriteLine("[ 服务器 ]启动成功");

}

}

接着往服务器端添加回调函数

private void AcceptCb(IAsyncResult ar)

{

try

{

Socket socket = listenfd.EndAccept(ar);

int index = NewIndex();

//如果连接池已满,拒绝连接

if (index < 0)

{

socket.Close();

Console.WriteLine("[ 警告 ]连接已满");

}

//如果连接池未满,那就连接分配新的conn

else

{

Conn conn = conns[index];

conn.Init(socket);

string adr = conn.GetAdress();

Console.WriteLine(" 客户连接 [ "+adr +" ] conn池 ID :"+index);

conn.socket.BeginReceive(conn .readBuff ,conn .buffCount,conn.BuffRemain (),SocketFlags.None ,ReceiveCb ,conn);

}

listenfd.BeginAccept(AcceptCb,null);

}

//抓取异常

catch (Exception e)

{

Console.WriteLine("AcceptCn失败 :"+e .Message);

}

}

服务器端接收回调函数

private void ReceiveCb(IAsyncResult ar)

{

Conn conn = (Conn)ar.AsyncState;

try

{

int count = conn.socket.EndReceive(ar);

//关闭信号

if (count <=0)

{

Console.WriteLine(" 收到 ["+conn .GetAdress () +" ] 断开连接");

conn.Close();

return;

}

//数据处理

string str = System.Text.Encoding.UTF8.GetString(conn.readBuff, 0, count);

Console.WriteLine(" 收到 [ "+ conn .GetAdress () + " ]数据:"+str);

str = conn.GetAdress() + ":" + str;

byte[] bytes = System.Text.Encoding.Default.GetBytes(str);

//广播

for (int i = 0; i < conns .Length; i++)

{

if (conns [i]== null)

{

continue;

}

if (!conns [i].isUse)

{

continue;

}

Console.WriteLine("将消息传播给 "+ conns [i].GetAdress());

conns[i].socket.Send(bytes);

}

//继续接收

conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn);

}

catch (Exception e)

{

Console.WriteLine(" 收到 ["+conn .GetAdress () +"] 断开连接");

Console.WriteLine(e.Message);

conn.Close();

}

}

服务器端开启服务端

static void Main(string[] args)

{

Console.WriteLine("Hello , World");

Serv serv = new Serv();

serv.Start("127.0.0.1", 1234);

while (true)

{

string str = Console.ReadLine();

switch (str)

{

case "quit":

return;

}

}

}

到这里,只是将服务器端的事做完了,接下来就是客户端了,客户端改动的很少的一部分

首先打开同步Socket的工程,添加2个组件:一个输入框,一个按钮

36zAve.PNG 界面展示.PNG

在net的脚本中改写connetion的方法:

public void Btn_Connetion()

{

//清空text

RecvText.text = "";

//Socket

socket = new Socket(AddressFamily .InterNetwork,

SocketType.Stream ,ProtocolType.Tcp);

//Connect

string host = HostInput.text;

string strport = PortInput.text;

int port = int .Parse(strport);

socket.Connect(host ,port);

ClientText.text = "客户端地址 " + socket.LocalEndPoint.ToString();

//Recv

socket.BeginReceive(readBuff ,0,BUFFER_SIZE ,SocketFlags.None,ReceiveCb ,null);

}

当收到服务器端发来的消息的时候,异步接收回调启用,开启异步接收的代码如下:

private void ReceiveCb(IAsyncResult ar)

{

try

{

//count 是接收数据的大小

int count = socket.EndReceive(ar);

//数据处理

string str = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);

if (recvStr.Length > 300) recvStr = "";

recvStr += str + "\n";

//继续接收

socket.BeginReceive(readBuff, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCb, null);

}

catch (Exception)

{

RecvText.text += "连接已断开";

socket.Close();

}

}

然后编写Send函数,向服务器发送消息

public void Send()

{

string str = textinput.text;

byte[] bytes = System.Text.Encoding.Default.GetBytes(str);

try

{

socket.Send(bytes);

}

catch

{

}

}

然后调试一下,没有错误之后,打包测试。首先开启服务器:

3MvIZv.PNG 开启服务器.PNG

然后打开打包好的客户端,打开两次并输入ip地址和端口号,点击连接,连接服务器

rIvumu.PNG 2个客户端连接服务器.PNG

然后测试发送消息

注意!!!!!

只能发送英文,中文发送只能是乱码,这是正常的,因为编码的问题。

运行之后的图:

2qMVfa.PNG 聊天界面.PNG

OK,感谢简书,感谢读完的你。

作者:Jtro

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值