Android开发之网络通讯详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dmk877/article/details/50893635

真正的才智是刚毅的志向。 —— 拿破仑

目前的App如果没有使用到网络技术,仅仅依靠本地是很难提升自己App的用户量的,21世纪是互联网时代,网络技术已经融入并改变了我们的生活。想象一下假如一周没有网络供你使用,你是不是有跟这个世界脱节了的感觉,哈哈,没有错,网络是如此的重要,因此我们就需要花点时间好好学习下这门技术。如有谬误,欢迎批评指正,如有疑问欢迎留言


通过本篇博客你将学到以下知识点:

①HTTP网络请求的方式

②Android中网络通讯实现的原理和步骤

③基于Http协议的访问网络的实例

④基于Socket套接字的服务器和Android客户端交互的一个实例

⑤GET请求和POST请求的区别

⑥Socket与HTTP区别


一、HTTP网络请求的方式

   在Android中发送HTTP网络请求一般有三种方式HttpURLConnectoin、HttpClient、AndroidHttpClient(很少用)。其中AndroidHttpClient这种方式很少使用。接下来我们就来分别学习下这三种HTTP协议的网络请求。

1.1 HttpURLConnection

    java.net.*提供与联网有关的类,包括流和数据包套接字、Internet协议、常见HTTP处理,通过HttpURLConnection(继承自URLConnection),可用于向指定网站发送GET请求、POST请求。

HttpURLConnection访问网络的主要步骤:

首先需要创建一个URL对象,并传入目标的网络地址这样就得到了一个URL对象,得到此对象后调用URL的openConnection();方法即可得到HttpURLConnection对象,与之对应的代码如下:

URL url=new URL("http://www.baidu.com");
httpUrlConnection = (HttpURLConnection) url.openConnection();

得到了HttpURLConnection的实例后,可以设置HTTP请求所使用的方法,在上面我们也提到主要有两个,GET和POST。GET表示希望从服务器那里获取数据,而POST则表示希望提交数据给服务器。首先来看下GET方法请求网络的操作,设置请求方式为GET的代码如下:

httpUrlConnection.setRequestMethod("GET");

然后可以针对自己的业务需求去定制符合这个需求的代码,比如设置连接超时、读取超时的毫秒数、设置允许输出流,即允许上传数据到web服务、设置允许输入流,即允许下载、设置是否允许使用缓存等等。代码如下:

httpUrlConnection = (HttpURLConnection) url.openConnection();
httpUrlConnection.setRequestMethod("GET"); //设置请求方法
httpUrlConnection.setConnectTimeout(8000); //设置链接超时的时间
//将读超时设置为指定的超时值,以毫秒为单位。用一个非零值指定在建立到资源的连接后从input流读入时的超时时间。
//如果在数据可读取之前超时期满,则会引发一个 java.net.sockettimeoutexception。超时时间为零表示无穷大超时。
httpUrlConnection.setReadTimeout(8000);
httpUrlConnection.setDoInput(true); //允许输入流,即允许下载
httpUrlConnection.setDoOutput(true); //允许输出流,即允许上传
httpUrlConnection.setUseCaches(false); //设置是否使用缓存

设置好这些之后,就可以调用相应的方法从服务器获得它返回的"流"了,这个返回的“流”中就包含了我们所想要的信息,所调用的方法就是getInputStream(),我们可以这样写代码

InputStream inputStream =httpUrlConnection.getInputStream();
这样就得到了服务器给我返回的这个“流”,我们就可以从这个“流”中来读取数据了,在操作完后大家不要忘记在最后调用disconnect()这个方法将这个HTTP链接关闭掉。再了解了这些理论知识后,接着我们一起来完成一个案例,相信通过这个案例会加深你对HttpURLConnection的了解。
首先来看一下这个案例的运行效果,效果图如下:
 

 
这个界面的布局如下

