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>