Socket使用解析(涉及TCP、UDP、HTTP)

1、网络基础部分了解

层次图:计算机网络分为五层(从上至下): 应用层 》》 运输层》》网络层》》数据链路层》》物理层

         其中:

         网络层,负责根据IP找到目的地址的主机

         运输层,根据端口数据传到目的主机的目的


端口号:规定为16位,即允许一个IP主机有2的16次方65535个不同的端口

              0~1023:分配给系统的端口号,在Socket使用时,可以用1024~65535的端口号。


C/S结构



由上图可见,Socket的使用是基于TCP或者UDP协议的


TCP协议   

             是一种传输层通信协议

那么基于TCP的应用层协议有哪些呢?    其中有FTP、Telnet、SMTP、HTTP、POP3与DNS    当然FTP和HTTP我们更为熟悉些

特点:面向连接,面向字节流,全双工通讯,可靠


面向连接:指的是要使用TCP传输数据,必须先建立TCP连接,传输完成后释放连接,就像打电话一样必须先拨号建立一条连接,打完后挂机释放连接。

全双工通信:即一旦建立了TCP连接,通信双方可以在任何时候都能发送数据。

可靠的:指的是通过TCP连接传送的数据,无差错,不丢失,不重复,并且按序到达。

面向字节流:流,指的是流入到进程或从进程流出的字符序列。简单来说,虽然有时候要传输的数据流太大,TCP报文长度有限制,不能一次传输完,要把它分为好几个数据块,但是由于可靠性保证,接收方可以按顺序接收数据块然后重新组成分块之前的数据流,所以TCP看起来就像直接互相传输字节流一样,面向字节流。


TCP如何建立连接?

三次握手

如A和B进行连接,则

第一次握手:建立连接。客户端发送请求连接报文段,将SYN位 置为1,Sequence Number为x,然后客户端进入SYN_SEND的状态,等待服务器的确认,即A发                      送消息给B。

第二次握手:服务器收到客户端SYN报文段,需要对这个SYN报文段进行确认,即B收到连接信息向A返回确认信息。

第三次握手:客户端收到服务器的(SYN_ACK)报文段,向服务器发送ACK报文段,即A收到确认信息后再次向B返回确认连接信息。

此时,A告诉自己上层连接建立;B收到连接信息后告诉上层连接建立


其中这里涉及到SYN   sequence Number   ACK 等一下标志状态位,如何理解?

在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG.

其中,对于我们日常的分析有用的就是前面的五个字段。

它们的含义是:

SYN表示建立连接,

FIN表示关闭连接,

ACK表示响应,

PSH表示有 DATA数据传输,

RST表示连接重置。

位码即tcp标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)Sequence number(顺序号码) Acknowledge number(确认号码)

那么现在再来重新理解一遍:


第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;

第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包;

第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。

完成三次握手,主机A与主机B开始传送数据。




由此,相信同志们应该明白TCP的三次握手了吧!!!

这样就完成TCP三次握手 = 一条TCP连接建立完成 = 可以开始发送数据


三次握手期间任何一次未收到对面回复都要重发。
最后一个确认报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态。


为什么TCP建立连接需要三次握手?

答:防止服务器端因为接收了早已失效的连接请求报文从而一直等待客户端请求,从而浪费资源


“已失效的连接请求报文段”的产生在这样一种情况下:Client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。
这是一个早已失效的报文段。但Server收到此失效的连接请求报文段后,就误认为是Client再次发出的一个新的连接请求。
于是就向Client发出确认报文段,同意建立连接。
假设不采用“三次握手”:只要Server发出确认,新的连接就建立了。
由于现在Client并没有发出建立连接的请求,因此不会向Server发送数据。
但Server却以为新的运输连接已经建立,并一直等待Client发来数据。>- 这样,Server的资源就白白浪费掉了。
采用“三次握手”的办法可以防止上述现象发生:


Client不会向Server的确认发出确认
Server由于收不到确认,就知道Client并没有要求建立连接
所以Server不会等待Client发送数据,资源就没有被浪费


TCP释放连接
TCP释放连接需要四次挥手过程,现在假设A主动释放连接:(数据传输结束后,通信的双方都可释放连接)


第一次挥手:A发送释放信息到B;(发出去之后,A->B发送数据这条路径就断了)
第二次挥手:B收到A的释放信息之后,回复确认释放的信息:我同意你的释放连接请求


