最近去了一趟上海,外滩的景色还是很nice的,非常喜欢这种具有历史感的建筑。
很久没写文章,小姐姐最近打代码比较少,搞项管去。。。。。。程序媛才是小姐姐的最爱,记录文档写起来,哈哈哈。
在一些工具类开发中,经常涉及不同设备之间的通信,今天我就针对安卓手机和PC端的通信,做一些文档说明。
在安卓手机和PC端的TCP通信中,PC做为接收端(服务器Server端),以Android设备做为发送端(客户Client端)。
关于PC端开发工具,我用的是eclipse,大家也可以用IntelliJ IDEA。
IntelliJ IDEA:业界公认最好的Java开发工具,平时用的最多。可以安装大量插件丰富功能,开发前端应用也不在话下!
demo再文章最后附上。
1、什么是TCP/IP协议?
百度百科的解释:Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。
TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。
通俗而言:
TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。
而IP是给因特网的每一台电脑规定一个地址。
2、什么是Socket?
Socket是应用层与TCP/IP协议簇通讯的中间抽象层,Socket是一组接口,在设计模式中,Socket的设计就是门面模式,它把复杂的TCP/IP协议簇的内容隐藏在套接字接口后面,用户无需关心协议的实现,只需使用Socket提供的接口即可。
Socket的类型有两种,一种是面向连接的TCP应用服务,一种是面向无连接的UDP(User Data Package)应用服务。通俗的理解就是,TCP方式是打电话(连接性),UDP方式是发短信(无连接)。
Ok,以上是一些概念的介绍,那么接下来就看下如何在Android上利用TCP/IP协议使用Socket与Server进行通讯吧!今天我们要使用的是面向连接的TCP方式。首先,在本机建立一个Java项目作为Server Client(这一部分也可以是用C或其他语言写的PC端TCP工具),JAVA代码如下:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class Server implements Runnable {
public static final int SERVERPORT = 3333;
public void run() {
try {
System.out.println("Server: Connecting...");
ServerSocket serverSocket = new ServerSocket(SERVERPORT);
while (true) {
System.out.println("Server:Connected.");
Socket client = serverSocket.accept();
System.out.println("Server: Receiving...");
try {
BufferedReader in = new BufferedReader(
new InputStreamReader(client.getInputStream()));
String str = in.readLine();
System.out.println("Server: Received: '" + str + "'");
} catch (Exception e) {
System.out.println("Server: Error");
e.printStackTrace();
} finally {
client.close();
System.out.println("Server: Done.");
}
}
} catch (Exception e) {
System.out.println("Server: Error");
e.printStackTrace();
}
}
public static void main(String a[]) {
Thread desktopServerThread = new Thread(new Server());
desktopServerThread.start();
}
}
以上设置PC端的端口为3333,IP地址可以通过计算机——网络查询,也可以ifconfig你的电脑
如我的是MAC电脑,在系统偏好—网络:
然后是Android客户端的实现:
import android.app.Activity;
import android.os.Bundle;
import android.os.StrictMode;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.xiaoniu.wifihotspotdemo.R;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class SocketAndrodActivity extends Activity {
TextView text;
EditText input,address,port;
Socket socket;
@Override
public void onCreate(Bundle savedInstanceState) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads().detectDiskWrites().detectNetwork()
.penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects().detectLeakedClosableObjects()
.penaltyLog().penaltyDeath().build());
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button) this.findViewById(R.id.btn_send);
text=(TextView)findViewById(R.id.receive);
input=(EditText)findViewById(R.id.input);
address=(EditText)findViewById(R.id.address);
port=(EditText)findViewById(R.id.port);
address.setText("162.30.200.26");
port.setText("205");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
text.setText("Client:Connecting");
//IP地址和端口号(对应服务端),我这的IP是本地路由器的IP地址
String addStr=address.getText().toString().trim();
int portStr=Integer.parseInt(port.getText().toString().trim());
socket = new Socket(addStr, portStr);
if(socket==null){
Log.e("error","socket null");
return;
}
//发送给服务端的消息
String message = input.getText().toString();
try {
text.setText("Client Sending: '" + message + "'");
//第二个参数为True则为自动flush
PrintWriter out = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream())), true);
out.println(message);
// out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭Socket
if(socket!=null){
socket.close();
}
text.append(" Client:Socket closed");
}
} catch (UnknownHostException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
以上的码块是我在出现android.os.NetworkOnMainThreadException:问题后,看到网上的一个分享,该分析认为:
一般主线程(跑UI的线程)不推荐使用网络相关的服务,因为使用到网络可能会导致运行UI的线程很卡,最终很可能会出现ARN(Application Not Responding)的提示框。
* 所以如果在主线程中运用到了网络相关的服务(例如本例的TCP连接),会在LogCat中出现NetworkOnMainThreadException的异常。
* <p>
* StrictMode则是Android专门为开发者提供的工具类,你可以决定当出现诸如NetworkOnMainThreadException异常时,将异常存至LogCat中,
* 而不是直接终止你的安卓应用的运行。由于本程序为示例代码,故不在安卓虚拟机中加入线程之类的东西去开启TCP客户端,
* 而用了StrictMode忽略NetworkOnMainThreadException异常。
因此在 super.onCreate(savedInstanceState);前加了
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads().detectDiskWrites().detectNetwork()
.penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects().detectLeakedClosableObjects()
.penaltyLog().penaltyDeath().build());
感谢他的分享。
我最终采用的方式是,多线程处理方式,handle更新UI状态,完整代码如下:
import com.xiaoniu.wifihotspotdemo.R;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
/**
* author Created by michelle on 2019/5/23.
* email: 1031983332@qq.com
*/
public class SocketAndrodActivity extends Activity {
private static final int CONNECTING = 0;
private static final int SENDING = 1;
private static final int CLOSE = 2;
private static final int RECEIVE = 3;
TextView text;
EditText input, address, port;
Socket socket;
String addStr,sendMsg,receiveMsg;
int portStr;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == CONNECTING) {
text.setText("正在连接中......");
}else if(msg.what==SENDING){
text.setText("Client Sending: '" + sendMsg + "'");
}else if(msg.what==CLOSE){
text.append("socket close");
}else if(msg.what==RECEIVE){
text.setText("Client Receiveing: '" + receiveMsg + "'");
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//方法一、用StrictMode模式,调用以下码块,删除Thread和handle方式
// setInternet();
setContentView(R.layout.main);
Button button = (Button) this.findViewById(R.id.btn_send);
text = (TextView) findViewById(R.id.receive);
input = (EditText) findViewById(R.id.input);
address = (EditText) findViewById(R.id.address);
port = (EditText) findViewById(R.id.port);
// address.setText("162.30.200.26");
// port.setText("205");
address.setText("162.30.202.220");
port.setText("3333");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addStr = address.getText().toString().trim();
portStr = Integer.parseInt(port.getText().toString().trim());
//方法二、用多线程的方式
new WorkThread().start();
}
});
}
//工作线程
private class WorkThread extends Thread {
@Override
public void run() {
//处理比较耗时的操作
//数据处理完成后,关于UI的更新要通过handler发送消息
Message msg = new Message();
Message msg1 = new Message();
Message msg2 = new Message();
msg.what = CONNECTING;
handler.sendMessage(msg);
try {
socket = new Socket(addStr, portStr);
if (socket == null) {
Log.e("error", "socket null");
return;
}
//发送给服务端的消息
sendMsg = input.getText().toString();
msg1.what = SENDING;
handler.sendMessage(msg1);
PrintWriter out = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream())), true);
out.println(sendMsg);
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭Socket
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
msg2.what = CLOSE;
handler.sendMessage(msg2);
}
}
}
}
布局为:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入ip地址"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:hint="请输入端口号"
android:id="@+id/port"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:hint="请输入发送的信息"
android:id="@+id/input"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:id="@+id/btn_send"
android:layout_marginTop="10dp"
android:text="send"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/receive"
android:text="收到信息为: "
/>
</LinearLayout>
在这个过程,需要注意的点有:
1、权限
在AndroidManifest.xml文件中,必须加入
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
2、IP地址和端口必须正确
查PC端IP的方式,我这边就不再复述了。
值得说明的是:有的window上的PC端工具装在mac的虚拟机上面,虚拟机识别的ip不是mac的ip地址,安卓没办法链接。
3、线程问题
处理数据用子线程,更新UI用主线程。在子线程中可以通过handle进行传数据给主线程。