[Android_网络] Socket通信 - PC与Device/模拟器

条件:

 1. 服务端:PC

 2. 客户端:模拟器/真实Android设备

目标:

 通过socket API 数据单向传输到服务端。

语言:

 1. 客户端采用Java

 2. 服务端采用C/C++

实现:
 由于模拟器的特殊性,因此我们需要将模拟器的端口映射到主机的某个端口,这样才可以和模拟器相互通信,而真实设备中就无需此映射设置。

 1. 端口映射:
  在android sdk的platform-tools下有一个adb可执行程序,我的路径是%SDK%/platform-tools/adb,运行如下命令进行端口映射:

    adb forward tcp:9400 tcp:9400

  上面命令的意思是将模拟器的9400端口映射到PC的9400端口,这样模拟器向192.168.1.X:9400发送的数据就会被映射到PC的9400端口,而我们的PC只要监听本地的9400端口即可

 2. 代码:
  下面是客户端的JAVA代码

    src/SocketEmulatorOrDevice.java

 1 package org.goodboy.android.demo5_network;
 2 
 3 import java.io.IOException;
 4 import java.io.PrintStream;
 5 import java.net.Socket;
 6 import java.net.UnknownHostException;
 7 
 8 import android.app.Activity;
 9 import android.os.Bundle;
10 import android.view.View;
11 import android.widget.Button;
12 import android.widget.EditText;
13 import android.widget.Toast;
14 
15 public class SocketDevice extends Activity {
16     /* 服务器地址 */
17     private final String SERVER_HOST_IP = "192.168.1.104";
18 
19     /* 服务器端口 */
20     private final int SERVER_HOST_PORT = 9400;
21 
22     private Button btnConnect;
23     private Button btnSend;
24     private EditText editSend;
25     private Socket socket;
26     private PrintStream output;
27 
28     public void toastText(String message) {
29         Toast.makeText(this, message, Toast.LENGTH_LONG).show();
30     }
31 
32     public void handleException(Exception e, String prefix) {
33         e.printStackTrace();
34         toastText(prefix + e.toString());
35     }
36 
37     /** Called when the activity is first created. */
38     @Override
39     public void onCreate(Bundle savedInstanceState) {
40         super.onCreate(savedInstanceState);
41         setContentView(R.layout.activity_main);
42 
43         initView();
44 
45         btnConnect.setOnClickListener(new Button.OnClickListener() {
46             @Override
47             public void onClick(View v) {
48                 initClientSocket();
49             }
50         });
51 
52         btnSend.setOnClickListener(new Button.OnClickListener() {
53             @Override
54             public void onClick(View v) {
55                 sendMessage(editSend.getText().toString());
56             }
57         });
58     }
59 
60     public void initView() {
61         btnConnect = (Button) findViewById(R.id.btnConnect);
62         btnSend = (Button) findViewById(R.id.btnSend);
63         editSend = (EditText) findViewById(R.id.sendMsg);
64 
65         btnSend.setEnabled(false);
66         editSend.setEnabled(false);
67     }
68 
69     public void closeSocket() {
70         try {
71             output.close();
72             socket.close();
73         } catch (IOException e) {
74             handleException(e, "close exception: ");
75         }
76     }
77 
78     private void initClientSocket() {
79         try {
80             /* 连接服务器 */
81             socket = new Socket(SERVER_HOST_IP, SERVER_HOST_PORT);
82 
83             /* 获取输出流 */
84             output = new PrintStream(socket.getOutputStream(), true, "utf-8");
85 
86             btnConnect.setEnabled(false);
87             editSend.setEnabled(true);
88             btnSend.setEnabled(true);
89         } catch (UnknownHostException e) {
90             handleException(e, "unknown host exception: " + e.toString());
91         } catch (IOException e) {
92             handleException(e, "io exception: " + e.toString());
93         }
94     }
95 
96     private void sendMessage(String msg) {
97         output.print(msg);
98     }
99 }
SocketEmulatorOrDevice.java

 

    layout/activity_main.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="fill_parent"
 4     android:layout_height="fill_parent"
 5     android:orientation="vertical" >
 6 
 7     <TextView
 8         android:layout_width="fill_parent"
 9         android:layout_height="wrap_content"
