首先跟大家道个歉,上一个同步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个组件:一个输入框,一个按钮
在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
{
}
}
然后调试一下,没有错误之后,打包测试。首先开启服务器:
然后打开打包好的客户端,打开两次并输入ip地址和端口号,点击连接,连接服务器
然后测试发送消息
注意!!!!!
只能发送英文,中文发送只能是乱码,这是正常的,因为编码的问题。
运行之后的图:
OK,感谢,感谢读完的你。