Android之TCP/UDP通信-ServerSocketSocketDatagramSocket使用

时间:2024-04-08 06:59:24

在日常开发中采用Http协议进行数据传输的情况非常多,但这都是APP主动请求服务端,将数据传到服务器或者从服务器下载数据;那么如果我们想服务器主动联系我们或者我们频繁的上报消息给服务器,怎么弄呢,显然这时候用Http就不是一个好的方案了,而Socket是一个好的方法。

平时碰到的网络协议很多,很容易跟socket搞混,怎么区分

IP :是网络层协议

TCP/UDP :传输层协议    

HTTP : 应用层协议

SOCKET : TCP/IP 网络的抽象接口

通常所说的TCP/IP代表传输控制协议,是一系列协议

TCP和UDP使用IP协议从一个网络传输数据到另一个网络,把IP想象成一条高速公路,TCP和UDP想象成一辆辆卡车,高数公路允许其它卡车行驶,也就是IP协议允许其它协议行驶并找到其它电脑出口,而TCP和UDP这样的卡车装的货物就是像HTTP    FTP这样的应用层协议;也可以说UDP和TCP是FTP,HTTP所使用的传输层协议。

虽然TCP和UDP都是使用IP协议来传输其它应用层协议的,但它们之间有显著不同,TCP提供有保障的数据传输,也就是有一个稳定的机制确保数据从一个端口传到另一个端口,比如打电话,必须对面接电话,你们之间的通话才会传输;而UDP不提供,就像发短信,不需要看对方,单方面直接发就行了。

HTTP是利用TCP在两台机器间(通常是web服务器和客户端)之间传输的协议

这些协议进行传输的前提是需要用IP协议来连接网络,比如货物没有卡车运输不行,卡车没有路跑不起来

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,是一组接口,不是协议;socket把复杂的TCP/IP协议隐藏在接口后面,对开发者来说,一组接口就是全部,用Socket来组装传输数据。

如下图

Android之TCP/UDP通信-ServerSocket\Socket\DatagramSocket使用

 

TCP通信 

Tranfer Control protocol 的简称,是一种面向连接的可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCp协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。

开发流程一般是

服务端:构建一个ServerSocket实例,指定一个本地端口;调用ServerSocket的accept()方法获取一个Socket(客户端)实例,这样就建立了与客户端的连接;通过这个Socket获取inputStream和outputStream,来进行写数据和读数据;最后调用Socket的close方法关闭与客户端的连接。

客户端:构建Socket实例,通过指定远程服务器地址和端口来建立与服务器的连接;通过这个Socket获取inputStream和outputStream,来进行写数据和读数据;最后调用Socket的close方法关闭与客户端的连接。

使用前先看下ServerSocket的API:

● ServerSocket( )

使用该构造方法在创建ServerSocket对象时并没有绑定端口号,这样的对象创建的服务器端没有监听任何端口,不能直接使用,还需要继续调用bind(SocketAdress endpoint)方法将其绑定到指定的端口号上才可以使用。

这个默认构造方法的用途是,允许服务器在绑定到特定端口之前,先设置ServerSocket的一些选项。因为一旦服务器与特定端口绑定,有些选项就不能再改变了。

如下代码

ServerSocket serverSocket=new ServerSocket();  
serverSocket.setReuseAddress(true);      //设置ServerSocket的选项  

serverSocket.bind(new InetSocketAddress(8000));   //与8000端口绑定  

ServerSocket serverSocket=new ServerSocket(8000);  

serverSocket.setReuseAddress(true);      //设置ServerSocket的选项  

在第二种例子里,serverSocket.setReuseAddress(true)方法就不起任何作用了,因为SO_ REUSEADDR选项必须在服务器绑定端口之前设置才有效

● ServerSocket(intport )

 

使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口上。端口号可以指定为0,此时系统就会分配给一个还没有被其他网络程序所使用的端口号。由于客户端需要根据指定的端口号来访问服务器端程序,因此端口号随机分配的情况并不常用,通常会让服务器端程序监听一个指定的端口号。

● ServerSocket(intport ,int backlog )

 

 

