socket网络编程-UDP和TCP传输数据

Socket-UDP介绍

  • 英语:User Datagram Protocol,简称UDP
  • 支持一个无连接的传输协议,该协议称为用户数据报协议,也称为用户数据报文协议
  • 是一个简单的面向数据报的传输层协议,正式规范为RFC 768

UDP特点

  • 它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份
  • UDP在IP数据报的头部仅仅加入复用和数据校验(字段)
  • 发送端生产数据,接收端从网络获取数据
  • 结构简单、无校验、速度快、容易丢包、可广播

UDP报文头结构

socket网络编程-UDP和TCP传输数据_第1张图片
image.png
  • 0-15是源端口
  • 16-31是目的端口
  • 上面是0这个32位的结构,下面0-32分别是字节长度和头部和数据校验
  • UDP包最大的长度

1 、首先看图,我们发现一个存储长度信息是16位(如Source Port),一个字节8位,所以16位->2个字节
2、根据UDP协议,UDP的最大长度是2^16-1=65535个字节
3、根据1我们可以知道,UDP自身实际占了的是8个字节,且典型的 IPv4报头是20个字节,所以总共是65535-8-20=35507个字节

UDP的单播、多播、广播

  • 单播:就是点对点进行传输数据,所以肯定需要知道对方的主机地址。如:你写情书给女神(如果你是博爱,请移步到广播,谢谢)
  • 多播:对一组进行传输数据,可以称为组播。如小组委员收小组作业
  • 广播:往这个地址发送的信息,在整个网段中的主机都能收到信息。可以理解为你在学校广播唱歌,不管是谁都会听到
  • 广播分为直接广播地址和受限广播地址,受限广播地址就是255.255.255.255。直接广播的地址是主机位全为1,可以由主机的ip地址和掩码计算的得到

广播地址运算

socket网络编程-UDP和TCP传输数据_第2张图片
image.png

socket网络编程-UDP和TCP传输数据_第3张图片
我的电脑.png
  • IP:192.168.1.111
  • 子网掩码:255.255.255.0
  • 网络地址:192.168.1.0
    1、首先IP192开头所以属于c类地址
    2、因为IP地址=网络地址+主机地址
    所以网络地址=子网掩码&&IP地址

11000000.10101000.000000001.01100101 //ip地址
&&
1111111111.1111111111.111111111111.0000000 //子网掩码
=11000000.10101000.000000001.0000000 =192.168.1.0

第一个可用192.168.1.1,最后一个可用:192.168.1.254
广播是192.168.1.255

广播
标识段host ID 为全1 的IP 地址为广播地址。

点对点实现局域网搜索

接受数据

public class UdpProvider {
    public static void main(String[] args) throws IOException {
        System.out.println("Provider start .......");
        //作为接受者,指定一个端口用于数据接受
        DatagramSocket ds = new DatagramSocket(2000);
        final byte[] buff = new byte[512];
        DatagramPacket receiverPack = new DatagramPacket(buff, buff.length);

        //接受
        ds.receive(receiverPack);
        //打印接受到的信息与发送者的信息
        //发送者的IP地址
        String ip = receiverPack.getAddress().getHostAddress();
        int port = receiverPack.getPort();
        int dataLen = receiverPack.getLength();
        String data = new String(receiverPack.getData(), 0, dataLen);
        System.out.println("UDPProvider receive from ip:" + ip
                + "\nport:" + port + "\ndata:" + data);

        //回送数据
        String responseData = "Receive  data with lenth:" + dataLen;
        byte[] responseDataByte = responseData.getBytes();
        //直接根据发送者构建一份回收信息
        DatagramPacket datagramPacket = new DatagramPacket(responseDataByte
                , responseDataByte.length
                , receiverPack.getAddress()
                , receiverPack.getPort());

          ds.send(datagramPacket);

        System.out.println("UDPProvider finish");
        ds.close();
    }
}

搜索

public class UdpSearch {
    public static void main(String[] args) throws IOException {
        System.out.println("UdpSearch start .......");
        //作为搜索方,让系统自动分配
        DatagramSocket ds = new DatagramSocket();


        //回送数据
        String requestData = "Hello UDP";
        byte[] requestDataByte = requestData.getBytes();
        //直接根据发送者构建一份回收信息
        DatagramPacket datagramPacket = new DatagramPacket(requestDataByte
                , requestDataByte.length);
        datagramPacket.setAddress(InetAddress.getLocalHost());
        datagramPacket.setPort(2000);
        ds.send(datagramPacket);


        //接受
        final byte[] buff = new byte[512];
        DatagramPacket receiverPack = new DatagramPacket(buff, buff.length);

        //接受
        ds.receive(receiverPack);
        //打印接受到的信息与发送者的信息
        //发送者的IP地址
        String ip = receiverPack.getAddress().getHostAddress();
        int port = receiverPack.getPort();
        int dataLen = receiverPack.getLength();
        String data = new String(receiverPack.getData(), 0, dataLen);
        System.out.println("UdpSearch receive from ip:" + ip
                + "\nport:" + port + "\ndata:" + data);


        System.out.println("UdpSearch finish");
        ds.close();
    }
}