<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" >
   
    <Button
        android:id= "@+id/btn_send_request"
        android:layout_width= "match_parent"
        android:layout_height= "wrap_content"
        android:text="请求网络" />

    <ScrollView
        android:layout_width= "match_parent"
        android:layout_height= "match_parent" >
       
     <TextView
        android:id= "@+id/tv_show_content"
            android:layout_width= "wrap_content"
            android:layout_height= "wrap_content" />
    </ScrollView >

</LinearLayout>

接着来看看MainActivity的代码

package com.example.httpcommunicatepractice;


import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;


import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;


public class MainActivity extends ActionBarActivity implements OnClickListener {
     
     private Button btnSendRequest;
     private TextView tvShowContent;
     private static final String httpUrl="http://www.imooc.com/api/teacher?type=4&num=40" ;
     public static final int SHOW_RESPONSE = 0;

     private Handler handler= new Handler(){
            public void handleMessage(Message msg) {
                 switch (msg. what) {
                 case SHOW_RESPONSE:
                     String result=(String) msg. obj;
                      tvShowContent.setText(result);
                      break;
                }
           };
     };
     
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout. activity_main);
       
        btnSendRequest=(Button) findViewById(R.id.btn_send_request );
        tvShowContent=(TextView) findViewById(R.id.tv_show_content );
       
        btnSendRequest.setOnClickListener( this);
       
    }
   
    @Override
     public void onClick(View v) {
     if(v.getId()==R.id. btn_send_request){
           httpUrlConnection_get();
     }
     }

     private void httpUrlConnection_get() {
            new Thread( new Runnable() {
                
                 @Override
                 public void run() {
                     
                     InputStreamReader in= null;
                     HttpURLConnection httpUrlConnection= null;
                      try {
                           
                          URL url= new URL("http://www.baidu.com" );
                          httpUrlConnection = (HttpURLConnection) url.openConnection();
                          httpUrlConnection.setRequestMethod( "GET"); //设置请求方法
                          httpUrlConnection.setConnectTimeout(8000); //设置链接超时的时间
                          //将读超时设置为指定的超时值,以毫秒为单位。用一个非零值指定在建立到资源的连接后从input流读入时的超时时间。
                          //如果在数据可读取之前超时期满,则会引发一个 java.net.sockettimeoutexception。超时时间为零表示无穷大超时。
                          httpUrlConnection.setReadTimeout(8000);
                          httpUrlConnection.setDoInput( true); //允许输入流,即允许下载
                          httpUrlConnection.setDoOutput( true); //允许输出流,即允许上传
                          httpUrlConnection.setUseCaches( false); //设置是否使用缓存
                            //建立连接,上面对urlConn的所有配置必须要在connect之前完,这里需要注意的是
                            //connect这个方法,在getInputStream()方法中会隐式的被调用,所以这里不写也没有问题
                           httpUrlConnection.connect();
                           InputStream inputStream=httpUrlConnection.getInputStream();
                           in= new InputStreamReader(inputStream);
                           BufferedReader bf= new BufferedReader(in);
                           StringBuffer sb= new StringBuffer();
                           String inputLine= null;
                           
                           while((inputLine=bf.readLine())!= null){
                                sb.append(inputLine);
                           }
                           
                           Message message= new Message();
                           message. what= SHOW_RESPONSE;
                           message. obj=sb.toString();
                            handler.sendMessage(message);
                           
                     } catch (Exception e) {
                           e.printStackTrace();
                     } finally{
                            if(httpUrlConnection!= null){
                                httpUrlConnection.disconnect();
                           }
                     }
                }
           }).start();
           
     }

}

从代码中可以看到当点击“请求网络”这个按钮的时候,首先会去调用httpUrlConnection_get()这个方法,在这个方法中回去开启一个子线程去访问网络,这里我们访问的是百度首页,通过httpUrlConnection.getInputStream()这个方法获取百度给我们返回过来的"流",然后通过BufferedReader去读取这个流并将读取的结果放进StringBuffer中,通过Handler、Message的形式发送个主线程,更新TextView的内容。
在这里要注意三点:
①不要忘了添加访问网路的权限

