Android 网络编程 TCP 与 UDP
TCP
TCP(Transmission Control Protocol,传输控制协议) 即传输控制协议,是一种传输层通信协议
特点:面向连接、面向字节流、全双工通信、可靠
面向连接:指的是要使用TCP传输数据,必须先建立TCP连接,传输完成后释放连接,就像打电话一样必须先拨号建立一条连接,打完后挂机释放连接。
全双工通信:即一旦建立了TCP连接,通信双方可以在任何时候都能发送数据。
可靠的:指的是通过TCP连接传送的数据,无差错,不丢失,不重复,并且按序到达。
面向字节流:流,指的是流入到进程或从进程流出的字符序列。简单来说,虽然有时候要传输的数据流太大,TCP报文长度有限制,不能一次传输完,要把它分为好几个数据块,但是由于可靠性保证,接收方可以按顺序接收数据块然后重新组成分块之前的数据流,所以TCP看起来就像直接互相传输字节流一样,面向字节流
TCP建立连接:必须经过三次握手
第一次握手:建立连接。客户端发送连接请求报文段
第二次握手:服务器收到客户端的报文段,需要对报文段进行确认,然后返回确认信息
第三次握手:客户端收到服务器的报文段,并向服务器发送报文段。即收到确认信息后再次向服务端返回确认连接信息
为什么TCP建立连接需要三次握手?
防止服务器端因为接收了早已失效的连接请求报文从而一直等待客户端请求,从而浪费资源。
TCP释放连接四次握手
现在假设A主动释放连接:(数据传输结束后,通信的双方都可释放连接),其释放TCP连接的过程如下:
第一次握手: A发送释放信息到B;(发出去之后,A->B发送数据这条路径就断了)
第二次握手: B收到A的释放信息之后,回复确认释放的信息:我同意你的释放连接请求
第三次握手: B发送“请求释放连接“信息给A
第四次握手: A收到B发送的信息后向B发送确认释放信息:我同意你的释放连接请求
为什么TCP释放连接要四次握手?
为了保证双方都能通知对方“需要释放连接”,即在释放连接后都无法接收或发送消息给对方
UDP
(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去!
特点:无连接的、不可靠的、面向报文、没有拥塞控制
无连接的:和TCP要建立连接不同,UDP传输数据不需要建立连接,就像写信,在信封写上收信人名称、地址就可以交给邮局发送了,至于能不能送到,就要看邮局的送信能力和送信过程的困难程度了。
不可靠的:因为UDP发出去的数据包发出去就不管了,不管它会不会到达,所以很可能会出现丢包现象,使传输的数据出错。
面向报文:数据报文,就相当于一个数据包,应用层交给UDP多大的数据包,UDP就照样发送,不会像TCP那样拆分。
没有拥塞控制:拥塞,是指到达通信子网中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象,就像交通堵塞一样。TCP建立连接后如果发送的数据因为信道质量的原因不能到达目的地,它会不断重发,有可能导致越来越塞,所以需要一个复杂的原理来控制拥塞。而UDP就没有这个烦恼,发出去就不管了。
TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保 证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
示例分析:分别使用UDP与TCP实现聊天功能
TCP:常用API:
ServerSocket 服务端
new ServerSocket(int port) —— 创建监听端口号为port的ServerSocket
getLocalPort() —— 获取本地端口,即Socket监听的端口
setSoTimeout(int timeout) —— 设置accept()的连接超时时间
accept() —— 等待连接。返回客户端的Socket实例对象。若设置了超时,连接超时将抛异常
isClosed() —— 连接是否关闭
close() —— 关闭连接
Socket 客户端
setSoTimeout(int timeout) —— 设置read()读取流的超时时间
getInetAddress().getHostAddress() —— 获取客户端的主机IP地址
getPort() —— 获取客户端与本服务器连接端口
getLocalPort() —— 获取本地端口,与serverSocket.getLocalPort()获取的端口一致
getInputStream() —— 获取输入流,用于接收客户端发送过来的信息。一般使用read(byte[] data)来读取,此方法也属于阻塞式,但若设置了读取流的超时时间,超时将抛异常SocketTimeoutException
getOutputStream() —— 获取输出流,用户给客户端发送信息
package com.hejun.socket.TCP; import com.hejun.socket.UDP.MsgPool; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class TCPService { public void start(){ try { //创建ServerSocket服务器套接字,并设置端口号 //注意 端口号应该大于等于1024,因为0~1023内都被系统内部占用了 ServerSocket serverSocket = new ServerSocket(10010); // 设置连接超时时间,不设置,则是一直阻塞等待 // serverSocket.setSoTimeout(8000); MsgPool.getsInstance().start(); while (true){ //等待客户度连接,超时则抛出异常 Socket accept = serverSocket.accept(); System.out.println("IP:" + accept.getInetAddress().getHostAddress() + "PORT:" + accept.getPort() + "IS ONLINE" ); CLientTask cLientTask = new CLientTask(accept); MsgPool.getsInstance().addMsgComingListener(cLientTask); cLientTask.start(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String arg[]){ new TCPService().start(); } }
package com.hejun.socket.UDP; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; public class MsgPool { private static MsgPool sInstance = new MsgPool(); //使用队列存放信息 private LinkedBlockingQueue<String> mQueue = new LinkedBlockingQueue<>(); public static MsgPool getsInstance(){ return sInstance; } private MsgPool() { } //向队列中中放入数据 public void sendMsg(String msg){ try { mQueue.put(msg); } catch (InterruptedException e) { e.printStackTrace(); } } //从队列中拿取数据 public void start(){ new Thread(){ @Override public void run() { try { String msg = mQueue.take(); notifyComingMsg(msg); } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); } private void notifyComingMsg(String msg) { for (MsgComingListener listener: comingListeners){ listener.onMsgComing(msg); } } public interface MsgComingListener{ void onMsgComing(String msg); } private List<MsgComingListener> comingListeners = new ArrayList<>(); public void addMsgComingListener(MsgComingListener comingListener){ comingListeners.add(comingListener); } }
package com.hejun.socket.TCP; import com.hejun.socket.UDP.MsgPool; 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 CLientTask extends Thread implements MsgPool.MsgComingListener{ private Socket mSocket; private InputStream mIs; private OutputStream mOs; public CLientTask(Socket socket){ this.mSocket = socket; try { mIs = socket.getInputStream(); mOs = socket.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } } //接收信息,读取数据 @Override public void run() { BufferedReader br = new BufferedReader(new InputStreamReader(mIs)); String line = null; try{ while ((line = br.readLine()) != null){ System.out.println(mSocket.getPort() + ":" + line); MsgPool.getsInstance().sendMsg(line); } }catch (IOException e){ e.printStackTrace(); } } //发送信息,写入数据 @Override public void onMsgComing(String msg) { try { mOs.write(msg.getBytes()); // mOs.write("\n" .getBytes()); mOs.flush(); } catch (IOException e) { e.printStackTrace(); } } }
UDP:
DatagramSocket:
new DatagramSocket(int port) —— 创建监听端口为port的套接字
setSoTimeout(int timeout) —— 设置接收信息的超时时间。不设置,则一直阻塞
receive(DatagramPacket packet) —— 用数据报packet接收数据,阻塞式。未设置超时时间,一直阻塞,设置了没接收到数据会抛SocketTimeoutException
close() —— 关闭
DatagramPacket:
new DatagramPacket(byte[] data, int length) —— 创建一个data为数据缓冲区,数据最大长度(≤data.length)为length的数据报。有效数据缓冲区应该足够大来装下对方发送过来的全部数据,否则超过缓冲区的数据将丢失。
getLength() —— 获取接收到数据的有效长度
getData() —— 获取数据报中的数据,就是上面的data
getAddress().getHostAddress() —— 获取数据报中的主机IP地址。发送和接收获取的,都是对方IP
getPort() —— 获取数据报中的端口。发送和接收获取的,都是对方IP
package com.hejun.socket.UDP; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Scanner; public class UDPService { private InetAddress mInetAddress; private int port = 8888; private DatagramSocket mSocket; private Scanner scanner; public UDPService() { try { mInetAddress = InetAddress.getLocalHost(); //创建套接字 mSocket = new DatagramSocket(port,mInetAddress); } catch (Exception e) { e.printStackTrace(); } } public void start(){ while(true){ try { //创建数据报 byte[] bytes = new byte[1024]; DatagramPacket received = new DatagramPacket(bytes,bytes.length); mSocket.receive(received); //接收信息 InetAddress address = received.getAddress(); int recevidPort = received.getPort(); String clietContent = new String(received.getData(),0,received.getLength()); System.out.print("address="+address + " port = " + recevidPort + "data="+ clietContent ); //发送信息 scanner = new Scanner(System.in); // scanner.useDelimiter("\n"); String sc = scanner.next(); byte[] bytes1 = sc.getBytes(); DatagramPacket datagramPacket = new DatagramPacket(bytes1,bytes1.length,address,recevidPort); mSocket.send(datagramPacket); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args){ new UDPService().start(); } }
package com.hejun.socket.UDP; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.util.Scanner; public class UDPClient { private String secviceIP = "192.168.141.2"; private InetAddress mInetAddress; private int port = 8888; private DatagramSocket mDatagramSocket; private Scanner scanner; public UDPClient() { try { mInetAddress = InetAddress.getByName(secviceIP); //创建套接字 this.mDatagramSocket = new DatagramSocket(); } catch (SocketException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); } } public void start(){ while (true){ try { //发送信息 scanner = new Scanner(System.in); // scanner.useDelimiter("\n"); String sc = scanner.next(); byte[] bytes = sc.getBytes(); DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,mInetAddress,port); mDatagramSocket.send(datagramPacket); //接收信息 byte[] bytes1 = new byte[1024]; DatagramPacket received = new DatagramPacket(bytes1,bytes1.length); mDatagramSocket.receive(received); String clietContent = new String(received.getData(),0,received.getLength()); System.out.print(clietContent); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args){ new UDPClient().start(); } public void start(String text){ try { byte[] bytes = text.getBytes(); DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,mInetAddress,port); mDatagramSocket.send(datagramPacket); } catch (IOException e) { e.printStackTrace(); } } public String get(){ byte[] bytes1 = new byte[1024]; try { DatagramPacket received = new DatagramPacket(bytes1,bytes1.length); mDatagramSocket.receive(received); String clietContent = new String(received.getData(),0,received.getLength()); return clietContent; } catch (IOException e) { e.printStackTrace(); } return null; } }
DatagramPacket中的数据data最大是65507,超过则会在发送的时候报错: Exception:sendto failed: EMSGSIZE (Message too long)