10         android:text="@string/hello" />
11 
12     <Button
13         android:id="@+id/btnConnect"
14         android:layout_width="fill_parent"
15         android:layout_height="wrap_content"
16         android:text="@string/connect" />
17 
18     <EditText
19         android:id="@+id/sendMsg"
20         android:layout_width="match_parent"
21         android:layout_height="wrap_content"
22         android:inputType="text" />
23 
24     <Button
25         android:id="@+id/btnSend"
26         android:layout_width="fill_parent"
27         android:layout_height="wrap_content"
28         android:text="@string/send" />
29 
30 </LinearLayout>
activity_main.xml

 

  在AndroidManifest.xml中需添加访问网络权限,由于SDK API如果调高了会报出ERROR,因涉及到网络影响主UI线程,可以将minSdkVersion和targetSdkVersion调小一些,例如8,这样可以暂时避免报错,但是不推荐这样修改,可采取另开启子线程/AsyncTask等方式进行网络连接来避免该ERROR出现:

1 <uses-permission android:name="android.permission.INTERNET" />
AndroidManifest.xml

 

  把代码编译。

  下载:APK

  安装到模拟器/真实设备中即可完成客户端的配置。

 3. 服务端的C++代码:
   server.c

 1 #include <sys/types.h>
 2 #include <sys/socket.h>
 3 #include <netinet/in.h>
 4 #include <arpa/inet.h>
 5 #include <unistd.h>
 6 #include <stdlib.h>
 7 #include <stdio.h>
 8 
 9 #define PORT 9400
10 #define MAX_BUFFER 1024
11 
12 
13 int main()
14 {
15   /* create a socket */
16   int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
17   
18   struct sockaddr_in server_addr;
19   server_addr.sin_family = AF_INET;
20   server_addr.sin_addr.s_addr = inet_addr("192.168.1.104");
21   server_addr.sin_port = htons(PORT);
22   
23   /* bind with the local file */
24   bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
25   
26   /* listen */
27   listen(server_sockfd, 5);
28   
29   int size;
30   char buffer[MAX_BUFFER + 1];
31   int client_sockfd;
32   struct sockaddr_in client_addr;
33   socklen_t len = sizeof(client_addr);
34   
35   /* accept a connection */
36   printf("waiting connection...\n");
37   client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);
38   printf("connection established!\n");
39     
40   while(1)
41   {
42     printf("waiting message...\n");
43     
44     /* exchange data */
45     size = read(client_sockfd, buffer, MAX_BUFFER);
46     buffer[size] = '\0';
47     printf("Got %d bytes: %s\n", size, buffer);
48   }
49 
50   /* close the socket */
51   close(client_sockfd);
52       
53   return 0;
54 }
server.c

 

   Makefile

1 all: server.c
2     gcc -g -Wall -o server server.c
3 
4 clean:
5     rm -rf *.o server
Makefile

  然后在Cygwin中进行编译,得到server.exe可执行文件。

  下载:server.exe

 4. 效果

  首先运行PC服务端server.exe,然后运行模拟器/真实设备的应用。

  PC服务端正等待模拟器/真实设备的连接。

  未连接之前模拟器/真实设备的文本编辑框和发送消息按钮都是不可用的。



  点击链接按钮进行连接,成功连接后我们文本编辑框和发送消息按钮可用了。

  PC从等待连接状态变成了等待消息状态。



  在文本编辑框中输入待发送内容后按发送消息按钮,服务端就会打印出模拟器/真实设备发来的文本消息。



注视:

 如果模拟器/真实设备连接时提示Connect refused或showStatusIcon on inactive InputConnection,那么把模拟器/真实设备的应用和PC端上的server都断开关闭掉,重新开进行链接发送即可。

posted @ 2016-02-26 13:20  It's_Lee  阅读(634)  评论(0编辑  收藏  举报