Android 作为服务器向 UNITY 客户端发送数据
Published in:2023-02-22 |
Words: 3.3k | Reading time: 17min | reading:

Android 作为服务器向 UNITY 客户端发送数据

简介

  • 分层对应协议

    • 应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等

    • 传输层:TCP,UDP

    • 网络层:IP,ICMP,OSPF,EIGRP,IGMP

    • 数据链路层:SLIP,CSLIP,PPP,MTU

  • 通信结构,如图
    alt 如图

  • socket 位置,如图
    alt img

  • 三次握手过程,如图
    alt img

  • 具体到 socket 如下
    alt img

  • Android 使用 Socket 与 UNITY 客户端通信

  • 两端定义相同协议进行数据的交互

  • 在数据发送前使用相应转换方式进行转换

实现方式

Android 服务端

    1. 建立 Android 服务器端 serversocket 服务
1
2
3
4
5
6
try {
server = new ServerSocket(port);
System.out.println("启动server,端口号:" + port);
} catch (IOException e) {
e.printStackTrace();
}
    1. 在创建 serversocket 后,在需要的地方调用方法并开启 socket 服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 启动server
public static ServerThread startServer() {
System.out.println("开启server");
if (serverThread != null) {
System.out.println("server不为null正在重启server");
// 以下为关闭server和socket
shutDown();
}
// 初始化
serverThread = new ServerThread();
new Thread(serverThread).start();
System.out.println("开启server成功");
return serverThread;
}

    1. 新建线程,等待客户端链接,在客户端连接后发送回应消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
   @Override
public void run() {
try {
while (!isExit) {
// 等待连接
System.out.println("等待手机的连接中... ...");
final Socket socket = server.accept();
System.out.println("获取的手机IP地址及端口号:" + socket.getRemoteSocketAddress().toString());
/**
* 因为考虑到多手机连接的情况 所以加入线程锁 只允许单线程工作
*/
new Thread(new Runnable() {

private String text;

@Override
public void run() {
try {
synchronized (this) {
// 在这里考虑到线程总数的计算 也代表着连接手机的数量
++sum;
// 存入到集合和Map中为群发和单独发送做准备
String string = socket.getRemoteSocketAddress().toString();
clientList.add(string);
clientMap.put("2", socket);
}

// 定义输入输出流
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();

// 接下来考虑输入流的读取显示到PC端和返回是否收到
// byte[] buffer = new byte[1024];
// int len;
// while ((len = is.read(buffer)) != -1) {
// text = new String(buffer, 0, len);
//
// System.out.println("收到的数据为:" + text);
// os.write("已收到消息".getBytes("utf-8"));
//
// }

} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("关闭连接:" + socket.getRemoteSocketAddress().toString());
synchronized (this) {
--sum;
String string = socket.getRemoteSocketAddress().toString();
clientMap.remove(string);
clientList.remove(string);
}
}
}
}).start();

}

} catch (IOException e) {
e.printStackTrace();
}
}
    1. 在连接结束后,关闭链接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 关闭server
