浅谈TCP协议在android中的使用
来源:互联网 发布:html注册页面源码 编辑:程序博客网 时间:2024/05/17 22:50
前言
手机作为移动设备,很受大家的青睐,因为它携带方便,可以随时随地上网聊天、玩游戏(如现在最火王者某某),这些都是在联网的情况进行的,如果手机不能上网的话,那么它就是没有用的铁疙瘩(某某奇葩也就不会为了某某果机割肾伤身了),因此网络对手机来说有着至关重要的作用。
由于JDK本身集成了TCP、UDP网路协议,那么Android 完全支持它;Android 也可以使用ServerSocket(服务端)、Socket(客户端),而ServerSocket、Socket是基于TCP/IP协议的网络通信;Android也可以使用DatagramSocket、DatagramPacket、MulticastSocket来建立基于UDP协议的网络通信;同时android也支持JDK提供URL、URLConnection;当然android还内置了HttpClient(这个自从android5.0以后需要自己添加Jar包),这样可以非常方便地发送HTTP请求,并获取HTTP响应;但是android并没有内置Web Service的支持,为了弥补这方面的不足,需要使用第三方类库(KSOAP2)来调用WebService。
一 TCP协议
TCP/IP是一种可靠的网络通信协议,在通信的两端各需要建立一个Socket,从而在两端之间形成虚拟链路,两端的程序可以通过虚拟链路进行通信。如下:
IP协议负责将消息从一个主机传送到另一个主机,信息在传送的过程中被分割成几个小包。
TCP协议负责提供可靠并且无差错的通信服务,它也被称为一种端对端的协议,这是因为,它为两台计算机之间的连接起到重要的作用,当一台计算机需要与另一台计算机连接时,TCP协议会让它们建立一个连接:用于发送和接收数据的虚拟链路。
TCP负责收集这些信息包,并将其按适当的次序排好传送,在接收端再将其正确地还原。TCP协议保证了数据包在传送中准确无误。同时TCP使用重发机制:当一个通信实体发送一个信息给另一个通信实体后,需要收到另一个通信实体的确认信息,如果没有收到另一个通信实体的确认信息,则会再次重发刚才发送的信息。
综上所述,虽然IP和TCP这两个协议的功能不尽相同,也可以分开单独使用,但它们时在同一个时期作为一个协议来设计的,并且在功能上也是互补的。只有两者结合才能保证Internet在复杂的环境下正常的运行。凡是连接到Internet计算机,都必须安装和使用这两个协议。
二 ServerSocket创建TCP服务端
两个通信实体在进行通信,必须有服务端、客户端之分,而Java中建立服务端是使用ServerSocket,下面看一些它的方法:
ServerSocket(int port):构造函数,参数port是端口,有效值:0~65535。
ServerSocket(int port,int backlog):构造函数,增加一个用来改变连接队列长度的参数backlog。
ServerSocket(int port,int backlog,InetAddress localAddr):构造函数,参数localAddr指定本地IP地址,用于在本地存在过个IP地址的情况。
Socket accept():如果接受到一个客户端Socket的连接请求,该方法将会返回与连接客户端Socket对应的Socket;否则该方法将会一直处于等待状态,线程也被阻塞。
接下来看一个示例:
public static void main(String[] args) { System.out.print("开启服务!");try {//创建一个ServerSocket 对象实例,用来监听客户端Socket的连接状态,端口号为30000,IP为本机IPServerSocket ss = new ServerSocket(30000);//无限循环,不断接收来自客户端Socket的请求,没有Socket请求时会进入阻塞状态。while(true){Socket socket = ss.accept();//获取输出流,向Socket通道中写入数据OutputStream os = socket.getOutputStream();os.write("您好,恭喜您中奖了!".getBytes("utf-8"));//关闭通道os.close();socket.close();}} catch (IOException e) {e.printStackTrace();} }
完整代码点击查看。
注意:
上面的输出流OutputStream并没有装成PrintStream,然后直接输出,整个字符串,这是由于服务端程序运行于window主机上,当直接使用PrintStream输出整个字符串默认使用系统平台的字符编码GBK,而android是在Linux平台运行的,客户端在读取网络数据时,默认使用的UTF-8编码,这样势必引起乱码。所以为保证能够正确解析数据,要手动控制字符串的编码,强行指定使用UTF-8字符集进行编码。
三 Socket创建TCP客户端
先来看下socket的构造函数:
Socket(InetAddress/String remoteAddress,int port):参数remoteAddress指定远程主机的IP地址,port指定远程主机的端口,这里没有指定本地IP地址,本地端口,默认使用本机的IP地址,默认使用系统动态分配的端口。
Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr, int localPort):增加参数localAddr指定本地Ip地址,localPort指定本地端口,这种情况使用本地主机有多个IP地址的情形。
再看其他重要的方法:
InputStream getInputStream():通过Socket对象实例获取输入流,让程序可以从Socket通道中获取数据。
OutputStream getOutputStream():通过Socket对象实例获取输出流,让程序可以往Socket通道中写入数据。
示例:
new Thread(new Runnable() { @Override public void run() { try { //建立连接到远程服务器的socket Socket socket = new Socket("192.168.11.139",30000); //=================或者============== //创建Socket对象 Socket s = new Socket(); //连接远程服务 s.connect(new InetSocketAddress("192.168.11.139",30000)); //设置超时时间 socket.setSoTimeout(10000); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); //进行I/O 操作 final String result = bufferedReader.readLine(); if (!TextUtils.isEmpty(result)){ ServerSocketActivity.this.runOnUiThread(new Runnable() { @Override public void run() { txt_serverSocket.setText(result); } }); } //关闭输入流、socket bufferedReader.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }).start();
完整代码点击查看。
说明,到这里服务端、客户端通信的虚拟链路算是建立起来了,要实现通信客户端还要添加权限:
<uses-permissionandroid:name="android.permission.INTERNET"/>
接下来先运行服务器,再运行客户端,那么客户端会收到字符串"您好,恭喜您中奖了!"。
四 加入多线程
在上面示例中已经简单的叙述了Socket的通信的过程,上面的示例中是一对一的关系,就是一个服务器对应一个客户端,那么在实际使用中却不是这样的,一个服务器需要服务多个对象(客户端),且可能需要与每个客户端保持长时间的通信,即服务端不断读取客户端的数据,并向客户端写入数据,而客户端也是如此。
当使用readLine()方法读取数据,该方法成功返回之前,线程被阻塞,程序是无法进行下去,也就不能接受其他的Socket连接请求了,所以为了解决这个问题,服务端为每个Socket启动一个新的线程,该线程负责与客户端进行通信,这样就不影响服务器接受其他线程了。
服务端:
首先定义一个类继承Runnable(例如我的类名叫作ServerThread),通过构造函数把Socket对象实例传递来,这样就可以获取输入流、输出流了,如:
public ServerThread(Socket s) {this.s = s;if (null != s) {try {br = new BufferedReader(new InputStreamReader(s.getInputStream(),"utf-8"));} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
再者读取Socket中的数据如下:
/** * 读取客户端数据 * @return */public String readFromClient () {if(null != br) {try {String result = br.readLine();System.out.println("服务端读取:" +result);return result;} catch (IOException e) {System.out.println("读取失败!");//捕捉到异常,表明s 对应的客户端已经关闭MutiThreadServerSocket.socketList.remove(s);// TODO Auto-generated catch blocke.printStackTrace();}}return null;}
还可以把刚刚获取的数据转发给所有的客户端
String content = null;// TODO Auto-generated method stub while((content = readFromClient()) != null) { //MutiThreadServerSocket.socketList为ArrayList<Socket>类型用来记录每个客户端 for(Socket s : MutiThreadServerSocket.socketList) { try {OutputStream os = s.getOutputStream();os.write(content.getBytes("utf-8"));os.flush();//os.close();System.out.println("服务端写入content:" + content);} catch (IOException e) {// TODO Auto-generated catch blockSystem.out.println("服务端写入失败!");e.printStackTrace();} } }
完整代码。
最后就是服务程序:
//存储Socket对象 public static ArrayList<Socket> socketList = new ArrayList<>();public static void main(String[] args) {System.out.println("开启多线程服务!");try {ServerSocket ss = new ServerSocket(30010);while(true){Socket socket = ss.accept(); socketList.add(socket); //每当一个客户端连接之后,就启动一个线程 new Thread(new ServerThread(socket)).start();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} }
完整代码。
客户端:
首先定义一个类继承Runnable,如下:
public class ClientThread implements Runnable { private Handler handler; private Socket socket; //输入流 读取服务端传送过来的消息 private BufferedReader br; //输出流往服务器发送信息 private static OutputStream outputStream; public static Handler revHandler ; private InputStream inputStream; //计时器 private Timer timer = new Timer(); public ClientThread(Handler handler) { this.handler = handler; } @Override public void run() { try { //192.168.11.139 为本地电脑的ip socket = new Socket("192.168.11.139",30010);// socket.setKeepAlive(true); inputStream = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(inputStream)); outputStream = socket.getOutputStream(); Log.e("进入循环线程:","-------------->"); /** * * 开子线程读取数据,因为readLine()会导致阻塞 * 对于socket,不能认为把某次写入到流中的数据读取完了就算流结尾了, * 但是socket流还存在,还可以继续往里面写入数据然后再读取。 * 这时用BufferedReader封装socket的输入流,调用BufferedReader的readLine方法是不会返回null的 * 所以在循环内如果不判断 content!=null && content.length() > 0 那么程序将会一直阻塞在这里(程序是因为readLine阻塞,并不是死循环) * */ new Thread(new Runnable() { @Override public void run() { Log.e("开始读取:","1111"); String content = null; try { while (((content = br.readLine()) != null) && (content.length() > 0)){ Log.e("读取====》",content); //每当有消息时,及时通知主界面更新消息 Message message = new Message(); message.what = 0x123; message.obj = content; handler.sendMessage(message); } } catch (IOException e) { closeSocket(); e.printStackTrace(); } } }).start(); Looper.prepare(); revHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 0x345){ try { outputStream.write((msg.obj.toString() + "\r\n").getBytes("utf-8")); Log.e("写入====》",msg.obj.toString()); outputStream.flush(); } catch (UnsupportedEncodingException e) { closeSocket(); e.printStackTrace(); } catch (IOException e) { closeSocket(); e.printStackTrace(); } } } }; //把当前的线程初始化为looper//,循环读取服务端发过来的消息 Looper.loop(); } catch (IOException e) { closeSocket(); e.printStackTrace(); } } /** * 关闭端口 */ private void closeSocket(){ try { if (null != inputStream){ inputStream.close(); } if (null != outputStream){ outputStream.close(); } if (null != br){ br.close(); } if (null != socket){ socket.close(); } } catch (IOException e) { e.printStackTrace(); } }}
完整代码
然后在主界面添加一个输入框和发送按钮,输入内容后,点击发送把内容发送给服务端,布局文件如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.ecric.http.socket.MultiThreadClientActivity"> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/ed_multi_input" android:layout_width="0dp" android:layout_weight="3" android:layout_height="wrap_content" /> <Button android:id="@+id/btn_send" android:text="发送" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" /> </LinearLayout> <!--用来展示文本信息--> <TextView android:id="@+id/txt_show" android:text="展示文本信息:" android:layout_width="match_parent" android:layout_height="wrap_content" /></LinearLayout>
最后就是客户端程序:
@EActivity(R.layout.activity_multi_thread_client)public class MultiThreadClientActivity extends AppCompatActivity { // 输入信息框 @ViewById(R.id.ed_multi_input) EditText edInput; //展示信息 @ViewById(R.id.txt_show) TextView txtShow; public Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); //如果消息来自于子线程 if (msg.what == 0x123){ txtShow.append("\n" + msg.obj.toString()); } } }; //客户端处理数据的线程 private ClientThread clientThread; @AfterViews public void initData(){ clientThread = new ClientThread(handler); new Thread(clientThread).start();// ClientBody clientBody = new ClientBody("192.168.11.139",30010,"1");// clientBody.start(); } /** * 发送监听 * @param view */ @Click(R.id.btn_send) public void onClick(View view){ //当用户按下发送按钮后,将用户输入的数据,封装成Message //然后发送给子线程的handle对象 Message message = new Message(); message.what = 0x345; message.obj = edInput.getText().toString(); clientThread.revHandler.sendMessage(message); edInput.setText(""); }}
说明在这个程序也没有处理多少,主要是启动ClientThread,及与输入操作,当用户点击“发送”按钮之后,程序将会吧输入的内容发送给ClientThread的revHandle,进而把内容写到Socket中,传送为服务端;同样从服务读取到内容时,将会把读取到的信息通过handle发送给UI线程,进而更新内容;还有一点要说说明下,上述代码我使用AndroidAnnotations注解框架,使代码看起来简单很多,不感兴趣的可以不管它,查看控件时,老老实实findViewById就可以了。
接下来先运行服务端,再运行客户端,在输入框输入QWERTY点击发送,服务端打印结果:
开启多线程服务!
服务端读取:QWERTY
服务端写入content:QWERTY
客户端结果:
但在此过程中遇到一个问题:服务端可以很好的接受来自客户端数据,但为什么只有服务端进程关闭(或者说在服务端关闭socket)后,客户端才会收到服务端传送过来的数据呢?谁有解决之法、或知其原理务必指点一下小编。
服务端项目代码:https://git.oschina.net/lzbgit/SocketServerTest
客服端项目代码:https://git.oschina.net/lzbgit/HttpProtocolApp
参考文献:
《疯狂android讲义》
http://www.cnblogs.com/sweetchildomine/p/6477563.html
阅读全文
0 0
- 浅谈TCP协议在android中的使用
- 浅谈PopupWindow在Android开发中的使用
- 浅谈 PopupWindow 在 Android 开发中的使用
- 浅谈MVP架构在Android中的使用
- Android中的TCP协议与UDP协议
- TCP/IP协议浅谈
- 浅谈TCP协议
- 浅谈接口回调以及在Android中的使用
- http协议Authorization认证方式在Android开发中的使用
- TCP/IP协议族浅谈
- 浅谈TCP/IP协议族
- 浅谈TCP-IP协议模型
- 浅谈TCP/IP协议栈
- Android 中使用TCP、UDP协议
- 协议在ios中的使用
- 浅谈MVP架构在Android中的应用
- TCP协议中的计时器
- TCP协议中的定时器
- hadoop-2.7.4-翻译文档-集群部署
- css复习——鼠标
- 024、面向对象的三大基本特征-多态
- 读懂 Netty 的高性能架构之道
- 笔记
- 浅谈TCP协议在android中的使用
- 邮件、短信
- QML入门必知教程
- log4j系列—log4j.properties配置详解与实例-全部测试通过
- struts2配置文件result的type属性有哪些?
- 6个变态的C语言Hello World程序
- GBDT算法原理及调参实现
- NYOJ 495 少年 DXH
- linux目录结构与文件基本操作