该构造方法就是在第二个构造方法基础上,增加了一个bscklog参数。管理客户连接请求的任务是由操作系统来完成的。操作系统把这些连接请求存储在一个先进先出的队列中。许多操作系统限定了队列的最大长度,一般为50。当队列中的连接请求达到了队列的最大容量时,服务器进程所在的主机会拒绝新的连接请求。只有当服务器进程通过ServerSocket的accept()方法从队列中取出连接请求,使队列腾出空位时,队列才能继续加入新的连接请求。

对于客户进程,如果它发出的连接请求被加入到服务器的队列中,就意味着客户与服务器的连接建立成功,客户进程从Socket构造方法中正常返回。如果客户进程发出的连接请求被服务器拒绝,Socket构造方法就会抛出ConnectionException。
ServerSocket构造方法的backlog参数用来显式设置连接请求队列的长度,它将覆盖操作系统限定的队列的最大长度。值得注意的是,在以下几种情况中,仍然会采用操作系统限定的队列的最大长度:
◆backlog参数的值大于操作系统限定的队列的最大长度;
◆backlog参数的值小于或等于0;

 

 

◆在ServerSocket构造方法中没有设置backlog参数。

● ServerSocket(intport ,int backlog,InetAdress bindAddr )

 

 

该构造方法就是在第三种构造方法的基础上,还制定了相关的IP地址,这种情况适用于计算机上有多块网卡和多个IP的情况,我们可以明确规定ServerSocket在那块网卡或者IP地址上等待客户的连接请求,显然对于只有一块网卡的情况我们就不用专门指定了。

 

 

 isClosed()

判断ServerSocket是否关闭,只有执行了ServerSocket的close()方法,isClosed()方法才返回true;否则,即使ServerSocket还没有和特定端口绑定,isClosed()方法也会返回false。

 isBound()

判断ServerSocket是否已经与一个端口绑定,只要ServerSocket已经与一个端口绑定,即使它已经被关闭,isBound()方法也会返回true。

 setSoTimeout(int timeout)

设置获取客户端连接的超时时间,默认是0,表示永远不会超时;如果accept()方法阻塞时间超过了这个设置的时间,就会抛出SocketTimeoutException异常

● setResuseAddress(boolean on)

设置是否允许重用端口,这个默认值其实与操作系统有关系,有的操作系统是不允许重用端口的;当一个ServerSocket关闭后,这个端口可能不会立即被释放,确保接受了网络数据,然后再释放;如果在这个延迟的时间内,另一个ServerSocket启用也绑定了这个端口,如果设置false,就会绑定失败,抛出BindException异常;如果设置true,那这个端口就可以正常绑定。

另外需要注意的是setResuseAddress(true)方法必须在ServerSocket还没有绑定到一个本地端口之前调用,否则无效。此外,两个共用同一个端口的进程必须都调用serverSocket.setResuseAddress(true)方法,才能使得一个进程关闭ServerSocket后,另一个进程的ServerSocket还能够立刻重用相同端口。

● setReceiveBufferSize(int size)

设置服务器端的用于接收数据的缓冲区的大小,以字节为单位。如果要设置大于64K的缓冲区,则必须在ServerSocket绑定到特定端口之前进行设置才有效

ServerSocket serverSocket=new ServerSocket();  
int size=serverSocket.getReceiveBufferSize();  
if(size<131072) serverSocket.setReceiveBufferSize(131072);  //把缓冲区的大小设为128K        

serverSocket.bind(new InetSocketAddress(8000));     //与8000端口绑定  

再看下Socket的API

● Socket( )
使用该构造方法在创建Socket对象时,并没有指定IP地址和端口号,也就意味着只创建了客户端对象,并没与去连接任何服务器。
通过该构造方法创建对象后,还需要调用connect(SocketAdress endpoint)方法,才能完成与指定服务器端的连接,其中参数endpoint用于封装IP地址和端口号。
● Socket(Stringhost, int port )
使用该构造方法在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host接收的是一个字符串类型的IP地址。
● Socket(InetAdressaddress ,int port )