TCP介绍

  • 英文:Transmission Control Protocol,简称TCP
  • TCP是传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义
  • 与UDP一样完成第四层传输层所指定的功能和指责

优缺点

  • 可以实现聊天消息传输、推送
  • 可以实现单人语音、视频聊天
  • 限制:无法进行广播、多播

TCP机制

  • 三次连接,四次挥手
    三次连接

玩游戏开黑或者打电话的时候,我们都会:
1、喂、能听到我说话吗
2、我能听到,你能听到我说话吗
3、能能,来来,游戏搞起来

四次挥手

打完游戏,准备关电脑睡觉
1、不玩了,我要睡觉了
2、嗯,我也不玩了,886
3、他关了LOL
4、你关了LOL

  • 具有校验机制,可靠,数据传输稳定

主要API

  • socket(): 创建Socket
  • bind(): 绑定一个Socket到一个本地地址和端口上
  • connect(): 连接到远程套接字
  • accept(): 接受一个进的连接
  • write(): 将数据写入Socket输出流
  • read(): 从Socket输入流读取数据

TCP流程

  • 客户端流程


    socket网络编程-UDP和TCP传输数据_第4张图片
    image.png
  • 服务器流程


    socket网络编程-UDP和TCP传输数据_第5张图片
    image.png

TCP传输数据

客户端

public class Client {
    private static final int PORT = 2000;
    private static final int LOCAL_PORT = 2002;

    public static void main(String[] args) throws IOException {
        //创建socket
        Socket socket = createSocket();
        //初始化socket
        initSocket(socket);
        //连接本地
        socket.connect(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 3000);
        System.out.println("客户端已发起服务器连接,等待服务器连接...........");
        System.out.println("客户端信息" + socket.getLocalAddress() + "\t本地端口号Port:" + socket.getLocalPort());
        System.out.println("服务器信息" + socket.getInetAddress() + "\t服务器端口号Port:" + socket.getPort());


        //发送数据
        todo(socket);
        //释放资源
        socket.close();

        System.out.println("客户端已退出");
    }

    private static void initSocket(Socket socket) throws SocketException {
        //设置读取超时时间
        socket.setSoTimeout(2000);
        //是否复用未完全关闭的socket地址,对于指定bind操作后的套接字有效
        socket.setReuseAddress(true);

        //是否开启Nagel算法
        socket.setTcpNoDelay(true);

        //是否需要在长时间无数据相应时才确认数据,时间大约2小时
        socket.setKeepAlive(true);

        //对于close操作进行处理,默认false,0
        //表示关闭时最长阻塞20毫秒
        socket.setSoLinger(true,20);

        //是否让紧急数据内敛;紧急数据通过socket.sendUrgentData(1)发送
        socket.setOOBInline(true);
        //设置接受缓存器的大小
        socket.setReceiveBufferSize(64*1024*1024);
        socket.setSendBufferSize(64*1024*1024);

        //设置性能参数:短连接时间,延迟,带宽的重要性 只表示三者之间的比重
        socket.setPerformancePreferences(1,1,1);
    }

    private static Socket createSocket() throws IOException {
        Socket socket = new Socket();
        //绑定本地端口
        socket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), LOCAL_PORT));
        return socket;
    }

    private static void todo(Socket client) throws IOException {
        //键盘输入流
        InputStream in = System.in;
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
        //socket输出流,并转换为打印流,用于发送数据
        OutputStream outputStream = client.getOutputStream();
        PrintStream socketPrintStream = new PrintStream(outputStream);

        //得到输入流
        InputStream inputStream = client.getInputStream();

        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        boolean flag = true;

        do {
            //键盘读取一行
            String str = bufferedReader.readLine();
            //发送到服务器
            socketPrintStream.println(str);

            //服务器中读取一行
            String s = reader.readLine();
            if ("bye".equalsIgnoreCase(s)) {
                flag = false;
            } else {
                System.out.println(s);
            }
        } while (flag);
        //资源释放
        reader.close();
        socketPrintStream.close();
    }

}

服务端

public class Server {
    private static final int PORT = 2000;

    public static void main(String[] args) throws IOException {
        ServerSocket server = createServerSocket();
        initServerSocket(server);
        System.out.println("服务器准备就绪...........");
        System.out.println("服务器信息" + server.getInetAddress() + "\t服务器端口号Port:" + server.getLocalPort());

        //等待客户端连接
        for (; ; ) {
            //得到客户端
            Socket client = server.accept();
            ClientHandler clientHandler = new ClientHandler(client);
            clientHandler.start();
        }

    }

