最近正在用Unity做一个类似三国杀的卡牌游戏,网上也找不到什么东西参考,只好自己动手一点点解决问题,由于服务器不是很熟,于是决定一边学Photon一边先做个局域网联机的玩玩,局域网联机我决定参考CS红警等游戏的模式先做个大厅一人建主其他人加入这种,毕竟直接输ip什么的太low了。
局域网联机说白了就是建主主机既是客户端也是服务器端,我选择选用UDP广播搞定大厅的问题,在用TCP连接主机和服务器。
大概流程如下:
1、 一个客户端创建房间,成为服务器端,开始监听;
2、客户端点击局域网联机后向全网广播UDP请求服务器端,
3、服务器收到消息,向客户端发送IP及端口号(房间信息,比如当前人数)
4、客户端接收并将收到的服务器房间显示在大厅中
5、客户端加入房间,和服务器建立TCP连接
之前为了逻辑清楚还在文档里画了图也放这里吧
这里的所有UDP都默认开启监听
这里的Server端同时也要创建TCPServer准备进行和客户端的正式连接
以下是测试图
输入姓名确认然后点创建
building好新开一个,点一下刷新
点加入
服务器客户端显示都如上图,这时候服务器和客户端都已经建立好连接了,开始游戏上注册上发送方法就行了,方法什么的都简单的封装过了,局域网亲测没问题。不过这里的可能会有一些小问题或者没做的比如返回关闭页面什么的,其实后来已经做了,但是游戏进度做了一些,删东西太麻烦就直接用之前的了,整体没啥问题的。
注意:如果是在同一主机测试,要把场景中EthernetManager上的端口改一下,保证打开的每一个程序的端口号不一样,多个主机就不用了,原因是UDP广播固定端口号,但是单机测试用回环ip,一个端口是不能开两个udpSocket的,默认广播端口9876,可以自己在代码改,如果单机测试记得用9876的程序来建主
EthernetManager中的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Threading;
using System.Net.Sockets;
using LitJson;
using System.Text;
using System;
public class EthernetManager : MonoBehaviour
{
private static EthernetManager _instance;
public static EthernetManager instance
{
get
{
return _instance;
}
set
{
_instance = value;
}
}
private SocketClient client = null;
private SocketTcpServer tcpServer = null;
private SocketTcpClient tcpClient = null;
public List roomList = new List();
public Dictionary ClientList = new Dictionary();
public List rmiList = new List();
public EndPoint tempRoomEp = null;
EndPoint host = null;
public Thread t = null;
public Thread l = null;
public Thread tcpListen = null;
public Thread clientListen = null;
public int port;
public bool shutClientListen = false;
public string clientReceiveMessage = null;
public string name = "";
public Action> ReceiveAction;
void Awake()
{
instance = this;
string hostName = Dns.GetHostName();
IPAddress ipaddress = Dns.GetHostAddresses(hostName)[1];
host = new IPEndPoint(ipaddress, port);
}
# region UDP客户端
///
/// 创建客户端
///
public void CreateSocket()
{
client = new SocketClient(host);
}
///
/// 监听服务器的消息
///
public void ListenServerResponse()
{
Thread l = new Thread(client.ReceiveRoomInfo);
l.IsBackground = true;
l.Start();
Debug.Log("UDP客户端开始监听");
RequestEthernetServer();
}
///
/// 请求当前局域网中的服务器
///
public void RequestEthernetServer()
{
client.SendRequest(port);
}
#endregion
#region UDP 服务端
///
/// 开始监听客户端请求
///
public void ListenClientRequest()
{
t = new Thread(client.BackRoomInfo);
t.IsBackground = true;
t.Start();
Debug.Log("开始监听");
}
#endregion
#region TCP客户端
public void CreateTcpClient()
{
if (tempRoomEp != null)
tcpClient = new SocketTcpClient(tempRoomEp, host);
tempRoomEp = null;
}
public void ConnectTcpServer()
{
tcpClient.Connect();
clientListen = new Thread(tcpClient.Receive);
clientListen.IsBackground = true;
clientListen.Start();
RequestRoomInfo();
}
public void RequestRoomInfo()
{
tcpClient.SendMessage(EthernetOrder.RequestMemberInfo.ToString() + ":" + name);
}
#endregion
#region TCP服务端
public void CreateTcpServer()
{
tcpServer = new SocketTcpServer(host);
tcpListen = new Thread(tcpServer.Listen);
tcpListen.IsBackground = true;
tcpListen.Start();
}
public void BroadRoomInfo()
{
RoomMemberInfo info = new RoomMemberInfo()
{
id = 0,
name = name,
otherInfo = ""
};
rmiList.Add(info);
foreach (var item in ClientList)
{
info = new RoomMemberInfo()
{
id = item.Key,
name = item.Value.username,
otherInfo = ""
};
rmiList.Add(info);
}
ReceiveAction(rmiList);
string json = JsonMapper.ToJson(rmiList);
Debug.Log(json);
BroadCast(json);
}
///
/// 广播
///
public void BroadCast(string str)
{
foreach (Client client in ClientList.Values)
{
client.SendMessage(str);
}
}
#endregion
void OnDestroy()
{
if (t != null)
t.Abort();
if (l != null)
l.Abort();
if (tcpListen != null)
tcpListen.Abort();
if (clientListen != null)
clientListen.Abort();
if (client != null)
client.Close();
foreach (Client item in ClientList.Values)
{
item.ShutDown();
}
}
public void Reset()
{
if (t != null)
t.Abort();
if (l != null)
l.Abort();
if (tcpListen != null)
tcpListen.Abort();
if (clientListen != null)
clientListen.Abort();
if (client != null)
client.Close();
foreach (Client item in ClientList.Values)
{
item.ShutDown();
}
/*client.Close();
tcpServer.Close();
tcpClient.Close();*/
}
}
接收发送以及Socket创建监听什么的都被我分装到了类里
UDP
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System.Net;
using System.Text;
using LitJson;
using System.Threading;
public class SocketClient
{
public string name = "kzg";
Socket udpSocket = null;
List clientIplist = new List();
public EndPoint host;
public int defaultPort = 9876;
#region 客户端
///
/// 创建UDPSocket
///
///
public SocketClient(EndPoint host)
{
udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
udpSocket.Bind(host);
this.host = host;
}
///
/// 发送请求服务器广播
///
///
public void SendRequest(int port)
{
string message = "RequestServer";
byte[] data = Encoding.UTF8.GetBytes(message);
Debug.Log(message);
EndPoint ip = new IPEndPoint(IPAddress.Broadcast, defaultPort);
udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); //注释记住
udpSocket.SendTo(data, ip);
}
///
/// 接收房间信息
///
///
public void ReceiveRoomInfo()
{
EndPoint remote = new IPEndPoint(IPAddress.Any, 0);
while (true)
{
byte[] data = new byte[1024];
int length = udpSocket.ReceiveFrom(data, ref remote);
string s = Encoding.UTF8.GetString(data, 0, length);
Debug.Log(s);
if (!s.Equals("RequestServer"))
{
RoomInfo info = LitJson.JsonMapper.ToObject(s);
if (EthernetManager.instance.roomList.Count == 0)
EthernetManager.instance.roomList.Add(info);
for (int i = 0; i < EthernetManager.instance.roomList.Count; i++)
{
if (EthernetManager.instance.roomList[i].Ipport == info.Ipport)
{
EthernetManager.instance.roomList.Remove(EthernetManager.instance.roomList[i]);
EthernetManager.instance.roomList.Add(info);
break;
}
EthernetManager.instance.roomList.Add(info);
}
}
if (EthernetManager.instance.shutClientListen == true)
break;
}
}
#endregion
#region 服务端
///
/// 回发房间信息
///
///
public void BackRoomInfo()
{
EndPoint ep = new IPEndPoint(IPAddress.Any, 0);
while (true)
{
byte[] data = new byte[1024];
int length = udpSocket.ReceiveFrom(data, ref ep);
string s = Encoding.UTF8.GetString(data, 0, length);
Debug.Log("收到来自" + ep + "的信息" + s);
if (s.Equals("RequestServer"))
{
RoomInfo info = new RoomInfo()
{
name = name,
number = EthernetManager.instance.ClientList.Count+1,
maxNumber = 8,
Ipport = host.ToString()
};
string jsonStr = JsonMapper.ToJson(info);
SendMessage(ep, jsonStr);
}
}
}
#endregion
///
/// UDP监听
///
public string Listen(out EndPoint ep)
{
ep = new IPEndPoint(IPAddress.Any, 0);
byte[] data = new byte[1024];
int length = udpSocket.ReceiveFrom(data, ref ep);
string s = Encoding.UTF8.GetString(data, 0, length);
return s;
//clientIplist.Add(remote);
}
public void SendMessage(EndPoint ip, string str)
{
byte[] data = Encoding.UTF8.GetBytes(str);
udpSocket.SendTo(data, ip);
}
public void Close()
{
udpSocket.Close();
}
}
监听连接的TCP端
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;
using LitJson;
public class SocketTcpServer {
Socket tcpServer = null;
public EndPoint host=null;
public SocketTcpServer(EndPoint host)
{
tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tcpServer.Bind(host);
this.host = host;
}
public void Listen()
{
tcpServer.Listen(7);
int key=1;
while(true)
{
Socket tcp= tcpServer.Accept();
Client client=new Client(tcp);
EthernetManager.instance.ClientList.Add(key, client);
}
}
public void Close()
{
tcpServer.Close();
}
}
监听到连接后,创建新的socket和客户端连接,单独分装到了Client中,并且在EthernetManager中用DictionaryClient代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System.Threading;
using System.Text;
public class Client
{
public string username;
private Socket clientSocket;
private Thread t;
private byte[] data = new byte[1024];
public Client(Socket clientSocket)
{
this.clientSocket = clientSocket;
t = new Thread(ReceiveMessage);
t.IsBackground = true;
t.Start();
}
public void ReceiveMessage()
{
while (true)
{
int length = clientSocket.Receive(data);
string str = Encoding.UTF8.GetString(data,0,length);
string[] temp = str.Split(':');
if (temp[0].Equals(EthernetOrder.RequestMemberInfo.ToString()))
{
username = temp[1];
EthernetManager.instance.BroadRoomInfo();
}
}
}
public void SendMessage(string str)
{
byte[] byt = Encoding.UTF8.GetBytes(str);
clientSocket.Send(byt);
}
public void ShutDown()
{
t.Abort();
clientSocket.Close();
}
}
Tcp的客户端代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;
using LitJson;
using System;
public class SocketTcpClient {
public Socket tcpClient=null;
public EndPoint server;
private byte[] byt = new byte[1024];
public SocketTcpClient(EndPoint Server,EndPoint client)
{
tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tcpClient.Bind(client);
server = Server;
}
public void Connect()
{
tcpClient.Connect(server);
Debug.Log("TCP连接成功");
}
public void SendMessage(string str)
{
byte[] data = Encoding.UTF8.GetBytes(str);
tcpClient.Send(data);
}
public void Receive()
{
while(true)
{
int length=tcpClient.Receive(byt);
string str = Encoding.UTF8.GetString(byt,0,length);
EthernetManager.instance.clientReceiveMessage = str;
EthernetManager.instance.ReceiveAction(JsonMapper.ToObject>(EthernetManager.instance.clientReceiveMessage));
}
}
public void Close()
{
tcpClient.Close();
}
}
主要代码基本就这些,其余和UI交互的代码就不贴了,如果这样看比较乱等会我打包上传下,可以直接下载我的源码,运行起来查找引用什么的比较方便。
http://download.csdn.net/detail/kzhenguo/9910612