该方法在使用上与第二种构造方法类似,参数address用于接收一个InetAddress类型的对象,该对象用于封装一个IP地址。

● getInetAddress().getHostAddress()      

获得远程server的IP 地址. 

● getPort()                   

获得远程server的port. 

getLocalAddress().getHostAddress()   

获得客户本地的IP 地址. 

● getLocalPort()          

获得客户本地的port. 

● getInputStream()

 获得输入流. 假设Socket 还没有连接, 或者已经关闭, 或者已经通过 shutdownInput() 方法关闭输入流, 那么此方法会抛出IOException. 

● getOutputStream()

获得输出流, 假设Socket 还没有连接, 或者已经关闭, 或者已经通过 shutdownOutput() 方法关闭输出流, 那么此方法会抛出IOException. 

● setTcpNoDelay(boolean on)

默认情况下, 发送数据採用Negale 算法. Negale 算法是指发送方发送的数据不会马上发出, 而是先放在缓冲区, 等缓存区满了再发出. 发送完一批数据后, 会等接收方对这批数据的回应, 然后再发送下一批数据. Negale 算法适用于发送方须要发送大批量数据, 而且接收方会及时作出回应的场合, 这样的算法通过降低数据传输的次数来提高通信效率.

假设发送方持续地发送小批量的数据, 而且接收方不一定会马上发送响应数据, 那么Negale 算法会使发送方执行非常慢. 对于GUI 程序, 如网络游戏程序(server须要实时跟踪client鼠标的移动), 这个问题尤其突出. client鼠标位置修改的信息须要实时发送到server上, 因为Negale 算法有缓冲, 大大减低了实时响应速度, 导致客户程序执行非常慢.

TCP_NODELAY 的默认值为 false, 表示采用 Negale 算法. 假设调用setTcpNoDelay(true)方法, 就会关闭 Socket的缓冲, 确保数据及时发送:
if(!socket.getTcpNoDelay()) socket.setTcpNoDelay(true);                                                                                   

假设Socket 的底层实现不支持TCP_NODELAY 选项, 那么getTcpNoDelay() 和 setTcpNoDelay 方法会抛出 SocketException.

● setResuseAddress(boolean on) 

当接收方通过Socket 的close() 方法关闭Socket 时, 假设网络上还有发送到这个Socket 的数据, 那么底层的Socket 不会马上释放本地port, 而是会等待一段时间, 确保接收到了网络上发送过来的延迟数据, 然后再释放port. Socket接收到延迟数据后, 不会对这些数据作什么处理. Socket 接收延迟数据的目的是, 确保这些数据不会被其它碰巧绑定到相同port的新进程接收到.

当server程序关闭后, 有可能它的port还会被占用一段时间, 假设此时立马在同一个主机上重新启动server程序, 因为port已经被占用, 使得server程序无法绑定到该port, 启动失败.为了确保一个进程关闭Socket 后, 即使它还没释放port, 同一个主机上的其它进程还能够马上重用该port, 能够调用Socket 的setResuseAddress(true) 方法:                                                                   

值得注意的是 socket.setResuseAddress(true) 方法必须在 Socket 还没有绑定到一个本地port之前调用, 否则运行 socket.setResuseAddress(true) 方法无效. 因此必须依照下面方式创建Socket 对象, 然后再连接远程server:
Socket socket = new Socket();              //此时Socke 对象为绑定本地port, 而且未连接远程server
socket.setReuseAddress(true);
SocketAddress localAddr = new InetSocketAddress("localhost",9000);
SocketAddress remoteAddr = new InetSocketAddress("localhost",8000);
socket.bind(localAddr);             //与本地port绑定
socket.connect(remoteAddr); //连接远程server

此外, 两个共用同一个port的进程必须都调用 socket.setResuseAddress(true) 方法, 才可使得一个进程关闭 Socket后, 还有一个进程的 Socket 可以马上重用同样port.

● setSoTimeout(int milliseconds) 

设定输入流读数据的等待超时时间, 单位为毫秒, 它的默认值为 0, 表示会无限等待, 永远不会超时.

 ● setSoLinger(boolean on, int seconds)