<uses-permission android:name= "android.permission.INTERNET" />

②更新控件的操作必须要放到主线程中,不能在子线程中更新控件
③可能看完后会有人问,一般我们在真正的开发中都是带有参数的,这里我提供了一个连接在上述MainActivity中大家可以看到有这么一行代码

private static final String httpUrl ="http://www.imooc.com/api/teacher?type=4&num=40" ;

这个连接是慕课网的一个连接,大家可以把上述的"http://www.baidu.com",直接换成httpUrl,然后运行,可以发现它返回了相应的json。这里我这样做的主要目的就是要告诉大家,这个方式请求的参数是直接加载链接后面的。
那么如果是想要提交数据给服务器应该怎么办呢?其实也不复杂,只需要将HTTP 请求的方法改成POST,并在获取输入流之前把要提交的数据写出即可。注意每条数据都要以键值对的形式存在,数据与数据之间用&符号隔开,比如说我们想要向服务器提交用户名和密码,就可以这样写:

httpUrlConnection.setRequestMethod( "POST");//设置请求方法
DataOutputStream dataOutputStream=new DataOutputStream(urlConnection.getOutputStream());
 //要提交的参数 
String content = "username=admin&password=123456";
//将要上传的内容写入流中 
dataOutputStream.writeBytes(content);  
//刷新、关闭 
dataOutputStream.flush(); 
dataOutputStream.close();

关于HttpURLConnection的post请求大家可以找个自己公司的POST请求的接口,或者找一个POST请求的网站,自己去试试。我这里就不做演示了


1.2 HttpClient

     HttpClient是Apache提供的HTTP网络访问接口,从一开始的时候就被引入到了AndroidAPI中。它可以完成和HttpURLConnection几乎一模一样的效果,但是和HttpURLConnection不同的是HttpClient是org.apache.*下提供的类两者之间的用法也是有很大的差别的,HttpClient与HttpURLConnection一样也同样有GET和POS请求两种方式,同HttpURLConnection一样首先我们来学习一下HttpClient访问网络的主要步骤:
首先我们应该清楚HtppClient是一个接口,因此无法创建它的实例,通常情况下我们都会创建一个Apache提供给我们的一个默认的实例,代码如下:

//取得HttpClient对象
HttpClient httpClient= new DefaultHttpClient();

假如我们想发送一条GET请求,我们需要创建一个HttpGet对象,将要访问的目标网络的地址传递过去,然后调用HttpClient的execute()方法并将HttpGet对象传入即可,与之对应的代码如下:

//HttpGet连接对象
HttpGet httpGet= new HttpGet("http://www.baiduc.com" );
//请求HttpClient,取得HttpResponse
HttpResponse httpResponse=httpClient.execute(httpGet);

假如我们想发送一条POST请求,我们需要创建一个HttpPost请求,将要访问的目标网络的地址传递过去,代码如下:

HttpPost httpPost=new HttpPost( "http://www.baidu.com" );

与GET方法不同的是,POST请求需要NameValuePair集合来存放待提交的参数,并将这个参数传到UrlEncodedFormEntity中,然后调用HttpPost的setEntity()方法将构建好的UrlEncodedFormEntity设置进去,与之对应的代码如下:

List<NameValuePair> params=new ArrayList<NameValuePair>();
params.add( new BasicNameValuePair("username" ,"admin" ));
params.add( new BasicNameValuePair("password" ,"123456" ));
HttpEntity httpEntity = new UrlEncodedFormEntity(params, "utf-8");
httpPost.setEntity(httpEntity);

接下来的操作就与GET方法类似了,调用HttpClient的execute()方法,并将HttpPost对象传入即可,代码如下:

HttpResponse httpResponse = httpClient.execute(httpPost);

从上面我们可以看到它返回了一个HttpResponse对象,服务器返回的所有信息都包含在了这个对象中,通常我们的做法是先取得服务器返回的状态码,如果返回的状态码为200就说明请求和响应都成功了,此时我们就可以按照业务需求去进行相应的代码的书写了,与之对应的代码如下:

