Android TCP通信的简单实例以及常见问题[超时/主线程阻塞]

来源:互联网 发布:java license 框架 编辑:程序博客网 时间:2024/06/06 03:46

个人更喜欢着眼于实例,从最简单的开始,一步步进行测试。

理论什么的先放一边,把程序跑起来再说。只有跑起来了,才会有动力去继续往下学,参透整个代码的运行机制。


本次的实例目标是——

模拟一个PC服务器与android端的通信,目标是尽量的做到精简,使代码仅留下所需核心部分,降低笔记代码的阅读难度。

--------------------------

>【实例】

PC上的服务器的代码:

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket;public class SocketServer {    //监听端口12345    private static final int PORT = 12345;        public static void main(String[] args) {          try {              System.out.println("等待客户端");              ServerSocket serverSocket = new ServerSocket(PORT);              Socket clientSocket = serverSocket.accept();              System.out.println("客户端上线");            while (true) {                  //循环监听客户端请求                  try {                      //获取输入流                      BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));                      //获取从客户端发来的信息                      String msg = in.readLine();                     System.out.println("客户端消息:"+msg);                  } catch (IOException e) {                      System.out.println("读写错误");                      e.printStackTrace();                  } finally {                    serverSocket.close();                    clientSocket.close();                    System.out.println("服务器关闭");                      break;                }            }            } catch (Exception e) {              System.out.println("端口被占用");              e.printStackTrace();          }      }  }

从中可以看出服务器的搭建主要有以下步骤:

1.建立服务器的Socket,并设定一个监听的端口PORT

ServerSocket serverSocket = new ServerSocket(PORT);

2.将服务器的ServerSocket套接到客户端的Socket上:(未套上时,会一直阻塞。诸位可以试试)

Socket clientSocket = serverSocket.accept();

由于需要进行循环监听,因此获取消息的操作应放在一个while大循环中:

3.从客户端发来的clientSocket上获取输入流的抽象类,然后实例化,并进行读写。

 BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));   String msg = in.readLine(); 

安卓上的客户端代码:

@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Button b=(Button)findViewById(R.id.button);             b.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                new Thread(net).start();//新的子线程            }        });    }    Runnable net=new Runnable() {        @Override        public void run() {            try {                Socket socket;                //socket=new Socket("192.168.1.102", 12345);//注意这里                socket = new Socket();                SocketAddress socAddress = new InetSocketAddress("192.168.1.102", 12345);                socket.connect(socAddress, 3000);//超时3秒 //发送给服务端的消息                String msg = "Good Night";                try {                    //获取输出流并实例化                    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));                    out.write(msg+"\n");//防止粘包                    out.flush();//不加这个flush会怎样?                } catch (Exception e) {                    e.printStackTrace();                } finally {                    //关闭Socket                    socket.close();                    System.out.println("客户端关闭");                }            }    catch (Exception e) {                System.out.println("链接错误");                e.printStackTrace();            }        }    };
从中,可以看到客户端的搭建有以下步骤:

1.创建客户端本身的套接字Socket:

socket = new Socket();

2.建立一个连接(我知道这里和代码不一样,你可能会存疑,但请往下看)

socket = new Socket(ip,port);

3.发送消息:

BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));out.write(msg);out.flush();//不加这个flush会怎样?
4.关闭客户端Socket:
socket.close();


现在,对于Android的TCP通讯功能还远没有完成,不过快了,先别急,继续往下看:

观察以上android客户端代码。再想想,如何在Activity中使用相关的客户端代码呢?为什么我专门写了一个线程来进行网络操作呢?

答:从4.0开始,安卓就已经不允许在主线程中进行网络相关的操作。这样设计的原因是,由于网络的延迟、不确定性等因素,加之Socket本身在未套接上时是处于阻塞状态的,如果在主线程中进行网络相关操作,就会导致整个app被严重阻塞。

因此,从4.0起,任何尝试在主线程中进行网络操作的动作,都会导致抛出“android.os.NetworkOnMainThreadException”的异常。

那我们该如何解决呢?

创建一个子线程,所以,你会在我给出的代码里看到以下内容:

    Runnable net=new Runnable() {        @Override        public void run() {           //网络操作        }    }
使用时,直接让这个Runnable启动就行了:
new Thread(net).start();


此时你可能会注意到,我在客户端对Socket的实例化,并没有使用大多数人常写的:

socket=new Socket("192.168.1.102", 12345);
而是这样写:

socket = new Socket();SocketAddress socAddress = new InetSocketAddress("192.168.1.102", 12345);socket.connect(socAddress, 3000);//超时3秒
为什么呢?我们知道,socket在未连接上时,会一直处于阻塞状态,而此时由于socket本身并未实例化,导致你无法对socket的超时时间进行设置。这往往会导致线程卡主很长一段时间,最后抛出error110(TIME_OUT)。

我这么写,可以人为的设定超时时间,也便于进行多次的超时重播。

另外,android对权限也卡得很死,有些机型或者系统版本根本不允许你的app使用网络。那么我们需要在权限中添加以下内容:

    <!--允许应用程序改变网络状态-->    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>    <!--允许应用程序改变WIFI连接状态-->    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>    <!--允许应用程序访问有关的网络信息-->    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>    <!--允许应用程序访问WIFI网卡的网络信息-->    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>    <!--允许应用程序完全使用网络-->    <uses-permission android:name="android.permission.INTERNET"/>    <uses-permission android:name="android.permission.WAKE_LOCK" />

现在可以测试这个实例了。如果你仍然无法成功连接你的PC服务器,接着往下看。


------------------------

>【排错】

从排错的角度来看,我们需要做的,是从服务器和客户端两个方面进行排错。

首先,我们应当确保服务器和端口是没问题的。

在eclipse里打开服务器,然后进入cmd,输入talent localhost 12345。

如果成功,那么eclipse的控制台会输出“客户端已上线”。

然后,我们再测试android客户端连接时输入的PC服务器的ip是没问题的:(ip和port值请自行确定)

重启服务器,进入cmd输入talent 192.168.1.102 12345。

如果这一步成功,但你的android客户端依旧无法连接上你的PC服务器,那么,我想请你检查以下你的windows防火墙的设置。

控制面板\系统和安全\Windows 防火墙 -> 高级设置 ->入站规则 ,看看以下这几项是不是被防火墙禁止了:

现在再去测试一遍你的客户端,看看是否能连上服务器。大部分的样例代码的超时问题(error:110)在此都可以被解决了。


如果还不行,我们接下来检测安卓客户端。

首先,确保你的手机给了你的app权限。

接着,请检查一遍你设置的端口port值,是不是处于1024以下,或者使用了常用软件及敏感的port的值?如果是,请改成12345再进行测试。

基本上,到了这一步,只要编译没有报错,操作正确,在安卓版本没有发生大的变化的情况下,已经可以连上服务器了。


----------------------

>【理论】

实例结合基础,这里找到了大手子的几篇理论性的文章,可以参考我以上给出这个小实例,再进行深入学习:


Android UDP通信的实现与归纳:

http://blog.csdn.net/shenpibaipao/article/details/70237697

Java输入输出流详解:

http://blog.csdn.net/zsw12013/article/details/6534619

 Socket 通信原理(Android客户端和服务器以TCP&&UDP方式互通):

http://blog.csdn.net/mad1989/article/details/9147661



2 0
原创粉丝点击