Android多个模拟器网络结构和通信

伙伴最近遇到一个小问题,多个Android模拟器如何进行网络通信。虚拟机不习惯用,平时一直用真机测试,第一次尝试解决这种问题。

问题分析(着急要结论的直接看后面的小结)

记得用Android模拟器访问本机http服务用的网址是10.0.2.2,所以Android模拟器有自己的虚拟网络。两个模拟器网络独立,相互通信,一定要通过本机的网络。另外Android模拟器默认情况下不转发10.0.2.2的udp、tcp等协议,需要进行手动配置。

我们先来看看两个Android模拟器的网络结构: Android模拟器的网络结构

通过上图可以看到,要实现多个模拟器上应用网络通信,需要设置一下虚拟路由器,让虚拟机1的网络请求,可以通过虚拟路由访问到虚拟机2。

在这里,我们暴露虚拟机16666端口到本机网络,让虚拟机2可以发送消息到6666端口。 在这里参考一下解决两个 Android 模拟器之间无法网络通信的问题和官方文档Set Up Android Emulator Networking

配置Android模拟器

1、安装telnet工具

window自带telnet工具,但是需要启用,启用方法自行百度。 mac telnet工具已经移除,需要用brew安装一下。brew安装方法参考Mac OS X系统 HomeBrew的安装和简单使用。 安装telnet一句命令搞定:

brew install telnet

2、使用telnet命令

使用telnet命令连接到模拟器:

telnet localhost 5554
小问题:

你说5554是啥?

5554是模拟器与PC通信的端口号,在窗口栏上可以看到。
例如:Android Emulator - Nexus_5X_API_24_1:5554。

什么?你说连接不上,显示下面这些?

Trying 127.0.0.1...
telnet: connect to address 127.0.0.1: Connection refused
Trying ::1...
telnet: connect to address ::1: Connection refused
telnet: Unable to connect to remote host

请先打开Android模拟器好不,我们要配置的就是模拟器。 连接后,很有可能显示如下:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Android Console: Authentication required
Android Console: type 'auth <auth_token>' to authenticate
Android Console: you can find your <auth_token> in
'/Users/doc881/.emulator_console_auth_token'
OK

这是缺少权限,vi ~/.emulator_console_auth_token查看一下自己的,然后在这里输入auth ,例如:

auth NJ6QjlVf3/fWFEin

返回如下内容:

Android Console: type 'help' for a list of commands
OK

这时候我们已经进入到模拟器的console,可以配置路由转发规则了。

3、配置虚拟路由

命令为redir add <协议名>:<PC端口号>:<Android模拟器内网端口号>,例如:

redir add udp:6666:6666

这样就可以通过本机网络的6666端口,访问Android模拟器的6666端口了。对本地网络6666端口的tcp、udp等连接请求,也会被转发到模拟器的6666端口。 redir的使用还是比较灵活的,某种情况下,要将pc端的5555端口映射到Android模拟器的6666端口上,可以使用如下命令:

redir add udp:5000:6666

这里不要把端口号的顺序整错了。

4、小结

映射虚拟机16666端口到本机网络的6666端口

telnet localhost 5554
auth NJ6QjlVf3/fWFEin
redir add udp:6666:6666

到这里,端口的配置就完成了。下面写个测试程序来试验一下

简单的测试程序

下面是一个服务端和一个客户端的简单实现。先来看看效果:

Android模拟器的网络结构

伙伴的例子里用的是DatagramSocket,我这里也使用同样的类。

Tip:DatagramSocket在通信的时候,需要给定一个数据对象,而不是数据流或byte数组。

服务器端监听6666端口

  try {
      serverSocket = new DatagramSocket(6666);
  } catch (SocketException e) {
      e.printStackTrace();
  }
  check_thread = new Thread(new Runnable() {
      @Override
      public void run() {
          while (true){
              byte[] receiveData = new byte[1024];
              Log.i("MainActivity","start recieving");
              DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);

              try {

                  serverSocket.receive(receivePacket);

                  InetAddress sourceIPAddress = receivePacket.getAddress();


                  receiveData = receivePacket.getData();

                  String messageNow = new String(receiveData);
                  Log.i("MainActivity","messageNow="+messageNow);
                  message.add(messageNow);

                  handler.sendEmptyMessage(SHOW_MESSAGE);


              } catch (Exception e) {
              }
          }
      }
  });
  check_thread.start();

客户端向10.0.2.2的6666端口发送消息

  String host = client_host_input.getText().toString();
  int port = Integer.parseInt(client_port_input.getText().toString());
  byte[] content = client_content_input.getText().toString().getBytes(StandardCharsets.UTF_8);

  try {
      InetAddress address = InetAddress.getByName(host);
      DatagramPacket sendPacket = new DatagramPacket(content,
                    content.length, address, port);
      clientSocket.send(sendPacket);

      client_content_input.setText("");

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

具体的代码看AndroidNetworkTest


作者

NewImaging
  • Shadust
  • 有你,真好~

评论