if(httpResponse.getStatusLine().getStatusCode()==HttpStatus.SC_OK){
    //访问网络成功   
}

访问网络成功之后,可能有的同学会说,怎样拿到服务器给返回的数据呢?其实Apache已经给我们提供好方法了,我们可以调用HttpResponse这个类的getEntity()方法来获取一个HttpEntity对象,然后在调用EntityUtils.toString()这个方法将HttpEntity对象转换成字符串就可以了,与之对应的代码如下:

HttpEntity entity = httpResponse.getEntity();
String strResult = EntityUtils.toString(entity,"utf-8");

可以看到在转换成字符串时,我们还可以指定相应的编码,防止转换后出现乱码的情况,至此HttpClient访问网络的内容就讨论的差不多了,接着来一个案例,这个案例HttpURLConnection的GET方法一样,只不过这里我们采用的是HttpClient的GET方法,因此我们只要稍作修改即可,修改过后的代码如下:

public class MainActivity extends ActionBarActivity implements OnClickListener {
     
   。。。。。。。。。
    @Override
     public void onClick(View v) {
            switch (v.getId()) {
            case R.id. btn_send_request:
                httpClient_get();
                 break;
           }
     }
   

     private void httpClient_get() {
           
            new Thread( new Runnable() {
                
                 @Override
                 public void run() {
                     
                      try {
                            //HttpGet连接对象
                           HttpGet httpGet= new HttpGet("http://www.baidu.com" );
                            //取得HttpClient对象
                           HttpClient httpClient= new DefaultHttpClient();
                            //请求HttpClient,取得HttpResponse
                           HttpResponse httpResponse=httpClient.execute(httpGet);
                           if(httpResponse.getStatusLine().getStatusCode()==HttpStatus. SC_OK){
                                 //取得返回的字符串
                                 resultData=EntityUtils. toString(httpResponse.getEntity(),"utf-8");
                                Message message= new Message();
                                message. what= SHOW_RESPONSE;
                                message. obj= resultData;
                                 handler.sendMessage(message);
                           }
                     } catch (Exception e) {
                           e.printStackTrace();
                     }
                     
                }
           }).start();
           
     }
     
}

这与刚开始的运行效果是一样的。如果有参数对于GET请求直接加在链接后面就行了,你也可以跟HttpURLConnection一样试试访问这个链接"http://www.imooc.com/api/teacher?type=4&num=40";它的POST请求大家可以自己试验一下。


1.3 Android.net.*(Android网络接口)

常常使用此包下的类进行Android特有的网络编程,如:访问WiFi,访问Android联网信息,邮件等功能。


1.4 GET和POST请求方式的区别

 Get和Post提交的区别
 ①get提交,提交的信息都显示在地址栏中。post提交,提交的信息不显示在地址栏中
 ②get提交,对于敏感的数据信息不安全。post提交,对于敏感信息安全。
 ③get提交,对于大数据不行,因为地址栏存储体积有限post提交,可以提交大体积数据
 ④get提交,将信息封装到了请求消息的请求行中post提交,将消息封装到了请求体中

 在服务器的一个区别:
 如果出现将中文提交到tomcat服务器,服务器默认会用iso8859-1进行解码此时会出现乱码,通过iso8859-1进行编码,在用指定的中文码表解码,即可。这种方式get提交和post提交都有效,但是对于post提交方式提交的中文,还有另一种解决办法,就是直接使用服务端一个对象,request对象的setCharacterEncoding方法直接设置指定的中文码表就可以将中文数据解析出来。这个方法只对请求体中的数据进行解码。