第三次挥手:B发送“请求释放连接“信息给A


第四次挥手:A收到B发送的信息后向B发送确认释放信息:我同意你的释放连接请求


B收到确认信息后就会正式关闭连接;
A等待2MSL后依然没有收到回复,则证明B端已正常关闭,于是A关闭连接

为什么TCP释放连接需要四次挥手?

为了保证双方都能通知对方“需要释放连接”,即在释放连接后都无法接收或发送消息给对方

  • 需要明确的是:TCP是全双工模式,这意味着是双向都可以发送、接收的
  • 释放连接的定义是:双方都无法接收或发送消息给对方,是双向的
  • 当主机1发出“释放连接请求”(FIN报文段)时,只是表示主机1已经没有数据要发送 / 数据已经全部发送完毕;

    但是,这个时候主机1还是可以接受来自主机2的数据。

  • 当主机2返回“确认释放连接”信息(ACK报文段)时,表示它已经知道主机1没有数据发送了

    但此时主机2还是可以发送数据给主机1

  • 当主机2也发送了FIN报文段时,即告诉主机1我也没有数据要发送了

    此时,主机1和2已经无法进行通信:主机1无法发送数据给主机2,主机2也无法发送数据给主机1,此时,TCP的连接才算释放

UDP协议

   特点:无连接的、不可靠的、面向报文、没有拥塞控制  无连接的:和TCP要建立连接不同,UDP传输数据不需要建立连接,就像写信,在信封写上收信人名称、地址就可以交给邮局发送了,至于能不能送到,就要看邮局的送信能力和送信过程的困难程度了。


不可靠的:因为UDP发出去的数据包发出去就不管了,不管它会不会到达,所以很可能会出现丢包现象,使传输的数据出错。

面向报文:数据报文,就相当于一个数据包,应用层交给UDP多大的数据包,UDP就照样发送,不会像TCP那样拆分。

没有拥塞控制:拥塞,是指到达通信子网中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象,就像交通堵塞一样。TCP建立连接后如果发送的数据因为信道质量的原因不能到达目的地,它会不断重发,有可能导致越来越塞,所以需要一个复杂的原理来控制拥塞。而UDP就没有这个烦恼,发出去就不管了。


HTTP协议
交互的数据单元称为报文,基本上是基于C/S方式。
 特点
无连接:HTTP本身是无连接的,即交换HTTP报文前不需要建立HTTP连接
无状态:HTTP协议是无状态的:数据传输过程中,并不保存任何历史信息和状态信息。无状态特性简化了服务器的设计,使服务器更容易支持大量并发的HTPP请求。
传输可靠性高:采用TCP作为运输层协议(面向连接、可靠传输),即交换报文时需要预先建立TCP连接
兼容性好:支持B/S模式及C/S模式;
简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST
灵活:HTTP 允许传输任意类型的数据对象




HTTP的报文分为请求报文和响应报文

