Android开发:Socket的简介与使用解析

ty391255 8年前
   <p><img src="https://simg.open-open.com/show/374f635f21e2f63ee1efd57bf0db81f2.png"></p>    <h2><strong>前言</strong></h2>    <ul>     <li> <p>Socket的使用在Android的网络编程中非常重要</p> </li>     <li> <p>今天我将带大家全面了解Socket及其使用方法</p> </li>    </ul>    <h2><strong>目录</strong></h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/528b7608951f1dbbce070a77ab11fe65.png"></p>    <p style="text-align:center">目录</p>    <h2><strong>1.网络基础</strong></h2>    <p><strong>1.1 计算机网络分层</strong></p>    <p>计算机网络分为五层:物理层、数据链路层、网络层、运输层、应用层</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/16391ff69bc51402b337f3010950eaa4.png"></p>    <p style="text-align:center">计算机网络</p>    <p>其中:</p>    <ul>     <li>网络层:负责根据IP找到目的地址的主机</li>     <li>运输层:通过端口把数据传到目的主机的目的进程,来实现进程与进程之间的通信</li>    </ul>    <p><strong>1.2 端口号(PORT)</strong></p>    <p>端口号规定为16位,即允许一个IP主机有2的16次方65535个不同的端口。其中:</p>    <ul>     <li>0~1023:分配给系统的端口号 <p>我们不可以乱用</p> </li>     <li> <p>1024~49151:登记端口号,主要是让第三方应用使用</p> <p>但是必须在IANA(互联网数字分配机构)按照规定手续登记,</p> </li>     <li> <p>49152~65535:短暂端口号,是留给客户进程选择暂时使用,一个进程使用完就可以供其他进程使用。</p> </li>    </ul>    <p>在Socket使用时,可以用1024~65535的端口号</p>    <p><strong>1.3 C/S结构</strong></p>    <ul>     <li>定义:即客户端/服务器结构,是软件系统体系结构</li>     <li>作用:充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统的通讯开销。 <p>Socket正是使用这种结构建立连接的,一个套接字接客户端,一个套接字接服务器。</p> </li>    </ul>    <p>如图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/22267d134df8090fceb2d55dcc10e6bd.png"></p>    <p style="text-align:center">Socket架构</p>    <p>可以看出,Socket的使用可以基于TCP或者UDP协议。</p>    <p><strong>1.4 TCP协议</strong></p>    <ul>     <li> <p>定义:Transmission Control Protocol,即传输控制协议,是一种传输层通信协议</p> <p>基于TCP的应用层协议有FTP、Telnet、SMTP、HTTP、POP3与DNS。</p> </li>     <li> <p>特点:面向连接、面向字节流、全双工通信、可靠</p>      <ul>       <li> <p><strong>面向连接</strong>:指的是要使用TCP传输数据,必须先建立TCP连接,传输完成后释放连接,就像打电话一样必须先拨号建立一条连接,打完后挂机释放连接。</p> </li>       <li> <p><strong>全双工通信</strong>:即一旦建立了TCP连接,通信双方可以在任何时候都能发送数据。</p> </li>       <li> <p><strong>可靠的</strong>:指的是通过TCP连接传送的数据,无差错,不丢失,不重复,并且按序到达。</p> </li>       <li> <p>面向字节流:流,指的是流入到进程或从进程流出的字符序列。简单来说,虽然有时候要传输的数据流太大,TCP报文长度有限制,不能一次传输完,要把它分为好几个数据块,但是由于可靠性保证,接收方可以按顺序接收数据块然后重新组成分块之前的数据流,所以TCP看起来就像直接互相传输字节流一样,面向字节流。</p> </li>      </ul> </li>    </ul>    <ul>     <li> <p>TCP建立连接</p> 必须进行 <strong>三次握手</strong> <p>:若A要与B进行连接,则必须</p>      <ul>       <li>第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认。即A发送信息给B</li>       <li>第二次握手:服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认。即B收到连接信息后向A返回确认信息</li>       <li>第三次握手:客户端收到服务器的(SYN+ACK)报文段,并向服务器发送ACK报文段。即A收到确认信息后再次向B返回确认连接信息 <p>此时,A告诉自己上层连接建立;B收到连接信息后告诉上层连接建立。</p> </li>      </ul> </li>    </ul>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1b6eab56f5f0b66009cd1d2957187bc1.png"></p>    <p style="text-align:center">TCP三次握手</p>    <p>这样就完成TCP三次握手 = 一条TCP连接建立完成 = 可以开始发送数据</p>    <ol>     <li>三次握手期间任何一次未收到对面回复都要重发。</li>     <li>最后一个确认报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态。</li>    </ol>    <p>为什么TCP建立连接需要三次握手?</p>    <p>答:防止服务器端因为接收了 <strong>早已失效的连接请求报文</strong> 从而一直等待客户端请求,从而浪费资源</p>    <ul>     <li>“已失效的连接请求报文段”的产生在这样一种情况下:Client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。</li>     <li>这是一个早已失效的报文段。但Server收到此失效的连接请求报文段后,就误认为是Client再次发出的一个新的连接请求。</li>     <li>于是就向Client发出确认报文段,同意建立连接。</li>     <li>假设不采用“三次握手”:只要Server发出确认,新的连接就建立了。</li>     <li>由于现在Client并没有发出建立连接的请求,因此不会向Server发送数据。</li>     <li>但Server却以为新的运输连接已经建立,并一直等待Client发来数据。>- 这样,Server的资源就白白浪费掉了。</li>    </ul>    <p>采用“三次握手”的办法可以防止上述现象发生:</p>    <ul>     <li>Client不会向Server的确认发出确认</li>     <li>Server由于收不到确认,就知道Client并没有要求建立连接</li>     <li>所以Server不会等待Client发送数据,资源就没有被浪费</li>    </ul>    <ul>     <li> <p>TCP释放连接</p> <p>TCP释放连接需要 <strong>四次挥手</strong> 过程,现在假设A主动释放连接:(数据传输结束后,通信的双方都可释放连接)</p>      <ul>       <li>第一次挥手:A发送释放信息到B;(发出去之后,A->B发送数据这条路径就断了)</li>       <li> <p>第二次挥手:B收到A的释放信息之后,回复确认释放的信息:我同意你的释放连接请求</p> </li>       <li> <p>第三次挥手:B发送“请求释放连接“信息给A</p> </li>       <li> <p>第四次挥手:A收到B发送的信息后向B发送确认释放信息:我同意你的释放连接请求</p> <p>B收到确认信息后就会正式关闭连接;</p> <p>A等待2MSL后依然没有收到回复,则证明B端已正常关闭,于是A关闭连接</p> </li>      </ul> </li>    </ul>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c5873efae0ea6357ab07988622aa6374.png"></p>    <p style="text-align:center">TCp四次握手</p>    <p><strong>为什么TCP释放连接需要四次挥手?</strong></p>    <p>为了保证双方都能通知对方“需要释放连接”,即在释放连接后都无法接收或发送消息给对方</p>    <ul>     <li>需要明确的是:TCP是全双工模式,这意味着是双向都可以发送、接收的</li>     <li>释放连接的定义是:双方都无法接收或发送消息给对方,是双向的</li>     <li>当主机1发出“释放连接请求”(FIN报文段)时,只是表示主机1已经没有数据要发送 / 数据已经全部发送完毕; <p>但是,这个时候主机1还是可以接受来自主机2的数据。</p> </li>     <li>当主机2返回“确认释放连接”信息(ACK报文段)时,表示它已经知道主机1没有数据发送了 <p>但此时主机2还是可以发送数据给主机1</p> </li>     <li>当主机2也发送了FIN报文段时,即告诉主机1我也没有数据要发送了 <p>此时,主机1和2已经无法进行通信:主机1无法发送数据给主机2,主机2也无法发送数据给主机1,此时,TCP的连接才算释放</p> </li>    </ul>    <p><strong>1.5 UDP协议</strong></p>    <ul>     <li> <p>定义:User Datagram Protocol,即用户数据报协议,是一种传输层通信协议。</p> <p>基于UDP的应用层协议有TFTP、SNMP与DNS。</p> </li>     <li> <p>特点:无连接的、不可靠的、面向报文、没有拥塞控制</p>      <ul>       <li> <p>无连接的:和TCP要建立连接不同,UDP传输数据不需要建立连接,就像写信,在信封写上收信人名称、地址就可以交给邮局发送了,至于能不能送到,就要看邮局的送信能力和送信过程的困难程度了。</p> </li>       <li> <p>不可靠的:因为UDP发出去的数据包发出去就不管了,不管它会不会到达,所以很可能会出现丢包现象,使传输的数据出错。</p> </li>       <li> <p>面向报文:数据报文,就相当于一个数据包,应用层交给UDP多大的数据包,UDP就照样发送,不会像TCP那样拆分。</p> </li>       <li><strong>没有拥塞控制</strong> :拥塞,是指到达通信子网中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象,就像交通堵塞一样。TCP建立连接后如果发送的数据因为信道质量的原因不能到达目的地,它会不断重发,有可能导致越来越塞,所以需要一个复杂的原理来控制拥塞。而UDP就没有这个烦恼,发出去就不管了。</li>      </ul> </li>     <li> <p>应用场景</p> <p>很多的实时应用(如IP电话、实时视频会议、某些多人同时在线游戏等)要求源主机以很定的速率发送数据,并且允许在网络发生拥塞时候丢失一些数据,但是要求不能有太大的延时,UDP就刚好适合这种要求。所以说,只有不适合的技术,没有真正没用的技术。</p> </li>    </ul>    <p>1.6 HTTP协议</p>    <p>详情请看我写的另外一篇文章 <a href="/misc/goto?guid=4959722678707364032" rel="nofollow,noindex">你需要了解的HTTP知识都在这里了!</a></p>    <h2><strong>2. Socket的定义</strong></h2>    <ul>     <li>即套接字,是其中 计算机网络中运输层和应用层之间的一种一个中间抽象层,也是一个编程接口</li>     <li>成对出现,一对套接字Socket的 <strong>组成</strong> 就是 Socket ={(IP地址1:PORT端口号),(IP地址2:PORT端口号)}</li>    </ul>    <h2><strong>3. Socket具体使用</strong></h2>    <p>Socket可以基于TCP或者UDP协议,由于TCP比UDP常用,所以下面实例中的Socket将基于TCP协议</p>    <h3><strong>3.1 实例Demo1</strong></h3>    <p>下面是一个简单的、基于TCP协议的Socket连接demo</p>    <p>客户端:AndroidStudio实现</p>    <p>服务器端:Eclipse实现</p>    <p>服务器端(用eclipse编译):</p>    <pre>  <code class="language-java">package scut;  import java.io.BufferedReader;    import java.io.InputStream;    import java.io.InputStreamReader;    import java.io.OutputStream;    import java.net.ServerSocket;    import java.net.Socket;      public class Sock {            /**          * 服务器端          *           * @author Administrator          *           */                  // 程序入口              public static void main(String args[]) {                    try {                        // 建立一个ServletSocket ,监听对应的端口,用于监听客户端的连接请求                      ServerSocket serverSocket = new ServerSocket(40004);                        while (true) { // 循环不断接收客户端的请求                            System.out.println("等待客户端请求....");                          Socket socket = serverSocket.accept(); // 等待接收                          System.out.println("收到请求,服务器建立连接...");                            // 返回数据                          OutputStream os = socket.getOutputStream();                          String msg = "服务器已连接成功...";                          os.write(msg.getBytes("utf-8"));                                os.close();                          socket.close();                      }                    } catch (Exception e) {                      e.printStackTrace();                  }              }        }</code></pre>    <p>输出</p>    <pre>  <code class="language-java">收到请求,服务器建立连接...  等待客户端请求....  收到请求,服务器建立连接...  等待客户端请求....</code></pre>    <p>客户端(用Android studio编译):</p>    <pre>  <code class="language-java">package scut.myserversocket;    import android.os.Bundle;  import android.support.design.widget.FloatingActionButton;  import android.support.design.widget.Snackbar;  import android.support.v7.app.AppCompatActivity;  import android.support.v7.widget.Toolbar;  import android.util.Log;  import android.widget.TextView;  import android.widget.Toast;    import java.io.BufferedReader;  import java.io.IOException;  import java.io.InputStreamReader;  import java.io.OutputStream;  import java.net.ServerSocket;  import java.net.Socket;    public class MainActivity extends AppCompatActivity {      private TextView tv;      String str;      boolean running = false;      @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          tv = (TextView) findViewById(R.id.TV);          findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  running = !running;                  new Thread(){                      //建一个线程防止阻塞UI线程                      public void run(){                          super.run();                          while (running){                                try {                                  Socket socket = new Socket("192.168.56.1",40004);                                  //建立连接,因为genymotion的模拟器的本地ip不同于一般的模拟器,所以ip地址要用这个                                  sleep(1000);                                  // 获取服务器返回的数据                                  BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));                                  System.out.println("服务器数据:" + (str = br.readLine()));                                  os.close();                                  br.close();                                  socket.close();                              } catch (IOException e) {                                  e.printStackTrace();                              } catch (InterruptedException e) {                                  e.printStackTrace();                              }                          }                      }                    }.start();                  tv.setText(str);              }          });      }      }</code></pre>    <p>最后还要在Mainifest</p>    <p>输出:</p>    <pre>  <code class="language-java">服务器数据:服务器已连接成功...  服务器数据:服务器已连接成功...  服务器数据:服务器已连接成功...</code></pre>    <h3><strong>3.2 实例Demo2</strong></h3>    <p>一个较复杂、基于TCP的Socket通信demo(可以双向互发信息)</p>    <ol>     <li>服务器端和客户端都是在Android实现</li>     <li>需要两台Android手机连到同一个wifi,使它们处于同一个网段,才能用Socket访问IP地址实现客户端和服务器端的连接通信。</li>    </ol>    <p>服务器端代码.</p>    <p>xml代码:</p>    <pre>  <code class="language-java"><?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:paddingBottom="@dimen/activity_vertical_margin"      android:paddingLeft="@dimen/activity_horizontal_margin"      android:paddingRight="@dimen/activity_horizontal_margin"      android:paddingTop="@dimen/activity_vertical_margin"      android:orientation="vertical"      tools:context="scut.serversocket.MainActivity">          <TextView          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:hint="未连接"          android:id="@+id/tvIP"          android:layout_gravity="center_vertical" />        <TextView          android:id="@+id/tv"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:hint="未连接"          android:textSize="30sp"/>        <EditText          android:id="@+id/etSend"          android:layout_width="fill_parent"          android:layout_height="wrap_content"          android:hint="请输入要发送的内容" />        <Button          android:id="@+id/btnAccept"          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:text="建立" />        <Button          android:id="@+id/btnSend"          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:text="发送" />  </LinearLayout></code></pre>    <p>MainActivity.java:</p>    <pre>  <code class="language-java">package scut.serversocket;    import android.os.Handler;  import android.os.Message;  import android.support.v7.app.AppCompatActivity;  import android.os.Bundle;  import android.view.View;  import android.widget.Button;  import android.widget.EditText;  import android.widget.TextView;  import android.widget.Toast;    import java.io.BufferedReader;  import java.io.IOException;  import java.io.InputStream;  import java.io.InputStreamReader;  import java.io.OutputStream;  import java.io.UnsupportedEncodingException;  import java.net.ServerSocket;  import java.net.Socket;    public class MainActivity extends AppCompatActivity {      private TextView tv = null;      private EditText et = null;      private TextView IPtv = null;      private Button btnSend = null;      private Button btnAcept = null;      private Socket socket;      private ServerSocket mServerSocket = null;      private boolean running = false;      private AcceptThread mAcceptThread;      private ReceiveThread mReceiveThread;      private Handler mHandler = null;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          tv = (TextView) findViewById(R.id.tv);          et = (EditText) findViewById(R.id.etSend);          IPtv = (TextView) findViewById(R.id.tvIP);          btnAcept = (Button) findViewById(R.id.btnAccept);          btnSend = (Button) findViewById(R.id.btnSend);          mHandler = new MyHandler();          btnSend.setEnabled(false);//设置发送按键为不可见          btnAcept.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  //开始监听线程,监听客户端连接                  mAcceptThread = new AcceptThread();                  running = true;                  mAcceptThread.start();                  btnSend.setEnabled(true);//设置发送按键为可见                  IPtv.setText("等待连接");                  btnAcept.setEnabled(false);                }          });          //发送数据按钮          btnSend.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  OutputStream os = null;                  try {                      os = socket.getOutputStream();//获得socket的输出流                      String msg = et.getText().toString()+"\n";  //                    System.out.println(msg);                      os.write(msg.getBytes("utf-8"));//输出EditText的内容                      et.setText("");//发送后输入框清0                      os.flush();                  } catch (IOException e) {                      e.printStackTrace();                  }catch (NullPointerException e) {                      displayToast("未连接不能输出");//防止服务器端关闭导致客户端读到空指针而导致程序崩溃                      }              }          });      }      //定义监听客户端连接的线程      private class AcceptThread extends Thread{          @Override          public void run() {  //            while (running) {                  try {                      mServerSocket = new ServerSocket(40012);//建立一个ServerSocket服务器端                      socket = mServerSocket.accept();//阻塞直到有socket客户端连接  //                System.out.println("连接成功");                      try {                          sleep(500);                      } catch (InterruptedException e) {                          e.printStackTrace();                      }                        Message msg = mHandler.obtainMessage();                      msg.what = 0;                      msg.obj = socket.getInetAddress().getHostAddress();//获取客户端IP地址                      mHandler.sendMessage(msg);//返回连接成功的信息                      //开启mReceiveThread线程接收数据                      mReceiveThread = new ReceiveThread(socket);                      mReceiveThread.start();                  } catch (IOException e) {                      e.printStackTrace();                  }  //            }          }      }        //定义接收数据的线程      private class ReceiveThread extends Thread{          private InputStream is = null;          private String read;          //建立构造函数来获取socket对象的输入流          public ReceiveThread(Socket sk){              try {                  is = sk.getInputStream();              } catch (IOException e) {                  e.printStackTrace();              }          }          @Override          public void run() {              while (running) {                  try {                      sleep(500);                  } catch (InterruptedException e) {                      e.printStackTrace();                  }                  BufferedReader br = null;                  try {                      br = new BufferedReader(new InputStreamReader(is, "UTF-8"));                  } catch (UnsupportedEncodingException e) {                      e.printStackTrace();                  }                  try {                      //读服务器端发来的数据,阻塞直到收到结束符\n或\r                      read = br.readLine();                      System.out.println(read);                  } catch (IOException e) {                      e.printStackTrace();                  } catch (NullPointerException e) {                      running = false;//防止服务器端关闭导致客户端读到空指针而导致程序崩溃                      Message msg2 = mHandler.obtainMessage();                      msg2.what = 2;                      mHandler.sendMessage(msg2);//发送信息通知用户客户端已关闭                      e.printStackTrace();                      break;                  }                  //用Handler把读取到的信息发到主线程                  Message msg = mHandler.obtainMessage();                  msg.what = 1;                  msg.obj = read;                  mHandler.sendMessage(msg);                }          }      }        private void displayToast(String s)      {          Toast.makeText(this, s, Toast.LENGTH_SHORT).show();      }        class MyHandler extends Handler{//在主线程处理Handler传回来的message          @Override          public void handleMessage(Message msg) {              switch (msg.what){                  case 1:                      String str = (String) msg.obj;                      tv.setText(str);                      break;                  case 0:                      IPtv.setText("客户端"+msg.obj+"已连接");                      displayToast("连接成功");                      break;                  case 2:                      displayToast("客户端已断开");                      //清空TextView                      tv.setText(null);//                      IPtv.setText(null);                      try {                          socket.close();                          mServerSocket.close();                      } catch (IOException e) {                          e.printStackTrace();                      }                      btnAcept.setEnabled(true);                      btnSend.setEnabled(false);                      break;              }          }      }        @Override      protected void onDestroy() {          mHandler.removeCallbacksAndMessages(null);//清空消息队列,防止Handler强引用导致内存泄漏      }  }</code></pre>    <p>客户端代码:</p>    <p>xml代码:</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:app="http://schemas.android.com/apk/res-auto"      xmlns:tools="http://schemas.android.com/tools"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:paddingBottom="@dimen/activity_vertical_margin"      android:paddingLeft="@dimen/activity_horizontal_margin"      android:paddingRight="@dimen/activity_horizontal_margin"      android:paddingTop="@dimen/activity_vertical_margin"      android:orientation="vertical"      tools:context="scut.myserversocket.MainActivity"      tools:showIn="@layout/activity_main">          <LinearLayout          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:layout_weight="3">          <TextView              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:hint="未连接"              android:id="@+id/TV"              android:textSize="30sp"/>      </LinearLayout>        <LinearLayout          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_weight="1">          <EditText              android:layout_width="match_parent"              android:layout_height="wrap_content"              android:hint="请输入IP要连接服务器端地址"              android:textSize="20sp"              android:id="@+id/IPet"/>        </LinearLayout>      <LinearLayout          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:layout_weight="1"          android:orientation="horizontal">            <Button              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:text="开启连接"              android:id="@+id/btnStart"              android:layout_weight="1"              />            <Button              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:text="发送消息"              android:id="@+id/btnSend"              android:layout_weight="1"              />            <Button              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:text="取消连接"              android:id="@+id/btnStop"              android:layout_weight="1"              />      </LinearLayout>        <LinearLayout          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:layout_weight="1">          <EditText              android:layout_width="match_parent"              android:layout_height="wrap_content"              android:hint="请输入要发送的数据"              android:id="@+id/et"              />      </LinearLayout>  </LinearLayout></code></pre>    <p>MainActivity.java:</p>    <pre>  <code class="language-java">package scut.clientsocket;    import android.os.Handler;  import android.os.Message;  import android.support.v7.app.AppCompatActivity;  import android.os.Bundle;  import android.view.View;  import android.widget.Button;  import android.widget.EditText;  import android.widget.TextView;  import android.widget.Toast;    import java.io.BufferedReader;  import java.io.IOException;  import java.io.InputStream;  import java.io.InputStreamReader;  import java.io.OutputStream;  import java.net.Socket;    public class MainActivity extends AppCompatActivity implements View.OnClickListener {      private TextView tv;      private EditText et;      private EditText IPet;      private Handler myhandler;      private Socket socket;      private String str = "";      boolean running = false;      private Button btnSend;      private Button btnStart;      private Button btnStop;      private StartThread st;      private ReceiveThread rt;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          tv = (TextView) findViewById(R.id.TV);          et = (EditText) findViewById(R.id.et);          IPet = (EditText) findViewById(R.id.IPet);            btnSend = (Button) findViewById(R.id.btnSend);          btnStart = (Button) findViewById(R.id.btnStart);          btnStop = (Button) findViewById(R.id.btnStop);            setButtonOnStartState(true);//设置按键状态为可开始连接            btnSend.setOnClickListener(this);          btnStart.setOnClickListener(this);          btnStop.setOnClickListener(this);            myhandler = new MyHandler();//实例化Handler,用于进程间的通信        }        @Override      public void onClick(View v) {          switch (v.getId()){              case R.id.btnStart:                  //按下开始连接按键即开始StartThread线程                  st = new StartThread();                  st.start();                  setButtonOnStartState(false);//设置按键状态为不可开始连接                    break;              case R.id.btnSend:                  // 发送请求数据                  OutputStream os = null;                  try {                        os = socket.getOutputStream();//得到socket的输出流                      //输出EditText里面的数据,数据最后加上换行符才可以让服务器端的readline()停止阻塞                      os.write((et.getText().toString()+"\n").getBytes("utf-8"));                      et.setText("");//发送后输入框清0  //                    System.out.println(et.getText().toString()+"\n");                  } catch (IOException e) {                      e.printStackTrace();                  }                    break;              case R.id.btnStop:                  running = false;                  setButtonOnStartState(true);//设置按键状态为不可开始连接                  try {                      socket.close();                  } catch (NullPointerException e) {                      e.printStackTrace();                      displayToast("未连接成功");                  } catch (IOException e) {                      e.printStackTrace();                  }                  break;          }        }  private class StartThread extends Thread{      @Override      public void run() {          try {                socket = new Socket(IPet.getText().toString(),40012);//连接服务端的IP              //启动接收数据的线程              rt = new ReceiveThread(socket);              rt.start();              running = true;              System.out.println(socket.isConnected());              if(socket.isConnected()){//成功连接获取socket对象则发送成功消息                  Message msg0 = myhandler.obtainMessage();                  msg0.what=0;                  myhandler.sendMessage(msg0);                  }          } catch (IOException e) {              e.printStackTrace();          }      }  }        private class ReceiveThread extends Thread{          private InputStream is;          //建立构造函数来获取socket对象的输入流          public ReceiveThread(Socket socket) throws IOException {              is = socket.getInputStream();          }          @Override          public void run() {              while (running) {                  InputStreamReader isr = new InputStreamReader(is);                  BufferedReader br = new BufferedReader(isr);                  try {                      //读服务器端发来的数据,阻塞直到收到结束符\n或\r                      System.out.println(str = br.readLine());                    } catch (NullPointerException e) {                      running = false;//防止服务器端关闭导致客户端读到空指针而导致程序崩溃                      Message msg2 = myhandler.obtainMessage();                      msg2.what = 2;                      myhandler.sendMessage(msg2);//发送信息通知用户客户端已关闭                      e.printStackTrace();                      break;                    } catch (IOException e) {                      e.printStackTrace();                  }                    //用Handler把读取到的信息发到主线程                  Message msg = myhandler.obtainMessage();                      msg.what = 1;  //                }                  msg.obj = str;                  myhandler.sendMessage(msg);                  try {                      sleep(400);                  } catch (InterruptedException e) {                      e.printStackTrace();                  }                }              Message msg2 = myhandler.obtainMessage();              msg2.what = 2;              myhandler.sendMessage(msg2);//发送信息通知用户客户端已关闭            }      }        private void displayToast(String s)//Toast方法      {          Toast.makeText(this, s, Toast.LENGTH_SHORT).show();      }        private void setButtonOnStartState(boolean flag){//设置按钮的状态          btnSend.setEnabled(!flag);          btnStop.setEnabled(!flag);          btnStart.setEnabled(flag);          IPet.setEnabled(flag);      }      class MyHandler extends Handler{//在主线程处理Handler传回来的message      @Override      public void handleMessage(Message msg) {          switch (msg.what) {              case 1:              String str = (String) msg.obj;              System.out.println(msg.obj);              tv.setText(str);//把读到的内容更新到UI              break;              case 0:                  displayToast("连接成功");                  break;              case 2:                  displayToast("服务器端已断开");                  tv.setText(null);                  setButtonOnStartState(true);//设置按键状态为可开始                  break;            }        }  }    }</code></pre>    <p>PS:在操作Socket的输入输出流的时候一定不能close()关闭,一关闭的话就会导致整个Socket关闭,这里搞了我好久。</p>    <p>效果图:</p>    <p>操作:首先服务端点建立按钮,建立ServerSocket,然后客户端输入服务端的IP地址,点开始连接,提示连接成功之后可以发送消息,点取消连接之后回复到初始状态</p>    <p>服务端界面(未连接):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1c1f924b2c78951f53621a71b8f40241.png"></p>    <p>客户端界面(未连接):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/16f3f08851c8b9325abc4b214705c2d6.png"></p>    <p>服务端界面(连接):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/62783e42cb0173fb556a8e40b67a7ea2.png"></p>    <p>客户端界面(连接):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/90642ecb7419458388a28624e13e0c8b.png"></p>    <p>服务端界面(接收到消息):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f3d85c4faca78fe55da29dd2ebacb819.png"></p>    <p>客户端界面(接收到消息):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9ac898382c0a1cb1b617a8fcb0f225ad.png"></p>    <p> </p>    <h2><strong>4. 总结</strong></h2>    <ul>     <li> <p>相信大家已经非常了解关于Socket的使用</p> </li>     <li> <p>接下来,我会继续介绍Android中其他相关知识,有兴趣可以继续关注 Carson_Ho的安卓开发笔记</p> </li>    </ul>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/089fb79e308b</p>    <p> </p>