设置 Socket 关闭时的行为. 默认情况下, 运行 Socket 的 close() 方法, 该方法会马上返回, 但底层的 Socket 实际上并不马上关闭, 它会延迟一段时间, 直到发送全然部剩余的数据, 才会真正关闭 Socket, 断开连接.

假设运行下面方法:
socket.setSoLinger(true, 0);                                                                                               
那么运行Socket 的close() 方法, 该方法也会马上返回, 而且底层的 Socket 也会马上关闭, 全部未发送完的剩余数据被丢弃.
假设运行下面方法
socket.setSoLinger(true, 3600);                                                                                          
那么运行Socket 的 close() 方法, 该方法不会马上返回, 而是进入堵塞状态. 同一时候, 底层的 Socket 会尝试发送剩余的数据. 仅仅有满足下面两个条件之中的一个, close() 方法才返回:
⑴ 底层的 Socket 已经发送全然部的剩余数据;
⑵ 虽然底层的 Socket 还没有发送全然部的剩余数据, 但已经堵塞了 3600 秒(注意这里是秒, 而非毫秒), close() 方法的堵塞时间超过 3600 秒, 也会返回, 剩余未发送的数据被丢弃.

值得注意的是, 在以上两种情况内, 当close() 方法返回后, 底层的 Socket 会被关闭, 断开连接. 此外, setSoLinger(boolean on, int seconds) 方法中的 seconds 參数以秒为单位, 而不是以毫秒为单位.    
假设未设置 SO_LINGER 选项, getSoLinger() 返回的结果是 -1, 假设设置了 socket.setSoLinger(true, 80) , getSoLinger() 返回的结果是 80.

当程序通过输出流写数据时, 只表示程序向网络提交了一批数据, 由网络负责输送到接收方. 当程序关闭 Socket, 有可能这批数据还在网络上传输, 还未到达接收方. 这里所说的 "未发送完的数据" 就是指这样的还在网络上传输, 未被接收方接收的数据.

● setReceiveBufferSize(int size) 

设置 Socket 的用于输入数据的缓冲区的大小. 一般说来, 传输大的连续的数据块(基于HTTP 或 FTP 协议的通信) 能够使用较大的缓冲区, 这能够降低数据传输的次数, 提高数据传输的效率. 而对于交互频繁且单次传送数据量比較小的通信方式(Telnet 和 网络游戏), 则应该採用小的缓冲区, 确保小批量的数据能及时发送给对方. 这样的设定缓冲区大小的原则也相同适用于 Socket 的 SO_SNDBUF 选项.

假设底层 Socket 不支持 SO_RCVBUF 选项, 那么 setReceiveBufferSize() 方法会抛出 SocketException.

● setSendBufferSize(int size)

设置 Socket 的用于输出数据的缓冲区的大小. 假设底层 Socket 不支持 SO_SNDBUF 选项, setSendBufferSize() 方法会抛出 SocketException.

● setKeepAlive(boolean on)

设置为 true 时, 表示底层的TCP 实现会监视该连接是否有效. 当连接处于空暇状态(连接的两端没有互相传送数据) 超过了 2 小时时, 本地的TCP 实现会发送一个数据包给远程的 Socket. 假设远程Socket 没有发回响应, TCP实现就会持续尝试 11 分钟, 直到接收到响应为止. 假设在 12 分钟内未收到响应, TCP 实现就会自己主动关闭本地Socket, 断开连接. 在不同的网络平台上, TCP实现尝试与远程Socket 对话的时限有所区别.

默认值为 false, 表示TCP 不会监视连接是否有效, 不活动的client可能会永远存在下去, 而不会注意到server已经崩溃.

● setOOBInline(boolean on) 

当 设置为true 时, 表示支持发送一个字节的 TCP 紧急数据. Socket 类的 sendUrgentData(int data) 方法用于发送一个字节的 TCP紧急数据.
默认值为 false, 在这样的情况下, 当接收方收到紧急数据时不作什么处理, 直接将其丢弃. 假设用户希望发送紧急数据, 应该把 设为 true,此时收件人将接收紧急数据

