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
- Android TCP通信的简单实例以及常见问题[超时/主线程阻塞]
- Android UDP通信的简单实例和用法归纳,以及与TCP对比、常见问题
- 简单的TCP通信实例
- android简单的Sockt通信(TCP)
- iOS多线程使用实例(异步下载图片的时候不会阻塞主线程的执行)
- 通过实例验证播放本地音乐MediaPlayer的reset函数并不会阻塞主线程
- 解决MBProgressHUD阻塞主线程的方法
- Android子线程主线程之间的通信
- Android 子线程与主线程间的通信
- Android主线程与子线程之间的通信
- Android主线程与子线程之间的通信
- AVA实现基于Tcp协议的简单Socket通信实例
- 基于Tcp协议的简单Socket通信实例(JAVA)
- 基于Tcp协议的简单Socket通信实例(JAVA)
- 简单的TCP通信
- 简单的TCP通信
- 简单的TCP通信
- TCP简单的通信
- 字典树(Tire树)模板+例题
- hdu4734——F(x) (数位DP)
- 3.this关键字
- HTML5项目笔记5:使用HTML5 WebDataBase设计离线数据库
- 如何快速转载CSDN中的博客
- Android TCP通信的简单实例以及常见问题[超时/主线程阻塞]
- Android中的MVP模式与MVC模式的比较分析
- Android 关于ToolBar分分钟玩死自己?
- 个人模板 组合数 C(m,n)
- HTML5项目笔记4:使用Audio API设计绚丽的HTML5音乐播放器
- 正在等待localhost响应
- php字符串处理之全角半角转换(正则匹配全角字符思路)
- socket编程
- 苹果未到,专利先行?苹果获面部识别技术专利