• JAVA Class24


    学习内容:

    1.网络通信协议

    (1)TCP/IP协议:

    TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层

    链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。

    网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。

    传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。

    应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。

    客户端与服务端的三次握手:

    第一次握手,客户端向服务器端发出连接请求,等待服务器确认。

    第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。

    第三次握手,客户端再次向服务器端发送确认信息,确认连接。

    优缺点:每次连接都要经过三次握手,速度慢,优势在于可以保证传输数据的完整性。

    (2)UDP协议:

    UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

    优缺点:数据收发不确认,可能会造成数据的不完整,优势在于速度更快。

    (3)IP与端口:

    IP版本分为IPV4 IPV6,现阶段用户量最大的是IPV4,IP地址是网络上的客户端的身份标识,而端口是用来访问客户端的应用程序。形象的来说,IP地址就是小区名称,而端口则是门牌号。

    (4)JAVA的IP地址封装:

    InetAddress类,常用方法:

    public class Test {
    
        public static void main(String[] args) {
            try {
                InetAddress inet = InetAddress.getLocalHost();
                System.out.println(inet.getHostAddress());//本机IP地址
                System.out.println(inet.getHostName());//本机名
                System.out.println(InetAddress.getByName("USER-20180226VT"));//在局域网内,根据主机名获取IP,返回一个InetAddress对象,参数可填主机名或者IP地址
            } catch (Exception e) {
                // TODO: handle exception
            }
            
        }
    
    }

    2.UDP协议通信:

    收发信息的载体:DatagramPacket,常用构造方法有两个:

    new DatagramPacket(byte数组,发送字节长度,InetAddress对象,整型端口号);//用于通过指定IP的指定端口号收发数据

    new DatagramPacket(byte数组,发送字节长度);//不指定IP、端口号

    常用方法:getAddress() 获取ItnetAddress对象 getPort()获取端口号 getLength()获取数据字节长度 getData()获取数据,返回字节数组

    上述方法一般用于接收端,读取发送端的信息

    收发信息的对象:DatagramSocket,常用构造方法:

    new DatagramSocket() 不指定端口号

    new DatagramSocket(整型端口号)  指定端口号

    常用方法: send()发送数据 receive()接收数据 close() 关闭

    public class UDPSend {
    
        public static void main(String[] args) throws IOException {
            Scanner s = new Scanner(System.in);
            DatagramSocket dgs = new DatagramSocket();//没指定端口号,JVM自动分配
            InetAddress inet = InetAddress.getByName("192.168.1.255");
            while(true) {
                System.out.println("请输入内容:");
                String mess = s.next();
                byte[] send =mess.getBytes();
                DatagramPacket dgp = new DatagramPacket(send,send.length,inet,8888);
                //注意这里的8888是信息发往的端口,发送用的端口是JVM分配的
                dgs.send(dgp);
            }
            
            //dgs.close();
        }
    
    }
    
    public class UDPReceive {
    
        public static void main(String[] args) throws IOException {
            DatagramSocket dgs = new DatagramSocket(8888);
            while(true) {
                byte[] receive = new byte[1024*64];
                DatagramPacket dgp = new DatagramPacket(receive,receive.length);
                dgs.receive(dgp);
                String ip = dgp.getAddress().getHostAddress();
                int port = dgp.getPort();
                int length = dgp.getLength();
                byte[]data = dgp.getData();
                String s = new String(data,0,length);
                System.out.printf("发送方地址为:%s 发送方端口号为:%d 数据长度为:%d 数据为:%s 
    ",ip,port,length,s);
            }
            
        }
    
    }

    3.TCP通信

    由于TCP协议的三次握手机制,所以严格区分服务端和客户端,通过输出、输出字节流来进行数据收发

    (1)服务端

    ServerSocket 两种构造方式 无参不指定端口,有参指定端口

    服务端无法通过Socket对象获取输入输出流,要通过accept()方法返回一个Socket对象 :Socket s = ss.accept();//返回的是客户端的Socket对象

    这里要注意,如果用equals来判断客户端的Socket对象与服务端通过accept()对象返回的Socket,会返回false,虽然接收端口相同,但发送端口不同,例如

    public class Test {
    
        public static void main(String[] args) {
            try {
                ServerSocket ss = new ServerSocket(9008);
                Socket s = new Socket("127.0.0.1",9008);
                Socket ns = ss.accept();
                //不是同一个是对象:
                System.out.println("我是客户端"+s);
                //Socket[addr=/127.0.0.1,port=9008,localport=51515]
                System.out.println("我是服务端"+ns);
                //Socket[addr=/127.0.0.1,port=51515,localport=9008]
      }
    }

    (2)客户端

    Socket 构造器均为有参,new Socket(String host, int port) new Socket(InetAddress address, int port),常用的时第一种

    Socket通过getInputStream()、getOutputStream() 获取输入输出流

    聊天小程序:

    收发线程:

    public class SendThread implements Runnable{
             private Socket s;
             public SendThread(Socket s){
                this.s = s;
            }
            public void run(){
                try {
                    OutputStream os = s.getOutputStream();
                    DataOutputStream dos = new DataOutputStream(os);
                    while(true){
                        Scanner sc = new Scanner(System.in);
                        String str = sc.next();
                        dos.writeUTF(str);
                    }
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                 
            }
    }
    
    public class ReceiveThread implements Runnable{
        private Socket s;
        public ReceiveThread(Socket s) {
            this.s = s;
        }
        public void run() {
            try {
                InputStream is = s.getInputStream();
                DataInputStream dis = new DataInputStream(is);
                String ip = s.getInetAddress().getHostAddress();
                int port = s.getLocalPort();
                while (true) {
                    String msg = dis.readUTF();
                    System.out.println("来自:"+ip+"端口:"+port+"的消息:
    "+msg);
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
     
        }
    }

    服务端与客户端分别开一个收发线程:

    public class TCPClient {
    
        public static void main(String[] args) {
            try {
                Socket s = new Socket("127.0.0.1",8888);
                SendThread st = new SendThread(s);
                ReceiveThread rt = new ReceiveThread(s);
                new Thread(st).start();
                new Thread(rt).start();
            } catch (IOException e) {
                e.printStackTrace();
            }
            
        }
    
    }
    
    public class TCPServer {
    
        public static void main(String[] args) {
            try {
                ServerSocket ss = new ServerSocket(8888);
                System.out.println("服务器正在监听8888端口");
                Socket s = ss.accept();//返回的是客户端的Socket对象
                SendThread st = new SendThread(s);
                ReceiveThread rt = new ReceiveThread(s);
                new Thread(st).start();
                new Thread(rt).start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }

     多线程数据上传:

    注意shutdownOutput这个方法,当socket关闭时也会有同样效果,如果客户端在上传数据后还要再接收服务端返回的信息,则必须使用该方法,最后在关闭socket,通知服务端数据传输完毕,客户端输出流关闭,自动flush,服务端读取的字节数最终被确定!此时while循环的-1条件可以成立,同时服务端的输入流也会随之关闭。

    如果不使用该方法,服务端while循环-1条件会一直不满足,因为客户端的输入流一直在等待服务端传来的数据,socket也没法关闭,导致数据没法flush,而服务端一直在等待读取客户端传来的数据,双方等到海枯石烂...结果导致堵塞,所以在socket关闭前必须使用shutdownOutput这个方法,先关闭客户端的输出流,服务端得以接收到数据,然后服务端再把文字信息输出给客户端,客户端的输入流接收到信息并打印,最后socket关闭,整个流程执行完毕!

    注意,如果客户端只是上传文件,然后不再接受服务端传来的消息,则可以直接关闭socket,这样输出流自动关闭!不必调用该方法

    另外:如果不关闭socket,只是输出流手动flush,那么数据可以上传,但是服务端读取有问题,导致图片出现问题!

    同理,shutdownInput也有类似的效果!

    public class UploadThread implements Runnable{
        private Socket s;
         public UploadThread(Socket s){
            this.s = s;
        }
        public void run(){
            File f = new File("d:\test\1366768.jpg");
            try (FileInputStream fis = new FileInputStream(f);
                    OutputStream os=s.getOutputStream();
                    ){
                byte[] upload = new byte[(int)f.length()];
                fis.read(upload);
                os.write(upload);
                s.shutdownOutput();//关闭上传客户端的输出流,通知服务端客户端的数据传输完毕,服务端的输入流也随之关闭
                InputStream is = s.getInputStream();//不关闭,is在等待读取新的输出流的数据
                byte[] mess = new byte[1024];
                int len=is.read(mess);
                System.out.println(new String(mess,0,len));
            } catch (IOException e) {
                    // TODO Auto-generated catch block
            e.printStackTrace();
            } finally {
                try {
                    s.close();//关闭socket,所有流随之关闭
    //如果在这里不关闭socket,服务端不在发消息,客户端也不接受,那么结果是输出流没关闭,没有上传
    //如果输出流不关闭,只用flush,数据会全部上传,但是服务端读取不完整,图片有问题!
    }
    catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
    public class ServerThread implements Runnable{
        private Socket s;
        public ServerThread(Socket s) {
            this.s = s;
        }
        Object o = new Object();
        public void run() {
            synchronized(o) {
                File f = new File("d:\upload");
                  String file =f.getPath()+File.separator+System.currentTimeMillis()+".jpg";
                try(FileOutputStream fos = new FileOutputStream(file);){
                    InputStream is = s.getInputStream();
                    byte[] read = new byte[1024];
                    int a = 0;
                    if(!f.exists()) {
                          f.mkdirs();
                      }
                    while((a=is.read(read))!=-1) {
                        fos.write(read,0,read.length);
                    }
                    System.out.println("结束!");
                    s.getOutputStream().write("上传成功".getBytes());
    //注意这里,因为服务端又新开了一个输出流,导致客户端一定要关闭之前的输出流,终止输出,
    //使得服务端循环的-1条件成立,以此通知服务端文件上传完了,
    //与此同时服务端的输入流也随之关闭,
    //而新增的这个输出流与客户端用来接收这个输出流的输入流会在socket关闭时关闭,
    //如果没有这个输出流,客户端只是上传文件、之后不再接收服务端传来的消息,
    //只需最后关闭socket就好!
    } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
  • 相关阅读:
    Delphi Stream(流)介绍
    springboot 启动慢分析
    内网穿透NPS
    Docker非root用户使用
    springBoot项目中的log导入,ELK
    基于Addrparser根据经纬度分析所在地区位置
    java ArrayList条件排序
    未完成事项记录
    Python数据挖掘——银行分控模型的建立
    unity ugui的拖拽与放置
  • 原文地址:https://www.cnblogs.com/whwjava/p/8961923.html
Copyright © 2020-2023  润新知