HTTP请求报文的组成  (请求行+请求头+请求体

请求行:用于声明”请求报文“、主机域名、资源路径和协议版本
请求头:说明客户端、服务器或报文的部分信息
请求体:用于存放需要发送给服务器的数据信息


1、请求行


空格不能省略


1.1 请求方法
即对请求对象的操作,请求方法有8种:



方法类别 意义
OPTION 请求“选项”的信息
HEAD 请求读取”URL标志信息的首部“信息
GET 请求读取“URL标志的信息“的信息
POST 为服务器添加信息
PUT 为指定的URL下添加(存储)一个文档
DELETE 删除指定URL所标志的信息
TRACE 用于进行环回测试的请求报文
CONNECT 用于代理服务器


最常用的就是GET和POST方法。

1.2 请求路径
要了解请求地址,先来了解下URL概念:


定义:Uniform Resoure Locator,统一资源定位符,是一种自愿位置的抽象唯一识别方法。
作用:用于表示资源位置和访问这些资源的方法
组成:
<协议>://<主机>:<端口>/<路径>
协议:采用的应用层通信协议,比如在HTTP协议下的URL地址:
HTTP://<主机>:<端口>/<路径>
主机:请求资源所在主机的域名
端口和路径有时可以省略(HTTP默认端口号是80)
从上面可以了解到,路径则是端口号后面符号”/“的部分,下面举例
URL(统一资源定位符) PATH(路径)
http://www.baidu.com/ /
http://www.weibo.com/2874748/home /2874748/home


1.3 协议版本
HTTP协议版本主要是1.0、1.1、2.0


请求行举例

先假设:


URL地址为:http://www.tsinghua.edu.cn/chn/yxsz/index.htm
请求报文采用GET方法
请求报文采用HTTP1.1版本
则请求行是:GET /chn/yxsz/index.htm HTTP/1.1


2. 请求头

作用:说明客户端、服务器或报文的部分信息
使用方式:采用”header(字段名):value(值)“的方式
常用请求头
1. 请求和响应报文的通用Header


常见请求Header


举例:
(URL地址:http://www.tsinghua.edu.cn/chn/yxsz/index.htm)
Host:www.tsinghua.edu.cn (表示主机域名)
User - Agent:Mozilla/5.0 (表示用户代理是使用Netscape浏览器)



3. 请求体


作用:用于存放需要发送给服务器的数据信息
使用方式:目前来说,一共有三种
1. 数据交换格式
请求体是可以是任意类型的,但服务器需要额外进行解析,如JSON


{"skill":{
          "web":[
                 {
                  "name":"html",
                  "year":"5"
                 },
                 {
                  "name":"ht",
                  "year":"4"
                 }],
           "database":[
                  {
                  "name":"h",
                  "year":"2"
                 }]
`}}
想详细了解Android开发中的JSON解析可以看下我写的另外一篇文章:
Android开发:JSON简介及最全面解析方法!


2.键值对形式
键与值之间用”=“连接,每个键值对间用&连接,且只能用ASCII字符,如Query String

key1=value1&key2&value2

3. 分部分形式
请求体被分为多个部分,应用场景是文件上传,比如邮件上传等等


每段以-- {boundary}开头
然后是该段的描述头
描述头之后空一行接内容
每段以-- {boundary}--结束
如下:


请求报文实例


结合上述说的请求行、请求头和请求体,现假设


URL地址为:http://www.tsinghua.edu.cn/chn/yxsz/index.htm
请求报文采用GET方法
请求报文采用HTTP1.1版本
请求报文希望表明主机域名和用户代理是使用Netscape浏览器
请求体采用键值对形式
则请求报文如下:



HTTP响应报文  (状态行+响应头+响应体)

1. 状态行


其中,空格不能省

1.1 协议版本
HTTP协议版本主要是1.0、1.1、2.0

1.2 状态码
状态码分为5大类:



类别 含义
1xx 表示信息通知,如请求收到了或正在进行处理
2xx 表示成功,如接受或知道了
3xx 表示重定向,如要完成请求还必须采取进一步行动
4xx 客户的差错,如请求中有错误的语法或不能完成:404
5xx 表示服务器的差错,如服务器失效无法完成请求

1.3 状态信息
对状态码的简单解释

状态行举例
HTTP/1.1 202 Accepted(接受)
HTTP/1.1 301 Bad Request(永久性转移)
HTTP/1.1 404 Not Found(找不到)


2. 响应头


作用:说明客户端、服务器或报文的部分信息
使用方式:采用”header(字段名):value(值)“的方式
常用请求头
1. 请求和响应报文的通用Header


2. 常见响应Header


3. 响应体
  • 作用:用于存放需要返回给客户端的数据信息
  • 使用方式:和请求体是一致的,同样分为:任意类型的数据交换格式、键值对形式和分部分形式,这里不作过多描述。
如需了解更详细的状态码,可以通过这个网址进行了解:

http://tool.oschina.net/commons?type=5

 Socket具体使用

  • Socket可基于TCP或者UDP协议,但TCP更加常用

    由于TCP比UDP常用,所以下面实例中的Socket将基于TCP协议

一个较复杂、基于TCP的Socket通信demo(可以双向互发信息)

  1. 服务器端和客户端都是在Android实现
  2. 需要两台Android手机连到同一个wifi,使它们处于同一个网段,才能用Socket访问IP地址实现客户端和服务器端的连接通信。

服务器端代码.

xml代码:

<?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>

MainActivity.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强引用导致内存泄漏
    }
}

客户端代码:
xml代码:

<?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>

MainActivity.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;

        }

    }
}

}
以上大部分内容,编辑资料来源于:http://www.jianshu.com/p/089fb79e308b 

Android:最全面的Socket使用解析

作者 Carson_Ho

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值