1.5 HttpURLConnection和HttpClient应该使用哪种方式?

     目前网上的一些比较好的开源项目它们的网络请求基本都是基于HttpURLConnection或者HttpClient的,比如:Volley的Http请求在 android 2.3 版本之前是通过HttpClient ,在之后的版本是通过URLHttpConnection。xUtils都是通过HttpClient请求网(bitmap模块图片下载是通过URLHttpConnection),以及我们经常用的Universal-Image-Loader是采用HttpURLConnection等。URLHttpConnection默认支持GZIP压缩,api操作简单。那么究竟是选择HttpURLConnection呢?还是选择HttpClient呢?大家可以参考郭霖的这篇博客:http://blog.csdn.net/guolin_blog/article/details/12452307这里我只将他最后的总结的话跟大家分享下,如果你想详细的学习,就点击连接,最后的总结:

在Android 2.2版本之前,HttpClient拥有较少的bug,因此使用它是最好的选择。

而在Android 2.3版本及以后,HttpURLConnection则是最佳的选择。它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。对于新的应用程序应该更加偏向于使用HttpURLConnection,因为在以后的工作当中我们也会将更多的时间放在优化HttpURLConnection上面。


二、Socket(套接字)


2.1 简介及分类
    Socket(套接字)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
应用程序与服务器通信可以采用两种模式:TCP可靠通信 和UDP不可靠通信。


2.2 建立Socket链接
首先来看一点理论知识:
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。


服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。


客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。


连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。


接着我们来看一张来自于百度百科的TCP通讯模型


从图中我们可以看出要想建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务端称为ServerSocket。在建立服务端(ServerSocket)时需要使用ServertSocket对象,这个对象会自动对其构造函数中传入的端口号进行监听,并在接收到连接请求后,使用ServerSocket.accept()方法返回一个连接的Socket对象。而不管是建立客户端还是在进行其他数据交换方面的操作时,都需要使用Socket类,Socket类在进行初始化时需要传入Server端的Ip地址和端口号,代码如下:

Server socket = new Socket(HOST, PORT);   //host:为服务器的IP地址  port:为服务器的端口号
并返回连接到Server端的一个Socket对象,如果是连接失败,那么将返回异常,同ServerSocket,它也是自动进行连接请求的。如果你的配置也就是服务器的Ip地址以及端口号配置好的话,这个连接就已经建立好了,但是仅仅是建立连接还没有用,进行数据的交互才是我们最终的目的,此时就需要用到IO流中的OutputStream和InputStream了,这一点从图中也可以看到,主要有两种情况:
当应用程序需要对流进行数据写操作时,可以使用Socket.getOutputStream()方法返回的数据流进行操作。
当应用程序要从流中取出数据时,可以使用Socket.getInputStream()方法返回的数据流进行操作。我们可以这样操

InputStream inputStream = socket.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

这样我们就可以从bufferedReader中读取数据了。与之对应的socket.getOutputStream()的操作与之类似。好了说了这么多理论知识,我们来一个小例子
这个例子要实现的效果的效果图如下:



从图中可以看出它实现了手机客户端通过Socket与服务器进行通讯这个功能,手机客户端发送给服务器一条消息后,服务器会把这条消息再发给客户端。它是怎么实现的呢?首先来理理思路:
通过上面的理论知识可以想到我们首要做的就是建立socket连接,这样才能保证客户端和服务端进行通讯,其次在连接建立后再安卓端会向服务端发送一条消息,通过Socket.getOutputStream()这个方法得到输出流,然后然后将这个输出的字符流转化为字节流转化的方法是用OutputStreamWriter然后再通过BufferWriter这个类的对象将要发送的这个消息内容通过write()方法写进去,通过调用BufferWriter.flush()或close()方法后,将其发送到服务器端。它的示例代码为:

BufferedWriter  mWriter = new BufferedWriter( new OutputStreamWriter(mSocket.getOutputStream(),"utf-8" ));
mWriter.write("你想向服务器端发送的内容"+“\n” );
mWriter.flush();

而服务器端通过

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream,"utf-8" ));
通过这个方法就可以得到客户端向服务器发送过来的消息。
 
