[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 }
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>
在AndroidManifest.xml中需添加访问网络权限,由于SDK API如果调高了会报出ERROR,因涉及到网络影响主UI线程,可以将minSdkVersion和targetSdkVersion调小一些,例如8,这样可以暂时避免报错,但是不推荐这样修改,可采取另开启子线程/AsyncTask等方式进行网络连接来避免该ERROR出现:
1 <uses-permission android:name="android.permission.INTERNET" />
把代码编译。
下载: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 }
Makefile
1 all: server.c 2 gcc -g -Wall -o server server.c 3 4 clean: 5 rm -rf *.o server
然后在Cygwin中进行编译,得到server.exe可执行文件。
下载:server.exe
4. 效果
首先运行PC服务端server.exe,然后运行模拟器/真实设备的应用。
PC服务端正等待模拟器/真实设备的连接。
未连接之前模拟器/真实设备的文本编辑框和发送消息按钮都是不可用的。
点击链接按钮进行连接,成功连接后我们文本编辑框和发送消息按钮可用了。
PC从等待连接状态变成了等待消息状态。
在文本编辑框中输入待发送内容后按发送消息按钮,服务端就会打印出模拟器/真实设备发来的文本消息。
注视:
如果模拟器/真实设备连接时提示Connect refused或showStatusIcon on inactive InputConnection,那么把模拟器/真实设备的应用和PC端上的server都断开关闭掉,重新开进行链接发送即可。