具体看代码

服务端

public class SocketServer {
    
    public static void main(String[] args) {
        
        new Thread(new RecevieMsgTask()).start();
        new Thread(new SendMsgTask()).start();
    }
    
    static class RecevieMsgTask implements Runnable {

        ServerSocket sServer = null;
        Socket sClient = null;
        BufferedReader br = null;
        BufferedWriter bw = null;;
        
        @Override
        public void run() {
            
            //每次重新获取前释放掉资源
            try {
                if(br != null) 
                    br.close();
                br = null;
                if(bw != null){
                    bw.flush();
                    bw.close();
                    bw = null;
                }
                if(sClient != null) 
                    sClient.close();
                sClient = null;
           } catch (IOException e) {
               sClient = null;
               br = null;
               bw = null;
               e.printStackTrace();
           }
            
            try {  
                if(sServer == null){
                    sServer = new ServerSocket(1935);  
                }
                System.out.println("启动服务器....");  
                
                while (true) {
                    
                    //获取连接过来的客户端  会一直堵塞,直到获取成功才往下走
                    sClient = sServer.accept();
                    System.out.println("APP客户端:"+sClient.getInetAddress().getHostAddress()+"已连接到服务器");  
                     //如果是多个客户端,就把下面这段代码放到一个线程里执行
                    br = new BufferedReader(
                            new InputStreamReader(sClient.getInputStream(),"UTF-8"));  
                    bw = new BufferedWriter(
                            new OutputStreamWriter(sClient.getOutputStream(),"UTF-8"));  
                    while (true) {
                        //读取客户端发送来的消息  会一直堵塞 直到读取成功才往下走
                        String mess = br.readLine();  
                        if(mess == null) return;
                        System.out.println("收到手机消息:"+mess); 
                        //收到手机消息后就返回一些信息
                        bw.write("success"+"\n");  
                        bw.flush();
                    }
                }
                
             } catch (IOException e) { 
                 run();
                 e.printStackTrace();  
             }  
        }
    }
    
    static class SendMsgTask implements Runnable{

        Socket sClient = null;
        BufferedWriter br = null;
        
        @Override
        public void run() {
            
            try {
                connect();
                for(int i=0; ;i++){
                    System.out.println("发消息给手机");
                    br.write("服务端主动发第"+i+"消息给客户端\n"); 
                    br.flush();
                    Thread.sleep(5000);
                }
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                run();//如果APP的ServerSocket断了,那就尝试重新连接
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } 
        }
        
        public void connect() throws InterruptedException{
            
            try {
                if(br != null) 
                    br.close();
                br = null;
                if(sClient != null) 
                    sClient.close();
                sClient = null;
            } catch (IOException e) {
                br = null;
                sClient = null;
                e.printStackTrace();
            }
            System.out.println("connect APPserver");
            try {
                sClient = new Socket("10.47.105.233", 5556);
                br = new BufferedWriter(new OutputStreamWriter(sClient.getOutputStream(), "UTF-8"));
            } catch (IOException e) {
                Thread.sleep(1500);
                connect();//连接App的ServerSocket失败就重新连接
                e.printStackTrace();
            }
        }
 }

第一个线程是启用一个ServerSocket,也就是服务端,然后不断的获取连接过来的客户端,一直去读客户端发过来的消息;

第二个线程是启用一个Socket,也就是客户端,去连接APP上的ServerSocket,不断的往APP发消息

接下来就是APP上的写法

public class MainActivity extends Activity implements OnClickListener {
    
    private String TAG = "MainActivity";

    private boolean isStop;
    
    private TextView tv_receive;
    private EditText et_send;
    private Button connect;
    private Button send;
    private Button start;
    private Button stop;
    
    private String ip = "10.47.102.18";
    private int port = 1935;
    
    private Socket sClient;
    private BufferedWriter bwClient;
    private BufferedReader br;
    
    private User user;
    
    private Thread mServer;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        tv_receive = (TextView) findViewById(R.id.tv_receive);
        et_send = (EditText) findViewById(R.id.et_send);
        connect = (Button) findViewById(R.id.connect);
        send = (Button) findViewById(R.id.send);
        start = (Button) findViewById(R.id.start);
        stop = (Button) findViewById(R.id.stop);
        