首先来看下服务端的代码
package com.beijing.edu;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class MySocketServer {

     private static final int SOCKET_PORT = 1234;
     private ServerSocket serverSocket = null;
     private boolean flag = true;
     private BufferedReader reader;
     private BufferedWriter writer;

     /**
      * @param args
      */
     public static void main(String[] args) {
           MySocketServer socketServer = new MySocketServer();
           socketServer.initSocket();
     }

     private void initSocket() {
            try {
                 serverSocket = new ServerSocket( SOCKET_PORT);
                System. out.println( "服务已经启动,端口号:" + SOCKET_PORT);
                 while ( flag) {
                     Socket clientSocket = serverSocket.accept();
                     System. out.println( "有客户端连接" );

                     SocketThread socketThread = new SocketThread(clientSocket);
                     socketThread.start();

                }

           } catch (IOException e) {
                e.printStackTrace();
           } finally {
                 try {
                      writer.close();
                } catch (IOException e) {
                     e.printStackTrace();
                }
           }
     }

     public class SocketThread extends Thread {

            private Socket socket;

            public SocketThread(Socket clientSocket) {
                 this. socket = clientSocket;
           }

            @Override
            public void run() {
                 super.run();

                InputStream inputStream;
                 try {
                      //获取输入流
                     inputStream = socket.getInputStream();
                      //得到读取BufferedReader对象
                      reader = new BufferedReader( new InputStreamReader(inputStream,"utf-8" ));
                      writer = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(),"utf-8" ));
                      //循环读取客户端发过来的消息
                      while ( flag) {
                            if ( reader.ready()) {
                                String result= reader.readLine();
                                System. out.println( "客户端发过来的消息为:" + result);
                                 //将服务端发过来的消息,发送给客户端
                                 writer.write( "服务端发过来的消息:" + result+"\n" );
                                 writer.flush();
                           }
                     }
                } catch (IOException e) {
                     e.printStackTrace();
                }

           }
     }
}

其实服务端的代码非常简单,就是开启一个子线程来循环的从客户端那里获取消息,获取消息后将这个消息打印出来,并将这个消息再发送给客户端就,注意这里的服务端的代码我是运行在MyEclipse中的,是一个Java Project,你也可以在Eclipse中建一个Java Project。
接着来看看Android客户端的代码:

package com.example.socketclient1;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

import android.support.v7.app.ActionBarActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;


public class MainActivity extends ActionBarActivity implements OnClickListener {
     
     private EditText etIp;
     private EditText etPort;
     private Button btnStart;
     private TextView tvContent;
     private EditText etSendContent;
     private Button btnSendMessage;
     
     private Socket clientSocket;
     private boolean isReceivingMsgReady;
     private BufferedReader mReader;
     private BufferedWriter mWriter;
     private StringBuffer sb= new StringBuffer();
     
