Java TCP和UDP简介

来源:互联网 发布:网络歌手小右个人 编辑:程序博客网 时间:2024/05/18 17:42

UDP

用户数据报协议,无需建立连接就可以向目标地址和目标端口发送数据报,没有流量控制、拥塞控制、重传机制,这些需要依靠用户进程自己实现。应用领域有远程过程调用(RPC)、实时多媒体应用(语音视频)等。优点是效率高、占用资源少,缺点是不可靠。
UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给UDP多长的报文,UDP就照样发送,一次发送一个报文。报文最大长度有限制,否则会导致IP层分片,最大值最好小于548字节。


单播

服务端,ip地址是192.168.1.105

import java.net.DatagramPacket;import java.net.DatagramSocket;/** * Created by ICE on 2017-05-04 */public class UDPServer {    public static void main(String[] args) throws Exception {        System.out.println("启动UDP服务端");        // 构造DatagramSocket实例,随机指定本地端口        try (DatagramSocket datagramSocket = new DatagramSocket()) {            //设置超时时间为10秒            datagramSocket.setSoTimeout(10000);            while (true) {                // 构造DatagramPacket实例,用来接收最大长度为512字节的数据包                byte[] data = new byte[512];                DatagramPacket receivePacket = new DatagramPacket(data, 512);                // 接收报文,此方法在接收到数据报前一直阻塞                datagramSocket.receive(receivePacket);                System.out.println("客户端地址:" + receivePacket.getAddress().getHostAddress());                System.out.println("客户端端口:" + receivePacket.getPort());                System.out.println("接收到的数据长度:" + receivePacket.getLength());                System.out.println("接收到的数据:" + new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8"));            }        }    }}


客户端,ip地址是192.168.1.139

import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetSocketAddress;/** * Created by ICE on 2017-05-06 */public class UDPClient {    public static void main(String[] args) throws Exception {        {            // 构造DatagramSocket实例,指定本地端口6666。            try (DatagramSocket datagramSocket = new DatagramSocket(6666)) {                for (int i = 0; i < 5; i++) {                    String data = "当前循环:" + i;                    // 构造数据报包,用来将data发送到指定主机上的指定端口号。                    DatagramPacket sendPacket = new DatagramPacket(data.getBytes("UTF-8"), data.getBytes("UTF-8").length, new InetSocketAddress("192.168.1.105", 6666));                    //发送报文                    datagramSocket.send(sendPacket);                }            }        }    }}


服务端输出

启动UDP服务端客户端地址:192.168.1.139客户端端口:58251接收到的数据长度:16接收到的数据:当前循环:0客户端地址:192.168.1.139客户端端口:58251接收到的数据长度:16接收到的数据:当前循环:1客户端地址:192.168.1.139客户端端口:58251接收到的数据长度:16接收到的数据:当前循环:2客户端地址:192.168.1.139客户端端口:58251接收到的数据长度:16接收到的数据:当前循环:3客户端地址:192.168.1.139客户端端口:58251接收到的数据长度:16接收到的数据:当前循环:4Exception in thread "main" java.net.SocketTimeoutException: Receive timed outat java.net.DualStackPlainDatagramSocketImpl.socketReceiveOrPeekData(Native Method)at java.net.DualStackPlainDatagramSocketImpl.receive0(DualStackPlainDatagramSocketImpl.java:120)at java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:144)at java.net.DatagramSocket.receive(DatagramSocket.java:812)at UDPServer.main(UDPServer.java:19)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:497)at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

组播

组播是一对多的通信,允许同时向大量的接收方发送数据包。一个主机可以有多个组播进程,这些进程可以加入到同一个组播组也可以加入到不同的组播组,主机会跟踪记录当前哪些进程属于哪个组播组。进程随时可以要求主机主动告诉路由器加入和退出哪个组播组,而且每隔一段时间(大概1分钟)路由器也会向它所在的LAN发送一个查询数据包,要求主机告诉路由器它自己属于哪个组播组。支持组播的路由器负责向所有组内成员发送数据包,但不确保每个成员一定会收到。目的地址是组播ip的数据包会被路由器转发到对应组播组内的主机。
组播ip地址是D类地址,可以使用224.0.2.0~238.255.255.255这个范围的ip地址,组播进程根据组播ip进行分组。组播进程监听某端口并通过此端口接收数据,当一个组播数据包被转发到主机上的时候,主机会根据数据包的目的端口将数据包交给监听此目的端口的组播进程。

如果主机是多网卡,那么此时就需要注意了,一定要设置用哪个网卡发送和接受数据,因为组播是无法跨网段的,否则会导致数据接收不到。


发送端,ip地址是192.168.22.1。MulticastSocket继承于DatagramSocket,因此可以发送也可以接收数据包。MulticastSocket绑定的端口是接收和发送数据的,如果数据包目的端口和此端口一致,则这个程序就能接收到数据包。setNetworkInterface方法是用来绑定网卡的。joinGroup告诉主机该程序要加入到哪个组播组,leaveGroup则是退出组播组。其他用法和DatagramSocket基本一致。

import java.net.DatagramPacket;import java.net.InetAddress;import java.net.MulticastSocket;import java.net.NetworkInterface;public class MultiCastNode1 {    public static void main(String[] args) throws Exception {        InetAddress inetAddress = InetAddress.getByName("228.0.0.8");        MulticastSocket multicastSocket = new MulticastSocket();        multicastSocket.setNetworkInterface(NetworkInterface.getByInetAddress(InetAddress.getByName("192.168.22.1")));        multicastSocket.joinGroup(inetAddress);        while (true) {            String message = "时间戳:" + System.currentTimeMillis();            byte[] bytes = message.getBytes("UTF-8");            DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length,                    inetAddress, 8888);            multicastSocket.send(datagramPacket);        }    }} 

接收端,ip地址是192.168.22.133
import java.net.DatagramPacket;import java.net.InetAddress;import java.net.MulticastSocket;import java.net.NetworkInterface;public class MultiCastNode2 {    public static void main(String[] args) throws Exception {        InetAddress inetAddress = InetAddress.getByName("228.0.0.8");        MulticastSocket multicastSocket = new MulticastSocket(8888);        multicastSocket.setNetworkInterface(NetworkInterface.getByInetAddress(InetAddress.getByName("192.168.22.133")));        multicastSocket.joinGroup(inetAddress);        int count = 0;        while (true) {            if (count == 5) {                multicastSocket.leaveGroup(inetAddress);                return;            }            // 构造DatagramPacket实例,用来接收最大长度为512字节的数据包              byte[] data = new byte[512];            DatagramPacket receivePacket = new DatagramPacket(data, 512);            // 接收报文,此方法在接收到数据报前一直阻塞              multicastSocket.receive(receivePacket);            System.out.println("客户端地址:" + receivePacket.getAddress().getHostAddress());            System.out.println("客户端端口:" + receivePacket.getPort());            System.out.println("接收到的数据长度:" + receivePacket.getLength());            System.out.println("接收到的数据:" + new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8"));            count++;        }    }}  

接收端输出

客户端地址:192.168.22.1
客户端端口:50958
接收到的数据长度:25
接收到的数据:时间戳:1509421450872
客户端地址:192.168.22.1
客户端端口:50958
接收到的数据长度:25
接收到的数据:时间戳:1509421450872
客户端地址:192.168.22.1
客户端端口:50958
接收到的数据长度:25
接收到的数据:时间戳:1509421450872
客户端地址:192.168.22.1
客户端端口:50958
接收到的数据长度:25
接收到的数据:时间戳:1509421450872
客户端地址:192.168.22.1
客户端端口:50958
接收到的数据长度:25
接收到的数据:时间戳:1509421450872


TCP

面向连接,包含连接建立(三次握手),连接释放(四次挥手),拥塞控制,流量控制等,保证了连接的可靠和有序。优点是可靠,缺点是效率低、占用资源多。
TCP把应用程序看成是一连串的无结构的字节流,它有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。

服务端,ip地址是192.168.1.105
import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.net.Socket;import java.util.Arrays;/** * Created by ICE on 2017-05-06 */public class TCPServer {    public static void main(String[] args) throws Exception {        System.out.println("启动TCP服务端");        //构造ServerSocket实例,指定监听的本地端口        try (ServerSocket serverSocket = new ServerSocket(6666)) {            //设置超时时间为10秒              serverSocket.setSoTimeout(10000);            while (true) {                //侦听并接收到此套接字的连接,此方法在连接传入之前一直阻塞。                  Socket socket = serverSocket.accept();                TCPServerHandleThread tcpServerHandleThread = new TCPServerHandleThread(socket);                Thread thread = new Thread(tcpServerHandleThread);                thread.start();            }        }    }}/** * 为每个连接进来的请求单独起一个处理线程 */class TCPServerHandleThread implements Runnable {    private Socket socket;    public TCPServerHandleThread(Socket socket) {        this.socket = socket;    }    @Override    public void run() {        try {            try (BufferedInputStream inputStream = new BufferedInputStream(socket.getInputStream());                 BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream())) {                //每次读取的最大字节数,为了演示特地写小一点                  byte[] bytes = new byte[8];                //每次读取的有效字节数                  int count;                //结果数组                  byte[] result = new byte[0];                while ((count = inputStream.read(bytes)) != -1) {                    //本次读取的有效字节数组                      byte[] temp1 = Arrays.copyOfRange(bytes, 0, count);                    //复制结果数组到新数组,新数组长度为结果数组的长度加上本次读取的有效字节数,用0填充                      byte[] temp2 = Arrays.copyOf(result, result.length + count);                    //将本次读取的有效字节数组放到新数组里                      System.arraycopy(temp1, 0, temp2, result.length, count);                    result = temp2;                }                InetSocketAddress inetSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();                System.out.println("客户端的ip和端口:" + inetSocketAddress.getAddress() + " " + inetSocketAddress.getPort());                InetSocketAddress inetSocketAddressL = (InetSocketAddress) socket.getLocalSocketAddress();                System.out.println("本地绑定的ip和端口:" + inetSocketAddressL.getAddress() + " " + inetSocketAddressL.getPort());                System.out.println("接收到的数据长度:" + result.length);                System.out.println("接收到的数据:" + new String(result, "UTF-8"));                //关闭此socket输入流                  socket.shutdownInput();                outputStream.write("接收完毕".getBytes("UTF-8"));                outputStream.flush();                //关闭此socket输出流                  socket.shutdownOutput();            } finally {                socket.close();            }        } catch (Exception e) {            e.printStackTrace();        }    }}

客户端,ip地址是192.168.1.139
import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.net.InetSocketAddress;import java.net.Socket;/** * * Created by ICE on 2017-05-06 */public class TCPClient {    public static void main(String[] args) throws Exception {        // 构造Socket实例        try (Socket socket = new Socket("192.168.1.105", 6666);             BufferedInputStream inputStream = new BufferedInputStream(socket.getInputStream());             BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream())) {            InetSocketAddress inetSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();            System.out.println("服务端的ip和端口:" + inetSocketAddress.getAddress() + " " + inetSocketAddress.getPort());            InetSocketAddress inetSocketAddressL = (InetSocketAddress) socket.getLocalSocketAddress();            System.out.println("本地绑定的ip和端口:" + inetSocketAddressL.getAddress() + " " + inetSocketAddressL.getPort());            outputStream.write("abcdefg测试数据1234567890".getBytes("UTF-8"));            outputStream.flush();            //关闭此socket输出流            socket.shutdownOutput();            byte[] bytes = new byte[1024];            int count;            if ((count = inputStream.read(bytes)) != -1) {                System.out.println(new String(bytes, 0, count, "UTF-8"));            }            //关闭此socket输入流            socket.shutdownInput();        }    }}

服务端输出
启动TCP服务端客户端的ip和端口:/192.168.1.139 37810本地绑定的ip和端口:/192.168.1.105 6666接收到的数据长度:29接收到的数据:abcdefg测试数据1234567890Exception in thread "main" java.net.SocketTimeoutException: Accept timed outat java.net.DualStackPlainSocketImpl.waitForNewConnection(Native Method)at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:135)at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:404)at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)at java.net.ServerSocket.implAccept(ServerSocket.java:545)at java.net.ServerSocket.accept(ServerSocket.java:513)at TCPServer.main(TCPServer.java:20)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:497)at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

客户端输出
服务端的ip和端口:/192.168.1.105 6666本地绑定的ip和端口:/192.168.1.139 37810接收完毕

客户端还可以采用如下方式设置连接超时时间为10秒
Socket socket = new Socket();socket.connect(new InetSocketAddress("192.168.1.105", 6666), 10000);

0 0
原创粉丝点击