本人曾经做了一个基于MPVd的C#开发的播放器,用于自娱自乐,后来又用websocket 写了个简单的远程控制器。由于websocket 要依赖于浏览器,因此有诸多不便,后来又用flutter写了一个,方便多了。
下面介绍具体实现。
1、通信机制建立
1.1 基本说明:
服务端代码为C#,客户端为Flutter(dart)
服务端:即播放器端(PC端)
客户端:即遥控端(手机端),目前只支持一个手机
通信方式:UDP和TCP
第一步:获得各自host的IP地址
第二步:服务端开启UDP 广播监听和TCP监听到指定端口
第三步:客户端发送广播索取服务端IP地址
第四步:服务端接收到索取IP地址命令,然后发送服务端IP地址到客户端
第五步:客户端根据返回的IP地址建立和服务端TCP 连接(socket)
1.2代码示例
1.2.1 获得IP地址
服务端代码:
private string getLocalIP()
{
string localIP = string.Empty;
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0))
{
socket.Connect("8.8.8.8", 65530);
IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint;
localIP = endPoint.Address.ToString();
}
return localIP;
}
客户端代码:
Note:这里是获得局域网的WIFI IP,这个显然对于大多数用户是正确的。手机不可能用有线方式也不可能使用WALN的IP,因此只获得WIFI IP, 没有WIFI无法使用。另外笔者并没有找到获得WIFI的优雅方法,是采用了搜索的方法获得的,感觉有瑕疵。
....
import 'dart:io';
.....
Future GetLoalWfiIP() async {
for (var interface in await NetworkInterface.list()) {
if (interface.name.toUpperCase().contains("WLAN")) {
//print(interface.addresses.first.address);
m_localIP = interface.addresses.first.address;
return;
}
// print('== Interface: ${interface.name} ==');
// for (var addr in interface.addresses) {
// print(
// '${addr.address} ${addr.host} ${addr.isLoopback} ${addr.rawAddress} ${addr.type.name}');
// }
// }
}
}
1.2.2 开启监听
UDP监听:
Note:代码中包含了笔者定义的简单通信协议
public void SetUDPSvr()
{
try
{
Thread receThread = new Thread(new ThreadStart(RecvUDPThread));
receThread.IsBackground = true;
receThread.Start();
}catch(Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
// 实际监听在这里,采用了定义好的端口 8090,这个端口是要服务器和客户端实现协商好。
private void RecvUDPThread()
{
m_udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, 8009));
//广播地址监听
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);
//m_udpClient.Client.ReceiveTimeout = 1000*60;
m_Stopped = false;
while (!m_Stop)
{
try
{
byte[] buf = m_udpClient.Receive(ref endpoint);
string msg = Encoding.Default.GetString(buf);
//parser the msg
//if(msg == CMD_ICIP)
{
var rmtIP = endpoint.Address.ToString();
var rmtPort = endpoint.Port;
// send the server IP to the remote , simple protocol: ISIP:###.###.#.#
var sndMsg = string.Format("{0}:{1}{2}", appCfg.m_SVR_CMD_ISIP, m_HostIP,appCfg.m_CMD_SEND_END_LBL);
endpoint.Port = 8008;
buf = Encoding.Default.GetBytes(sndMsg);
m_udpClient.Send(buf, sndMsg.Length, endpoint);
}
//The simple protocol:IL:IP ask for the IP address of the host , IL is the indicator
var strReceive = string.Format((endpoint as IPEndPoint).Address.ToString() + ":" + (endpoint as IPEndPoint).Port.ToString());
Console.WriteLine(strReceive);
}catch(SocketException e)
{
// do nothing
if (e.SocketErrorCode == SocketError.TimedOut)
{
//do nothing
Console.WriteLine(e.Message);
}
else
{
Console.WriteLine(e.Message);
}
}
}
m_Stopped = true;
}
1.2.3 客户端UDP方式索取服务端IP地址
Future<void> getSvrIPViaUdp() async {
await RawDatagramSocket.bind(InternetAddress.anyIPv4, AppCfg.m_UdpSvrPort)
.then((RawDatagramSocket socket) async {
await GetLoalWfiIP(); // get m_localIP
// 这里首先获得手机IP,然后将最后一位改写为255,生成一个子网内的广播地址。
var pos = m_localIP.lastIndexOf(".");
var udpIP = "${m_localIP.substring(0, pos + 1)}255";
var updAdr = InternetAddress(udpIP);
var sendStr = "Hello,I am here.";
List<int> bytes = utf8.encode(sendStr);
//m_RcvDat.clear();
socket.broadcastEnabled = true;
//建立监听事件,发送广播的命令在后面。
socket.listen((RawSocketEvent e) {
Datagram? d = socket.receive();
if (d == null) {
return;
}
String message = String.fromCharCodes(d.data).trim();
//print('Datagram from ${d.address.address}:${d.port}: $message');
// get the IP of the server
var keyWord = "${AppCfg.m_SVR_CMD_ISIP}:";
var len = keyWord.length;
var endPos = message.lastIndexOf(AppCfg.m_CMD_SEND_END_LBL);
//获得服务端传送过来的IP,记录下来。
if (endPos >= 0) {
AppCfg.m_TcpSvrIP = message.substring(len, endPos);
}
//print(m_svrIP);
});
// 发送广播
socket.send(bytes, updAdr, AppCfg.m_UdpClntPort);
});
}
1.2.4 服务端发送IP地址到客户端
此代码在上述1.2.2 开启监听代码块中,如图示
1.2.5 客户端和服务端建立TCP连接
Future<void> setupConnection() async {
if (m_isConnect) return;
//await getSvrIPViaUdp();
//索取服务器IP地址
while (AppCfg.m_TcpSvrIP == "") {
await getSvrIPViaUdp();
await Future.delayed(const Duration(milliseconds: 100), () {
print("Waiting for getting m_srvip.....\r\n");
});
}
//获得IP地址后,建立和服务端的TCP 连接
if (AppCfg.m_TcpSvrIP != "" && !m_isConnect) {
m_socket = await Socket.connect(AppCfg.m_TcpSvrIP, AppCfg.m_TcpSvrPort);
m_isConnect = true;
//await for the first image sent from the server
m_socket.listen(_receivedMsgHandler,
onError: _errorHandler, onDone: _doneHandler, cancelOnError: false);
}
}
接收处理请看后续文章。
MaraSun BJFWDQ
兔年将至,预祝各位程序猿春节快乐!