    private static void initServerSocket(ServerSocket server) throws SocketException {
        //设置是否复用未完全关闭的地址端口
        server.setReuseAddress(true);
        //设置接受缓存器的大小
        server.setReceiveBufferSize(60 * 1024 * 1024);
        //设置性能参数:短连接时间,延迟,带宽的重要性 只表示三者之间的比重
        server.setPerformancePreferences(1, 1, 1);
    }

    private static ServerSocket createServerSocket() throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        //20代表我们允许等待连接数
        serverSocket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 20);
        return serverSocket;
    }

    private static class ClientHandler extends Thread {
        private Socket socket;
        private boolean isFlag = true;

        public ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            super.run();
            System.out.println("新客户端连接:" + socket.getInetAddress() + "客户端端口号:" + socket.getPort());

            try {
                //得到打印流,用于数据输出,服务器回收数据
                PrintStream socketPrintStream = new PrintStream(socket.getOutputStream());
                //得到输出流,用于接受数据
                BufferedReader socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                do {
                    //客户端得到一条数据
                    String str = socketInput.readLine();
                    if ("bye".equalsIgnoreCase(str)) {
                        isFlag = false;
                        //回送bye
                        socketPrintStream.println("bye");
                    } else {
                        //回送一条数据
                        System.out.println("接收到客户端发来的信息:" + str);
                        socketPrintStream.println("服务器得到内容是:" + str);
                    }

                } while (isFlag);
                socketInput.close();
                socketPrintStream.close();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("连接异常断开了...");
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("客户端已退出:" + socket.getInetAddress() + ",客户端端口号:" + socket.getPort());
        }
    }
}

基础类型数据传输

  • byte、char、short
  • boolean、int、long
  • float、double、string

byte数据传输

  • 修改之前的client中的todo 方法
private static void todo(Socket client) throws IOException {

        //socket输出流,并转换为打印流,用于发送数据
        OutputStream outputStream = client.getOutputStream();

        //得到输入流
        InputStream inputStream = client.getInputStream();
        byte[] buffer = new byte[512];
        boolean flag = true;

        //发送到服务器
        outputStream.write(new byte[]{111});


        //服务器中读取
        int read = inputStream.read(buffer);
        if (read > 0) {
            System.out.println("收到数量:" + read + "\t数据:" + Array.getByte(buffer, 0));
        } else {
            System.out.println("收到数量:" + read);
        }
        //资源释放
        outputStream.close();
        inputStream.close();
    }

修改服务器中的ClientHandler方法

 private static class ClientHandler extends Thread {
        private Socket socket;

        public ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            super.run();
            System.out.println("新客户端连接:" + socket.getInetAddress() + "客户端端口号:" + socket.getPort());

            try {
                OutputStream outputStream = socket.getOutputStream();
                InputStream inputStream = socket.getInputStream();
                 byte[] buffer=new byte[512];
                int read = inputStream.read(buffer);
                if(read>0){
                    System.out.println("收到数量:"+read+"\t数据:"+ Array.getByte(buffer, 0));
                    outputStream.write(buffer,0,read);
                }else{
                    System.out.println("收到数量:"+read);
                    outputStream.write(new byte[]{0});
                }

                outputStream.close();
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("连接异常断开了...");
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("客户端已退出:" + socket.getInetAddress() + ",客户端端口号:" + socket.getPort());
        }
    }

其他基础数据类型
客户端

byte[] buffer = new byte[512];
        ByteBuffer wrap = ByteBuffer.wrap(buffer);
        wrap.put((byte) 126);
        wrap.put((byte) 1234567);

        boolean b=false;
        wrap.put((byte) (b?1:0));

        String str="I am peakmain";
        wrap.put(str.getBytes());
        //发送到服务器
        outputStream.write(buffer,0,wrap.position()+1);

服务器获取数据

 OutputStream outputStream = socket.getOutputStream();
                InputStream inputStream = socket.getInputStream();
                 byte[] buffer=new byte[512];

                int read = inputStream.read(buffer);
                ByteBuffer wrap = ByteBuffer.wrap(buffer, 0, read);
                //string
                int position = wrap.position();
                String s=new String(buffer,position,read-position-1);
                int intValue = wrap.getInt();

                boolean b=wrap.get()==1;

                if(read>0){
                    System.out.println("收到数量:"+read+"\t数据:"+s+"\t"+intValue+"\t"+b);
                    outputStream.write(buffer,0,read);
                }else{
                    System.out.println("收到数量:"+read);
                    outputStream.write(new byte[]{0});
                }

你可能感兴趣的:(socket网络编程-UDP和TCP传输数据)