目录
一、了解编写服务器demo?
?二、Demo一之中的不足:1.byte[1024]无内容部分都转成了空格,如何解决?2.客户端只能发送一条消息,如何解决?3.只能接受一个客户端发送的消息,如何改进?
?解决方法:
三:实现消息的广播
?四:改写成为异步服务器
思考:有没有问题呢?会不会出错呢?
五、Client类的封装
ClientPeer类
NetManager类:
main函数类:
六、动态链接库:
一、了解编写服务器demo?
基础知识:
? ? ? ? IP地址:计算机在网络中的位置 ? ? ? ? DNS:将域名转化为对应IP地址的服务器 ? ? ? ? 端口号:计算机某一个程序,在操作系统中的编号 ? ? ? ? 协议:网络传输中的约定 ? ? ? ? TCP:管道式传输 ? ? ? ? ? ? ? ? 三次握手: ? ? ? ? ? ? ? ? 四次挥手: ? ? ? ? UDP:广播式传输
服务器端代码1.0:
using NetMQ.Sockets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace NetWorkTest
{
//ip:计算机在网络中的位置
//端口:计算机应用程序在操作系统中的编号
//传输协议:TCP:管道式传输,好处:安全,信息的先后顺序保持不变;缺点:传输速度限制比较大
// UDP:广播式传输,好处:传输速度很快;缺点:安全性较低,丢包
class Program
{
static void Main(string[] args)
{
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse("192.168.117.1");
int port = 9999;
serverSocket.Bind(new IPEndPoint(ip,port));
serverSocket.Listen(10);
Console.WriteLine("服务器启动了!");
Socket clientSocket = serverSocket.Accept();//这是一个阻塞函数
Console.WriteLine("服务器收到了一个客户端连接"+clientSocket.RemoteEndPoint.ToString());
Byte[] MsgArr = new Byte[1024];
clientSocket.Receive(MsgArr, 0, MsgArr.Length, SocketFlags.None);
string msg = Encoding.UTF8.GetString(MsgArr);
Console.WriteLine("接受的数据为:"+msg);
Console.ReadKey();
}
}
}
?客户端代码1.0:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace ClientTestOne
{
class Program
{
static void Main(string[] args)
{
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse("192.168.117.1");
int port = 9999;
clientSocket.Connect(new IPEndPoint(iPAddress, port));
Console.WriteLine("客户端开始连接服务器!");
string content = "wo只会心疼哥哥!";
byte[] msg = Encoding.UTF8.GetBytes(content);
clientSocket.Send(msg);
Console.ReadKey();
}
}
}
?二、Demo一之中的不足: 1.byte[1024]无内容部分都转成了空格,如何解决? 2.客户端只能发送一条消息,如何解决? 3.只能接受一个客户端发送的消息,如何改进?
思路:使用多线程
?解决方法:
1.
2.?
3.
服务端2.0
using NetMQ.Sockets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace NetWorkTest
{
//ip:计算机在网络中的位置
//端口:计算机应用程序在操作系统中的编号
//传输协议:TCP:管道式传输,好处:安全,信息的先后顺序保持不变;缺点:传输速度限制比较大
// UDP:广播式传输,好处:传输速度很快;缺点:安全性较低,丢包
class Program
{
static void Main(string[] args)
{
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse("192.168.117.1");
int port = 9999;
serverSocket.Bind(new IPEndPoint(ip,port));
serverSocket.Listen(10);
Console.WriteLine("服务器启动了!");
while (true)
{
Socket clientSocket = serverSocket.Accept();//这是一个阻塞函数
Console.WriteLine("服务器收到了一个客户端连接" + clientSocket.RemoteEndPoint.ToString());
Thread thread = new Thread(ReceiveMsg);
thread.Start(clientSocket);
}
Console.ReadKey();
}
private static void ReceiveMsg(object obj)
{
Socket clientSocket = obj as Socket;
byte[] MsgArr = new byte[1024];
while (true)
{
int Length = clientSocket.Receive(MsgArr, 0, MsgArr.Length, SocketFlags.None);
string msg = Encoding.UTF8.GetString(MsgArr, 0, Length);
Console.WriteLine("接受的数据为:" + msg);
}
}
}
}
客户端2.0
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ClientTestOne
{
class Program
{
static Socket clientSocket;
static void Main(string[] args)
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse("192.168.117.1");
int port = 9999;
clientSocket.Connect(new IPEndPoint(iPAddress, port));
Console.WriteLine("客户端开始连接服务器!");
Thread thread = new Thread(ReceiveMsg);
thread.Start();
while (true)
{
string content = Console.ReadLine();
byte[] msg = Encoding.UTF8.GetBytes(content);
clientSocket.Send(msg);
}
Console.ReadKey();
}
static void ReceiveMsg()
{
byte[] MsgArr = new byte[1024];
while (true)
{
int Length = clientSocket.Receive(MsgArr, 0, MsgArr.Length, SocketFlags.None);
string msg = Encoding.UTF8.GetString(MsgArr, 0, Length);
Console.WriteLine("接受的数据为:" + msg);
}
}
}
}
又出现一个小问题:只要有客户端关闭,服务器就会出错,那么如何改进这个问题呢?解决方式:需要使用try...catch
改进后的服务器2.0代码:
using NetMQ.Sockets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace NetWorkTest
{
//ip:计算机在网络中的位置
//端口:计算机应用程序在操作系统中的编号
//传输协议:TCP:管道式传输,好处:安全,信息的先后顺序保持不变;缺点:传输速度限制比较大
// UDP:广播式传输,好处:传输速度很快;缺点:安全性较低,丢包
class Program
{
static void Main(string[] args)
{
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse("192.168.117.1");
int port = 9999;
serverSocket.Bind(new IPEndPoint(ip,port));
serverSocket.Listen(10);
Console.WriteLine("服务器启动了!");
while (true)
{
Socket clientSocket = serverSocket.Accept();//这是一个阻塞函数
Console.WriteLine("服务器收到了一个客户端连接" + clientSocket.RemoteEndPoint.ToString());
Thread thread = new Thread(ReceiveMsg);
thread.Start(clientSocket);
}
Console.ReadKey();
}
private static void ReceiveMsg(object obj)
{
Socket clientSocket = obj as Socket;
byte[] MsgArr = new byte[1024];
while (true)
{
try
{
//receive的返回值,就是结束到的数据的长度,Receive()是一个阻塞函数
int Length = clientSocket.Receive(MsgArr, 0, MsgArr.Length, SocketFlags.None);
if (Length == 0)
{
clientSocket.Close();
return;
}
string msg = Encoding.UTF8.GetString(MsgArr, 0, Length);
Console.WriteLine("接受的数据为:" + msg);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
clientSocket.Close();
return;
}
}
}
}
}
?运行截图:
三:实现消息的广播
提示:客户端自己不需要给自己发消息,使用List存储客户端连接
服务端3.0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ChatServer
{
class Program
{
//消息的广播:需要使用List容器存储与客户端建立的连接
private static List<Socket> SocketPoolList = new List<Socket>();
static void Main(string[] args)
{
try
{
//连接客户端
Socket Serversocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
int port = 8899;
IPAddress iPAddress = IPAddress.Parse("192.168.117.1");
Serversocket.Bind(new IPEndPoint(iPAddress, port));
Serversocket.Listen(10);
Console.WriteLine("服务器启动了");
//主线程中最好不要放置死循环的代码
Thread thread = new Thread(AcceptClient);
thread.Start(Serversocket);
Console.ReadKey();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
//接收客户端连接
private static void AcceptClient(object obj)
{
Socket Serversocket = obj as Socket;
if (Serversocket == null)
{
return;
}
Socket ClientServer = Serversocket.Accept();
SocketPoolList.Add(ClientServer);
Console.WriteLine("服务器收到了一个连接:" + ClientServer.RemoteEndPoint.ToString());
Thread thread = new Thread(ReceiveMsg);
thread.Start(ClientServer);
//服务器需要一直接受客户端的消息
AcceptClient(Serversocket);
}
private static void ReceiveMsg(Object obj)
{
Socket ClientSocket = obj as Socket;
if (ClientSocket == null)
{
return;
}
byte[] msgArr = new byte[1024];
while (true)
{
try
{
//客户端接受消息
int Length = ClientSocket.Receive(msgArr, 0, msgArr.Length, SocketFlags.None);
if (Length == 0)
{
ClientSocket.Close();
SocketPoolList.Remove(ClientSocket);
return;
}
string clientMsg = Encoding.UTF8.GetString(msgArr, 0, Length);
Console.WriteLine("客户端发送的消息为:"+clientMsg);
byte[] msg = new byte[Length];
Array.Copy(msgArr, 0, msg,0, Length);
for (int i = 0; i < SocketPoolList.Count; i++)
{
if(SocketPoolList[i] == ClientSocket)
{
continue;
}
SocketPoolList[i].Send(msg);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
ClientSocket.Close();
SocketPoolList.Remove(ClientSocket);
return;
}
}
}
}
}
客户端3.0
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ClientTestOne
{
class Program
{
static Socket clientSocket;
static void Main(string[] args)
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse("192.168.117.1");
int port = 8899;
clientSocket.Connect(new IPEndPoint(iPAddress, port));
Console.WriteLine("客户端开始连接服务器!");
Console.WriteLine("请输入你的名字:");
String name = Console.ReadLine();
Thread thread = new Thread(ReceiveMsg);
thread.Start();
while (true)
{
string content = name + ":"+Console.ReadLine();
byte[] msg = Encoding.UTF8.GetBytes(content);
clientSocket.Send(msg);
}
Console.ReadKey();
}
static void ReceiveMsg()
{
byte[] MsgArr = new byte[1024];
while (true)
{
int Length = clientSocket.Receive(MsgArr, 0, MsgArr.Length, SocketFlags.None);
string msg = Encoding.UTF8.GetString(MsgArr, 0, Length);
Console.WriteLine(msg);
}
}
}
}
运行截图:
?四:改写成为异步服务器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace AsyncChatServer
{
class Program
{
//消息的广播:需要使用List容器存储与客户端建立的连接
private static List<Socket> SocketPoolList = new List<Socket>();
static void Main(string[] args)
{
try
{
Socket Serversocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAdress = IPAddress.Parse("192.168.117.1");
int port = 8899;
Serversocket.Bind(new IPEndPoint(iPAdress, port));
Serversocket.Listen(10);
Console.WriteLine("服务器启动了");
Serversocket.BeginAccept(AsyncAccept,Serversocket);//开启异步接受客户端连接
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadKey();
}
static byte[] msgArr = new byte[1024];
private static void AsyncAccept(IAsyncResult ar)
{
Socket serverSocket = ar.AsyncState as Socket;
if (serverSocket == null)
{
return;
}
Socket clientSocket = serverSocket.EndAccept(ar);
SocketPoolList.Add(clientSocket);
clientSocket.BeginReceive(msgArr, 0, msgArr.Length, SocketFlags.None, AsyncReveive, clientSocket);
serverSocket.BeginAccept(AsyncAccept, serverSocket);
}
private static void AsyncReveive(IAsyncResult ar)
{
Socket clientSocket = ar.AsyncState as Socket;
try
{
int Length = clientSocket.EndReceive(ar);
if (Length ==0)
{
clientSocket.Close();
SocketPoolList.Remove(clientSocket);
return;
}
string message = Encoding.UTF8.GetString(msgArr,0,Length);
Console.WriteLine(message);
//消息的广播
byte[] msg = new byte[Length];
Array.Copy(msgArr, 0, msg, 0, Length);
for (int i = 0; i < SocketPoolList.Count; i++)
{
if (SocketPoolList[i] == clientSocket)
{
continue;
}
SocketPoolList[i].Send(msg);
}
//重新开启监听
clientSocket.BeginReceive(msgArr, 0, msgArr.Length, SocketFlags.None, AsyncReveive, clientSocket);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
clientSocket.Close();
SocketPoolList.Remove(clientSocket);
return;
}
}
}
}
效果同上
思考:有没有问题呢?会不会出错呢?
不会出错,但是有问题,在服务器上的客户端连接接受消息间隔时间很短的情况下,那么就会导致向客户端广播的消息为同一条消息,因为连接上了服务器的客户端在接受消息的时候是异步的,而且共用堆中的数据byte[],因此在间隔时间很短的情况下,那么就会导致客户端接受的消息为同一条消息。
底层原理:多线程在执行的时候,会共享堆内存中的数据(不共享栈内存中的数据)
五、Client类的封装
解决四中的问题,可以利用面向对象的思想改进,将client连接封装成为一个类,使每一个client都能拥有自己byte[]数组来接受消息。
ClientPeer类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace AsyncChatServer
{
class ClientPeer
{
Socket clientSocket = null;
static byte[] msgArr = new byte[1024];
internal ClientPeer(Socket clientSocket)
{
this.clientSocket = clientSocket;
clientSocket.BeginReceive(msgArr,0,msgArr.Length, SocketFlags.None, AsyncReceive, clientSocket);
}
internal void AsyncReceive(IAsyncResult ar)
{
try
{
Socket clientSocket = ar.AsyncState as Socket;
int length = clientSocket.EndReceive(ar);
if (clientSocket == null)
{
clientSocket.Close();
NetManager.Instance.RemoveClient(this);
return;
}
string message = Encoding.UTF8.GetString(msgArr,0,length);
Console.WriteLine(message);
NetManager.Instance.BoardCastMessage(message, this);
clientSocket.BeginReceive(msgArr, 0, msgArr.Length, SocketFlags.None, AsyncReceive, clientSocket);
}
catch (Exception e)
{
clientSocket.Close();
NetManager.Instance.RemoveClient(this);
Console.WriteLine(e.Message);
return;
}
}
public void SendMessage(byte[] message)
{
clientSocket.Send(message);
}
public void SendMessage(string message)
{
clientSocket.Send(Encoding.UTF8.GetBytes(message));
}
}
}
NetManager类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace AsyncChatServer
{
class NetManager
{
private static NetManager _instance = null;
private NetManager()
{
}
public static NetManager Instance
{
get
{
if (_instance == null)
{
_instance = new NetManager();
}
return _instance;
}
}
//消息的广播:需要使用List容器存储与客户端建立的连接
private static List<ClientPeer> clientList = new List<ClientPeer>();
public void AddClient(ClientPeer client)
{
clientList.Add(client);
}
public void RemoveClient(ClientPeer client)
{
clientList.Remove(client);
}
public void BoardCastMessage(string message,ClientPeer client = null)
{
for (int i = 0; i < clientList.Count; i++)
{
if (client!=null&& clientList[i] == client)
{
continue;
}
clientList[i].SendMessage(message);
}
}
public void BoardCastMessage(byte[] message, ClientPeer client = null)
{
for (int i = 0; i < clientList.Count; i++)
{
if (client != null && clientList[i] == client)
{
continue;
}
clientList[i].SendMessage(message);
}
}
public void SendMessage(byte[] message,ClientPeer client)
{
client.SendMessage(message);
}
public void SendMessage(string message, ClientPeer client)
{
client.SendMessage(message);
}
}
}
main函数类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace AsyncChatServer
{
class Program
{
static void Main(string[] args)
{
try
{
Socket Serversocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAdress = IPAddress.Parse("192.168.117.1");
int port = 8899;
Serversocket.Bind(new IPEndPoint(iPAdress, port));
Serversocket.Listen(10);
Console.WriteLine("服务器启动了");
Serversocket.BeginAccept(AsyncAccept, Serversocket);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadKey();
}
static byte[] msgarr = new byte[1024];
private static void AsyncAccept(IAsyncResult ar)
{
Socket serversocket = ar.AsyncState as Socket;
if (serversocket == null)
{
return;
}
Socket clientsocket = serversocket.EndAccept(ar);
ClientPeer clientPeer = new ClientPeer(clientsocket);
NetManager.Instance.AddClient(clientPeer);
//clientsocket.beginreceive(msgarr, 0, msgarr.length, socketflags.none, asyncreveive, clientsocket);
serversocket.BeginAccept(AsyncAccept, serversocket);
}
}
}
按理来说:ip端口应该也不应该放在Main函数中,
六、动态链接库:
ddl:包含该动态链接库实际的函数和数据。在程序运行阶段,加载该文件,并将该文件映射到进程地址空间中,然后访问该文件中的相应函数。
一般用来实现一些工具类,配置接口
生成.dll文件
七、将网络模块移植到unity中:
代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;
public class NetManager : MonoBehaviour
{
//单例
private static NetManager _instance = null;
private NetManager()
{
}
private static NetManager Instance
{
get
{
if(_instance == null)
{
_instance = new NetManager();
}
return _instance;
}
}
// Start is called before the first frame update
//客户端连接服务器
//1.连接到服务器
//2.接受服务器的消息,并处理
//3.向服务器发送消息
byte[] msgArr = new byte[1024];
Socket clientSocket = null;
//消息队列,用于处理消息
Queue<string> messageQueue = new Queue<string>();
private void OnEnable()
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
void Start()
{
clientSocket.Connect(new IPEndPoint(IPAddress.Parse(Protocol.ProtocolConfig.ip), Protocol.ProtocolConfig.port));
Debug.Log("客户端开始连接服务器!");
try
{
clientSocket.BeginReceive(msgArr, 0, msgArr.Length, SocketFlags.None, AsyncReceive, clientSocket);
}
catch (System.Exception e)
{
Debug.Log(e.Message);
}
}
//接受服务器的消息
private void AsyncReceive(IAsyncResult ar)
{
Socket clientSocket = ar.AsyncState as Socket;
if (clientSocket == null)
{
return;
}
int length = clientSocket.EndReceive(ar);
string message = Encoding.UTF8.GetString(msgArr, 0, length);//接受的消息,思考为啥不在这个函数里面处理消息
//异步的本质是一条多线程,很多Unity的API没有办法在支线程中使用
messageQueue.Enqueue(message);
clientSocket.BeginReceive(msgArr, 0, msgArr.Length, SocketFlags.None, AsyncReceive, clientSocket);
}
private void SendMessageToServer(string message)
{
clientSocket.Send(Encoding.UTF8.GetBytes(message));
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
SendMessageToServer("helloWorld");
}
if (messageQueue.Count>0)
{
string message = messageQueue.Dequeue();
//处理这个消息
Debug.Log(message);
}
}
}
运行成功:
?思考:接受的消息为啥不在异步函数中执行?为啥要用到消息队列
|