     private Handler handler= new Handler(){
           public void handleMessage(android.os.Message msg) {
               switch (msg. what) {
               case 0:
                    sb.append((String)msg. obj);
                    tvContent.setText( sb.toString());
                    break;
              }
          };
     };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout. activity_main);
       
        etIp=(EditText) findViewById(R.id. et_ip);
        etPort=(EditText) findViewById(R.id. et_port);
        btnStart=(Button) findViewById(R.id. btn_start);
        etSendContent=(EditText) findViewById(R.id.et_send_content );
        btnSendMessage=(Button) findViewById(R.id.btn_send );
        tvContent=(TextView) findViewById(R.id.tv_content );
        btnStart.setOnClickListener( this);
        btnSendMessage.setOnClickListener( this);
       
    }

     @Override
     public void onClick(View v) {
           switch (v.getId()) {
          
           case R.id. btn_start:
               if(! isReceivingMsgReady){
                   initSocket();
              }
               break;
           case R.id. btn_send:
              send();
               break;
          }
     }

     private void send() {

           new AsyncTask<String, Integer, String>() {

               @Override
               protected String doInBackground(String... params) {
                   sendMsg();
                    return null;
              }

          }.execute();
     
     }

     /**
      * 向服务器发送消息
      */
     protected void sendMsg() {
           try {
              String msg=etSendContent.getText().toString();
               //通过BufferedWriter对象向服务器写数据
               mWriter.write(msg+ "\n");
               //一定要调用flush将缓存中的数据写到服务器
               mWriter.flush();
              String str= "\n"+ "我:" +msg+"   "+getTime(System.currentTimeMillis ())+"\n" ;
                handler.obtainMessage(0,str).sendToTarget();
          } catch (IOException e) {
              e.printStackTrace();
          }
     }

     private void initSocket() {
          
           new Thread( new Runnable() {
              
               @Override
               public void run() {
                   String ip= etIp.getText().toString();
                    int port=Integer.parseInt(etPort.getText().toString());
                   
                    try {
                         isReceivingMsgReady= true;
                         //在子线程中初始化Socket对象
                         clientSocket= new Socket(ip,port);
                         //根据clientSocket.getInputStream得到BufferedReader对象,从而从输入流中获取数据
                         mReader= new BufferedReader( new InputStreamReader(clientSocket.getInputStream(),"utf-8" ));
                         //根据clientSocket.getOutputStream得到BufferedWriter对象,从而从输出流中获取数据
                         mWriter= new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream(),"utf-8" ));
                         while( isReceivingMsgReady){
                              if( mReader.ready()){
                                   handler.obtainMessage(0, mReader.readLine()).sendToTarget();
                             }
                             Thread. sleep(200);
                        }
                         mWriter.close();
                         mReader.close();
                         clientSocket.close();
                   } catch (Exception e) {
                        e.printStackTrace();
                   }
              }
          }).start();
          
     }
     
     /**
      * 得到自己定义的时间格式的样式
      * @param millTime
      * @return
      */
     private String getTime( long millTime) {
          Date d = new Date(millTime);
          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" );
          System. out.println(sdf.format(d));
           return sdf.format(d);
     }
}

可以看到服务器的代码和客户端的代码都很简单,这样就通过Socket实现了 客户端和服务器的通信。大家可以动手试试,在这里需要提醒大家注意的是:
①千万不要忘了添加权限。

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

② Java socket编程时,常使用BufferedReader的readLine()函数来进行数据的读取。但是该函数是阻塞的,如果在接收的数据中不含有'\n','\r'或者结束符时,往往导致进程挂起,从而程序无法继续。所以在发送数据的另一端,一定要记得在最后加换行符,这一点在客户端的体现是mWriter.write(msg+ "\n");这一行代码,后面要加“\n”。


三、Socket与HTTP通讯的区别

           通常情况下Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。而HTTP连接使用的是“请求—响应”的方式,也就是说当需要数据的时候想服务器发送一条请求,服务器收到请求后返回相应的数据,请求过后这个连接就不在了,当下次需要数据时再发送请求,综上:HTTP这种方式连接不是一直存在的,而Socket一旦建立连接,这个连接是一直存在的,直到双方断开连接,这个连接才会消失。

         很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端需要定时向服务器端发送连接请求,这种在客户端的请求方式叫做“轮询”,顾名思义定时的去询问服务器是否有新数据,当服务器有新数据时,此时收到客户端发送的请求就会把这个数据发送给客户端,当服务器再有新消息时就要等到下次客户端的请求了。

         在开发中如果对数据的即时性要求比较高的情况下一般采用Socket通讯,例如聊天的功能。如果对数据的即时性要求不是特别高的情况下一般采用HTTP通讯, 比如在项目中,我们请求的接口,请求一次就给我们返回数据,很少会去不断的去请求服务器的数据。


关于Socket的讲解本节讲解了其理论知识,在下一节将会给大家带来利用Socket实现两个Android手机端的通讯,谢谢大家持续关注。

如有谬误,欢迎批评指正,如有疑问欢迎留言,大家一起讨论,共同进步。

 


源码猛戳这里。。。










猜你喜欢

转载自blog.csdn.net/dmk877/article/details/50893635
今日推荐