        connect.setOnClickListener(this);
        send.setOnClickListener(this);
        start.setOnClickListener(this);
        stop.setOnClickListener(this);
        user = new User();
    }
    
    @Override
    public void onClick(View v) {
       
        int id = v.getId();
        switch (id) {
        case R.id.connect:
            Log.e(TAG, "connect");
            if(sClient == null){
                new ConnectTask().execute();
            }
            break;
        case R.id.send:
            Log.e(TAG, "send");
            String content = et_send.getText().toString().trim();
            if(TextUtils.isEmpty(content)){
                Toast.makeText(this, "请输入内容", 0).show();
                return;
            }
            user.setSendMsg(content+"\n");
            break;  
        case R.id.start:
            Log.e(TAG, "start");
            isStop = false;
            mServer = null;
            mServer = new Thread(new GetClientTask());
            mServer.start();
            break;
        case R.id.stop :
            Log.e(TAG, "stop");
            destorySocket();
            break;
        }
        
    }
    
    class ConnectTask extends AsyncTask<Void, Void, String>{

        @Override
        protected String doInBackground(Void... params) {
            String result = "0";
            try {
                sClient = new Socket(ip, port);
                sClient.setSoTimeout(2000);
            } catch (IOException e) {
                Log.e(TAG, "e="+e.getMessage());
                result = "1";
                e.printStackTrace();
            }
            return result;
        }
        
        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            
            if(TextUtils.equals(result, "0")){
                new Thread(new SendMsg()).start();
                Toast.makeText(MainActivity.this, "连接服务器成功", 0).show();
            }else{
                sClient = null;
                Toast.makeText(MainActivity.this, "连接服务器失败", 0).show();
            }
            
        }
        
    }
    
    class SendMsg implements Runnable{
        
        private InputStream is;
        private OutputStream os;
        
        @Override
        public void run() {
            
            try {
                if(sClient != null){
                    is = sClient.getInputStream();
                    os = sClient.getOutputStream();
                    bwClient = new BufferedWriter(new OutputStreamWriter(os,"UTF-8"));
                    br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
                }
                
                while (true) {
                    
                    if(!TextUtils.isEmpty(user.getSendMsg())){
                        bwClient.write(user.getSendMsg());
                        bwClient.flush();
                        user.setSendMsg("");
                        handler.sendEmptyMessage(1);
                        
                        String result = br.readLine();//返回值
                        Log.e(TAG, "服务器返回 result="+result);
                    }
                    Thread.sleep(500);
                }
                
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    Handler handler = new Handler(){
      public void handleMessage(android.os.Message msg) {
          
          if(msg.what == 0){
              if(!TextUtils.isEmpty(user.getReceiceMsg())){
                  tv_receive.append(user.getReceiceMsg()+"\n");
                  user.setReceiceMsg("");
              }
          }else{
              Toast.makeText(getApplicationContext(), "发送成功", 0).show();
          }
          
      };  
    };
        
    ServerSocket sServer = null;
    Socket sClientServer = null;
    BufferedReader brServer = null;
    class GetClientTask implements Runnable{
        
        @Override
        public void run() {
            
            if(isStop) return;
            
            try {
                if(brServer != null) brServer.close();
                brServer = null;
            } catch (Exception e) {
                brServer = null;
            }
            try {
                if(sClientServer != null) sClientServer.close();
                sClientServer = null;
            } catch (Exception e) {
                sClientServer = null;
            }
            
            try {
                Log.e(TAG,"正在启动客户端服务器....");
                if(sServer == null){
                    sServer = new ServerSocket(5556);
                }
                sClientServer = sServer.accept();
                Log.e(TAG,"服务端:"+sClientServer.getInetAddress().
                        getHostAddress()+"已连接到客户端");  
                brServer = new BufferedReader(new InputStreamReader(
                        sClientServer.getInputStream(), "UTF-8"));
                while (true) {
                    if(isStop) return;
                    user.setReceiceMsg(brServer.readLine());
                    handler.sendEmptyMessage(0);
                    Log.e(TAG, "收到服务器消息");
                }
            } catch (IOException e) {
                Log.e(TAG, "GetClientTask e="+e.getMessage());
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                run();
                e.printStackTrace();
            } 
            
        }
    }
    
    @Override
    public void onBackPressed() {
        try {
            if(bwClient != null) 
                bwClient.flush();
                bwClient.close();
            bwClient = null;
        } catch (Exception e) {
            bwClient = null;
        }
        try {
            if(sClient != null)
                sClient.shutdownOutput();
                sClient.close();
            sClient = null;
        } catch (Exception e) {
            sClient = null;
        }
        destorySocket();
        super.onBackPressed();
    }
    
    private void destorySocket(){
        isStop = true;
        try {
            if(brServer != null) brServer.close();
            brServer = null;
        } catch (Exception e) {
            brServer = null;
        }
        try {
            if(sClientServer != null) sClientServer.close();
            sClientServer = null;
        } catch (Exception e) {
            sClientServer = null;
        }
        try {
            if(sServer != null) sServer.close();
            sServer = null;
        } catch (IOException e) {
            sServer = null;
        }
        
        if(mServer != null){
            mServer.interrupt();//在run没有停止时调用.interrupt()没有效果。
            mServer = null;
        }
    }
}

大致流程就是在异步任务里构建Socket,连接服务器,然后不停的读取用户输入信息,发送到服务端;

然后构建一个ServerSocket不停的读取服务端发送过来的消息

TCP通信就这样了

UDP通信

User Datagram protocol 的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达包的顺序,到达目的地的时间以及内容的正确性都是不能被保证的。

有如下特点

1,每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
2,UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。

3,UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方

开发流程其实双方操作基本类似,不像TCP有服务端客户端很明显的区分

开发流程一般是

在服务端

try {
            byte[] buff = new byte[4];
            DatagramSocket ds = new DatagramSocket(1935);
            DatagramPacket dp = new DatagramPacket(buff, buff.length);
            while (true) {
                ds.receive(dp);//此方法会一直阻塞,直到获取到数据
                System.out.println(new String(buff));
            }
            
        } catch (IOException e) {
            System.out.println("e "+e.getMessage());
            e.printStackTrace();

        } 

在客户端

try {
            
            DatagramSocket socket = new DatagramSocket();
            socket.connect(new InetSocketAddress("10.47.102.18", 1935));
            while (true) {
                byte[] buff = "在吗".getBytes();
                System.out.println("length="+buff.length);
                DatagramPacket dp = new DatagramPacket(buff, buff.length);
                socket.send(dp);
                Thread.sleep(2000);
            }
        } catch (IOException e) {
            System.out.println("e="+e.getMessage());
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();

        }

比较麻烦的一点是缓冲区大小的选择。接收数据报时,如果缓冲区不够大,放不下的数据部分会丢失。UDP数据报的数据部分最大为65507(IP最大数据长度65535-IP首部20-UDP首部8),但底层UDP实现设置的最大数据长度可能更小,如8192字节。所以UDP包的大小尽量不要太大。

如果希望复用DatagramPacket对象以避免重复创建多个相似对象的开销,可以使用DatagramPacket的set方法:

public synchronized void setAddress(InetAddress iaddr)
public synchronized void setData(byte[] buf)
public synchronized void setData(byte[] buf, int offset, int length)
public synchronized void setLength(int length)
public synchronized void setPort(int iport)

public synchronized void setSocketAddress(SocketAddress address)

这些方法中最实用的可能是setData(byte[] buf, int offset, int length)方法。通过改变offset的值,可以轻易地实现将一个大数组分成多个数据报发送。 
另外,每当调用DatagramSocket的receive()方法获得一个数据报时,DatagramPacket的length属性会被设置成数据报的数据部分的长度,这会导致后面的数据包的大小无法超过前面的数据包的大小。因此,如果要复用DatagramPacket对象,必须在每次提取数据完毕后调用setLength(buf.length)重置length属性值。