public void stop() {
isExit = true;
if (server != null) {
try {
server.close();
System.out.println("已关闭server");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void shutDown() {
for (Socket socket : clientMap.values()) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
serverThread.stop();
clientMap.clear();
clientList.clear();
}

    1. 发送简易消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 // 发送消息的方法
public static boolean sendMessage(String name, String mag) {
try {
Socket socket = clientMap.get(name);
// socket.setSendBufferSize(mag.length());
//// socket.setReceiveBufferSize(mag.length());
assert socket != null;
OutputStream outputStream = socket.getOutputStream();
// DataOutputStream out = new DataOutputStream(outputStream);
// outputStream.write(mag.getBytes("utf-8"));
new MessageProtocol().GenProtocol(outputStream,mag);

return true;
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

  • 6.定义消息协议
1
2
3
4
5
6
7
8
9
public class ProtocolHeader implements Serializable {
private static final long serialVersionUID = 1L;
public byte[] version = new byte[10];
public byte[] mask = new byte[10];
public byte[] length = new byte[10];
public byte[] timeStamp = new byte[10];


}
  • 7.在发送消息前,组装报文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  byte[] header = new byte[40];
public void GenProtocol(OutputStream out, String msg) throws Exception {
// int type = 1;

// out.writeInt(type);
// out.write(totalLen);
ProtocolHeader protocolHeader = new ProtocolHeader();
protocolHeader.length = ConvertUtils.intToByteArray(bytes.length);
long timeStamp = System.currentTimeMillis();
protocolHeader.timeStamp = longToBytes(timeStamp);
protocolHeader.mask = "aaaaaaa".getBytes(StandardCharsets.UTF_8);
protocolHeader.version= "1.0.0.0.0".getBytes(StandardCharsets.UTF_8);
byte[] val = addBytes(protocolHeader.version, protocolHeader.mask);
byte[] val2 = addBytes(val, protocolHeader.length);
header = addBytes(val2, protocolHeader.timeStamp);
out.write(header);
// Arrays.toString(serialize(protocolHeader));
// Log.i("sss", String.valueOf(serialize(protocolHeader)));
out.write(bytes,0, bytes.length);
out.flush();
}

/**

*

* @param data1

* @param data2

* @return data1 与 data2拼接的结果

*/

public static byte[] addBytes(byte[] data1, byte[] data2) {

byte[] data3 = new byte[data1.length + data2.length];

System.arraycopy(data1, 0, data3, 0, data1.length);

System.arraycopy(data2, 0, data3, data1.length, data2.length);

return data3;

}

  • 8.使用以下方法发送消息
1
2
ClientManager.sendMessage("id", msg);

    1. ClientManager 工具类完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// 管理连接到服务器中的手机类
public class ClientManager {

private static ServerThread serverThread = null;
private static int sum = 0;
private static Map<String, Socket> clientMap = new HashMap<>();
private static List<String> clientList = new ArrayList<>();

private static class ServerThread implements Runnable {

private ServerSocket server;
private int port = 10086;
private boolean isExit = false;// 一个boolean类型的判断 默认是退出状态false

// 构造方法初始化
public ServerThread() {
try {
server = new ServerSocket(port);
System.out.println("启动server,端口号:" + port);
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 1.获得远程服务器的IP 地址.
* InetAddress inetAddress = socket.getInetAddress();
* 2.获得远程服务器的端口.
* int port = socket.getPort();
* 3. 获得客户本地的IP 地址.
* InetAddress localAddress = socket.getLocalAddress();
* 4.获得客户本地的端口.
* int localPort = socket.getLocalPort();
* 5.获取本地的地址和端口号
* SocketAddress localSocketAddress = socket.getLocalSocketAddress();
* 6.获得远程的地址和端口号
* SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();
*/
@Override
public void run() {
try {
while (!isExit) {
// 等待连接
System.out.println("等待手机的连接中... ...");
final Socket socket = server.accept();
System.out.println("获取的手机IP地址及端口号:" + socket.getRemoteSocketAddress().toString());
/**
* 因为考虑到多手机连接的情况 所以加入线程锁 只允许单线程工作
*/
new Thread(new Runnable() {

private String text;

@Override
public void run() {
try {
synchronized (this) {
// 在这里考虑到线程总数的计算 也代表着连接手机的数量
++sum;
// 存入到集合和Map中为群发和单独发送做准备
String string = socket.getRemoteSocketAddress().toString();
clientList.add(string);
clientMap.put("2", socket);
}

// 定义输入输出流
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();

// 接下来考虑输入流的读取显示到PC端和返回是否收到
// byte[] buffer = new byte[1024];
// int len;
// while ((len = is.read(buffer)) != -1) {
// text = new String(buffer, 0, len);
//
// System.out.println("收到的数据为:" + text);
// os.write("已收到消息".getBytes("utf-8"));
//
// }

} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("关闭连接:" + socket.getRemoteSocketAddress().toString());
synchronized (this) {
--sum;
String string = socket.getRemoteSocketAddress().toString();
clientMap.remove(string);
clientList.remove(string);
}
}
}
}).start();

}

} catch (IOException e) {
e.printStackTrace();
}
}

// 关闭server
public void stop() {
isExit = true;
if (server != null) {
try {
server.close();
System.out.println("已关闭server");
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

// 启动server
public static ServerThread startServer() {
System.out.println("开启server");
if (serverThread != null) {
System.out.println("server不为null正在重启server");
// 以下为关闭server和socket
shutDown();
}
// 初始化
serverThread = new ServerThread();
new Thread(serverThread).start();
System.out.println("开启server成功");
return serverThread;
}


// 发送消息的方法
public static boolean sendMessage(String name, String mag) {
try {
Socket socket = clientMap.get(name);
// socket.setSendBufferSize(mag.length());
//// socket.setReceiveBufferSize(mag.length());
assert socket != null;
OutputStream outputStream = socket.getOutputStream();
// DataOutputStream out = new DataOutputStream(outputStream);
// outputStream.write(mag.getBytes("utf-8"));
new MessageProtocol().GenProtocol(outputStream,mag);
Log.e("AAA","AAA=="+new String(mag.getBytes("utf-8")));
return true;
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

// 群发的方法
public static boolean sendMsgAll(String msg){
try {
for (Socket socket : clientMap.values()) {
OutputStream outputStream = socket.getOutputStream();
outputStream.write(msg.getBytes("utf-8"));
}
return true;
}catch (Exception e){
e.printStackTrace();
}
return false;
}

// 获取线程总数的方法,也等同于<获取已连接了多少台手机>的方法+
public static int sumTotal() {
return sum;
}

// 一个获取list集合的方法,取到所有连接server的手机的ip和端口号的集合
public static List<String> getTotalClients() {
return clientList;
}

public static void shutDown() {
for (Socket socket : clientMap.values()) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
serverThread.stop();
clientMap.clear();
clientList.clear();
}


}

Unity 客户端

    1. 与 Android 建立 socket 连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//创建socket
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

Debug.Log(logHeader + "current ip and port is : ip :" + ip + ",port:" + port);

IPAddress mIp = IPAddress.Parse(ip);
IPEndPoint ip_end_point = new IPEndPoint(mIp, port);

try
{
clientSocket.Connect(ip_end_point);
IsConnected = true;
Debug.Log("连接服务器成功!");
// WriteLog("链接服务器成功!");
threadReceive = new Thread(ReceiveMsgWS);
threadReceive.IsBackground = true;
threadReceive.Start();

}
catch
{
IsConnected = false;
Debug.Log(logHeader + "连接服务器失败");
// Toast.Show("服务器连接失败!", 1f);
// WriteLog("链接服务器失败");
return;
}
    1. 接收 Android 消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Byte[] buffer_length;///buffer head
Byte[] buffer;
string data;
int length = 0;
bool messageStart = false;
string const_str;
int status = 0;
while (true)
{

buffer_length = new Byte[1024];
// clientSocket.get
status = clientSocket.Receive(buffer_length);
if (status == 40)
{
const_str = System.Text.Encoding.ASCII.GetString(buffer_length).Substring(0, 20);//,(buffer, 0, 12, "ASCII")
if (const_str.Equals("1.0.0.0.0aaaaaaa"))
{
messageStart = true;
}
}
else
{
continue;

}

if (messageStart)
{


length = SocketUtils.byteArrayToInt(buffer_length, 12, 4);
// length = headerData.length;
buffer = new Byte[length];
int readed_length = 0;

while (readed_length < buffer.Length)
{
status = clientSocket.Receive(buffer, readed_length, buffer.Length - readed_length, SocketFlags.None);
if (status <= 0)
{
return;
}
readed_length += status;
}

if (buffer.Length > 0 && readed_length == buffer.Length)
{
data = System.Text.Encoding.UTF8.GetString(buffer);
data_send(data);
}


}
}
    1. 断开连接
1
2
3
4
5
6
7
if (null != clientSocket && clientSocket.Connected)
{
Debug.Log(logHeader + "服务器连接已断开!");
// Toast.Show("服务器连接断开!", 1f);
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
    1. 发送消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if (IsConnected == false)
{
// Debug.Log("发送数据错误,服务器未连接!");
// WriteLog("服务器未连接!");
return;
}

if (null == msg)
{
Debug.Log(logHeader + "发送数据错误,消息不能为空!");
//WriteLog("消息不能为空");
return;
}

try
{
clientSocket.Send(msg);
//Debug.Log("发送成功!");
return;

// WriteLog("发送成功!");
}
catch
{
Debug.Log(logHeader + "发送数据错误,消息编码错误!");
// WriteLog("消息编码错误!");
IsConnected = false;
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
return;
}
    1. 整数据转 byte 数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14

public static int byteArrayToInt(byte[] byteArray, int offset, int length)
{
if (length != 4 || (offset + length) > byteArray.Length)
{
return 0;
}
int value = (byteArray[offset] & 0xFF);
value |= (byteArray[offset + 1] & 0xff) << 8;
value |= (byteArray[offset + 2] & 0xff) << 16;
value |= (byteArray[offset + 3] & 0xff) << 24;
return value;

}
  • 6.接收处理类代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258

using System.Net;
using System.Net.Sockets;
using System.Threading;
using System;
using System.Text;
using UnityEngine;
using Newtonsoft.Json;
// using System;
using System.Collections.Generic;
// using Newtonsoft.Json;
using cccc.util;

///
///socket 工具类
///

namespace ccccc.ccc.internet
{
public class SocketClient
{
#region 工具类参数
//是否登录
public bool login_flag = false;
private static Socket clientSocket;
//是否已连接的标识

public string logHeader = "file:SocketClient.cs: ";
public string result;
public bool IsConnected = false;
//握手消息
public Thread threadReceive;
//主数据
public Thread threadReceive_get_data;
//解析消息

// IMessage IMperson = new DetectionInfo();
// DetectionInfo p1 = new();
#endregion

/// <summary>
/// struct methods
/// </summary>
public SocketClient()
{
//创建socket
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}

/// <summary>
/// 连接指定IP和端口的服务器
/// </summary>
/// <param name="ip">IP地址</param>
/// <param name="port">端口号</param>
public void Connect(string ip, int port)
{
Debug.Log(logHeader + "current ip and port is : ip :" + ip + ",port:" + port);
IPAddress mIp = IPAddress.Parse(ip);
IPEndPoint ip_end_point = new IPEndPoint(mIp, port);

try
{
clientSocket.Connect(ip_end_point);
IsConnected = true;
Debug.Log("连接服务器成功!");
// WriteLog("链接服务器成功!");
threadReceive = new Thread(ReceiveMsgWS);
threadReceive.IsBackground = true;
threadReceive.Start();

}
catch
{
IsConnected = false;
Debug.Log(logHeader + "连接服务器失败");
// Toast.Show("服务器连接失败!", 1f);
// WriteLog("链接服务器失败");
return;
}
}

/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
if (null != clientSocket && clientSocket.Connected)
{
Debug.Log(logHeader + "服务器连接已断开!");
// Toast.Show("服务器连接断开!", 1f);
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
}

/// <summary>
/// 发送数据给服务器
/// </summary>
/// <param name="msg">发送消息内容</param>
public void Send(string msg)
{

if (IsConnected == false)
{
Debug.Log(logHeader + "发送数据错误,服务器未连接!");
// Toast.Show("无法发送数据,服务器未连接!", 1f);
// WriteLog("服务器未连接!");
return;
}

if (null == msg)
{
Debug.Log(logHeader + "发送数据错误,消息不能为空!");
// WriteLog("消息不能为空!");
return;
}

try
{
// clientSocket.Send(ProtobufEncoding.Encode(msg));
}
catch
{
Debug.Log(logHeader + "发送数据错误,消息编码错误!");
// WriteLog("消息编码错误!");
IsConnected = false;
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}

}

/// <summary>
/// 发送数据给服务器
/// </summary>
/// <param name="msg">发送消息内容</param>
public void SendLogin(byte[] msg)
{

if (IsConnected == false)
{
// Debug.Log("发送数据错误,服务器未连接!");
// WriteLog("服务器未连接!");
return;
}

if (null == msg)
{
Debug.Log(logHeader + "发送数据错误,消息不能为空!");
//WriteLog("消息不能为空");
return;
}

try
{
clientSocket.Send(msg);
//Debug.Log("发送成功!");
return;

// WriteLog("发送成功!");
}
catch
{
Debug.Log(logHeader + "发送数据错误,消息编码错误!");
// WriteLog("消息编码错误!");
IsConnected = false;
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
return;
}
// return;
}

/// <summary>
/// 通过线程服务器数据获取
/// </summary>
private void ReceiveMsgWS()
{
// string length_str;
Byte[] buffer_length;///buffer head
Byte[] buffer;
string data;
int length = 0;
bool messageStart = false;
string const_str;
int status = 0;
while (true)
{

buffer_length = new Byte[1024];
// clientSocket.get
status = clientSocket.Receive(buffer_length);
if (status == 24)
{
const_str = System.Text.Encoding.ASCII.GetString(buffer_length).Substring(0, 12);//,(buffer, 0, 12, "ASCII")
if (const_str.Equals("1.0.ccccc"))
{
messageStart = true;
}
}
else
{
continue;

}

if (messageStart)
{


length = SocketUtils.byteArrayToInt(buffer_length, 12, 4);
// length = headerData.length;
buffer = new Byte[length];
int readed_length = 0;

while (readed_length < buffer.Length)
{
status = clientSocket.Receive(buffer, readed_length, buffer.Length - readed_length, SocketFlags.None);
if (status <= 0)
{
return;
}
readed_length += status;
}

if (buffer.Length > 0 && readed_length == buffer.Length)
{
data = System.Text.Encoding.UTF8.GetString(buffer);
data_send(data);
}


}
}
}

/// <summary>
/// 发送数据至主线程
/// </summary>
/// <param name="p1">数据</param>
private void data_send(string p1)
{
// var event1 = "event1";
var event2 = "event2";
var thread = Unity.Threading.EasyThread.GetInstance();
//注册子线程监听
thread.childRemote.Once(event2, () =>
{

thread.mainRemote.Send(event2, p1);
});

//推送子线程事件,激活子线程监听方法
thread.childRemote.Send(event2);

}

}
Prev:
关于 Unity 中触摸屏幕坐标与物体世界坐标的判定方法(以矩形为例)
Next:
Integrating Unity as a library into standard Android app