zoukankan      html  css  js  c++  java
  • day23(上)_网络编程(UDP传输与TCP传输)


    1.网络概述:

    /*
    两台主机之间通过QQ或飞秋进行通信:
    1.找到对方IP
    2.数据发送到对方指定的应用程序上.为了标识这些
      应用程序,所以给这些网络应用程序用数字进行标识
      为了方便称呼这个数字,叫做端口(逻辑端口)
      例如:
        QQ  ------->QQ 4000(数字标识:0~65535,0~1024被系统程序保留)
        FeiQ------->FeiQ 2900
        类比:先找到具体几号楼(IP),在找楼里对应的教室(端口)
     3.定义通信规则,通讯规则称为协议
      例如:两人面对面交谈,约定使用共同语言(英语,汉语....)
       国际组织定义了通用协议:TCP/IP
     
    4.网络模型
    (示意图)
    */

    OSI参考模型(七层)与TCP/IP参考模型(四层)

    参考模型

      2.IP地址:

    /*
     IP地址:
      网络中设备的标识
     不易记忆,可用主机名
     本地回环地址:127.0.0.1,主机名:localhost 
    127.0.0.1的作用:(百度百科)
     一是测试本机的网络配置,
     能PING通127.0.0.1说明本机的网卡和IP协议安装都没有问题;
     另一个作用是某些SERVER/CLIENT的应用程序在运行时需调用服务器上的资源,
     一般要指定SERVER的IP地址,但当该程序要在同一台机器上运行而没有别的SERVER时就可以把SERVER的资源装在本机,
     SERVER的IP地址设为127.0.0.1同样也可以运行。
    */
    package ipdemo;
    import java.net.InetAddress;//此类表示互联网协议 (IP) 地址。 
                               //IP 地址是IP使用的 32 位或 128 位无符号数字,它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的。
    import java.util.Arrays;
    class IPDemo{
        public static void main(String[] args)throws Exception{
            InetAddress ia=InetAddress.getLocalHost();
            System.out.println(ia.toString());//UnknownHostException:指示主机 IP 地址无法确定而抛出的异常。    
                                                                  //返回InetAddress对象封装本地主机名+本地主机IP
          System.out.println(ia.getHostAddress()+"
    "+ia.getHostName()); 
    
            
        }
    }

    IP

    3.UDP,TCP,Socket概述:

    /*
    传输协议:
    一.UDP:(User Datagram Protocol,用户数据报协议)
        1.将数据及源和目的封装成数据包中,不需要建立连接
        2.每个数据报的大小限制在64K内
        3.因无连接,是不可靠协议(对方可能不在,源依然再发,数据丢失)
        4.不需要建立连接(面向无连接),速度快
    现实例子:1.寄包裹
             我去邮局寄一本书-->把书打包,写明寄往的地方(地址),某某人收(端口)
             -->但是寄往的地方(地址)和该人(端口)都不一定存在-->丢掉该包
             2.聊天/视频会议
      UDP优点:传输速度快
         缺点:可能丢包
    二.TCP:(Transmission Control Protocol,传输控制协议)
     1.建立连接,形成传输数据通道
     2.在连接中进行大数据传输
     3.通过三次握手完成连接,是可靠协议
     4.必须建立连接,效率会稍低
     例如:打电话
    
    三.Socket(直译:插座,空,套接字,端)
      通俗理解:
        Socket理解为港口,两个港口(A,B)想要进行通信
        通过船在A装货(数据),运送到B卸货.先有的港口,再有的船,最后进行通信.
        在通信之前必须两端先有Socket,有了Socket才能进行连接,之后有了数据传输的通路
      1.Socket就是为网络服务提供的一种机制
      2.通信的两端都有Socket
      3.网络通信其实就是Socket间的通信
      4.数据在两个Socket间通过IO传输
    
    */

    4.UDP传输:

    /*
    UDP传输:DatagramSocket与DatagramPacket
    DatagramSocket:
       此类表示用来发送和接收数据报包的套接字(Socket)。 
       在 DatagramSocket 上总是启用 UDP 广播发送。
    DatagramPacket:
      此类表示数据报包.
      封装了源地址,目的地址,源端口,目的端口
    */
    /*
    通过UDP传输方式,将一段文字数据发送出去
    1.建立udpscoket服务(类比先找邮局)
    2.提供数据,并将数据封装到数据包中(邮寄的东西,目的地址等等打包)
    3.通过scoket服务发送功能,将数据包发送出去
    4.关闭资源.(会用到底层系统资源,需要释放)
    
    */

    客户端:

    package udp;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    class UDPSend{
      public static void main(String[] args)throws Exception{//在创建服务,发送都可能出现问题
         //1.创建UDP服务,通过DatagramSocket对象
         DatagramSocket ds=new DatagramSocket();
         //2.提供数据,并封成数据包:DatagramPacket(byte[] buf, int length, InetAddress address, int port) 
         byte[] buf="UDP".getBytes();//UDP以GBK编码
         InetAddress ia=InetAddress.getLocalHost();//发送到本地主机
         DatagramPacket dp=new DatagramPacket(buf,buf.length,ia,10000);
         //3.通过Socket将已有数据包发送出去
         ds.send(dp);
         //4.关闭资源
         ds.close();
      }
    }

    服务端:

    class UDPReceive{
      public static void main(String[] args)throws Exception{
        //创建UDP Socket,建立端点
        DatagramSocket ds=new DatagramSocket(10000);
        //2.定义数据包,用于存储数据
        byte[] buf=new byte[1024];
        DatagramPacket dp=new DatagramPacket(buf,buf.length);
        //3.通过服务的receive方法收到数据存入数据包中
        ds.receive(dp);
        //4.通过数据包的方法获取其中的数据
        System.out.println(dp.getAddress().getHostAddress()+" "+
                           new String(buf,0,dp.getLength())+" "+ //将有效字节数以GBK解码成字符串,getLength在这里返回接收到的字节数据长度
                           dp.getPort());//发送方的主机IP地址+发送方发送的数据+发送方的端口
        //5.关闭资源
         ds.close();
      }
    }
    /*
    注意:
    1. 发送应用程序:
     DatagramSocket ds=new DatagramSocket();//系统随机分配一个端口给接收应用程序
     
     接收应用程序:
     DatagramSocket ds=new DatagramSocket(10000);//在这里给接收方应用程序一个标识10000
     //创建数据报套接字并将其绑定到本地主机上的指定端口
     //如果不指定数字标识(端口),它将接收不到,因为发送方指定了10000端口的应用程序去接收处理.
     //即使不指定,接收应用程序和发送应用程序均有数字标识(系统随机分配的)
     
    2.ds.receive(dp);
     从此套接字接收数据报包。
      当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。
      数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 
      此方法在接收到数据报前一直阻塞。
      数据报包对象的 length 字段包含所接收信息的长度。
      如果信息比包的长度长,该信息将被截短。 
    3.
      先运行发送端:发送端直接发送数据出去,可能丢包(接收方没运行),接收方接收到
      先运行接收端:如果没有收到数据,将一直堵塞.
    4.
     new DatagramSocket(10000);
     new DatagramSocket(10000);
     不能把两个socket服务对象绑定到一个端口上
     否则引发Exception in thread "main" java.net.BindException
    */

    需要在两个cmd下运行,因为发送端和接收端都是两个独立的可执行的程序,先运行哪一端均可,但是如果先运行发送端,它直接发送数据,这时候在开启接收端,接收不到已发送的数据,因此先运行接收端,在运行发送端即可:

    UDPReceive

    UDPSend

    示意图:

    UDP传输

    5.将发送数据改为键盘录入:

    /*
    将发送的数据改为键盘录入
    接收方改为循环接收
    */
    package udp;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.IOException;
    import java.net.DatagramSocket;
    import java.net.DatagramPacket;
    import java.net.InetAddress;
    class UDPSend2{
        public static void main(String[] args)throws Exception{
          DatagramSocket ds=new DatagramSocket();
          BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in));
          String data=null;
          while((data=bfr.readLine())!=null){
           if(data.equals("886"))
             break;
            byte[] buf=data.getBytes();
            DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.255"),10001);//主机IP地址:192.168.1.1~192.168.1.254都将收到
            ds.send(dp);
           }
           ds.close();
        }
    }
    class UDPReceive2{
      public static void main(String[] args)throws Exception{
       DatagramSocket ds=new DatagramSocket(10001);
       byte[] buf=new byte[1024];//发送的数据<=64K,也就是64*1024
       while(true){
        DatagramPacket dp=new DatagramPacket(buf,buf.length);
        ds.receive(dp); 
        System.out.println(dp.getAddress().getHostAddress()+" "+dp.getPort()+" "
                           +new String(buf,0,dp.getLength()));
       }
       
      } 
    }

    6.采用多线程将发送端与接收端在一个程序中运行:

    /*
    编写一个聊天程序
    有收数据的部分,和发数据的部分
    这两部分需要并发执行
    ->多线程
    一个线程控制收,一个控制发
    未使用多线程,启动了两个cmd命令窗口,两个进程,
    这两个进程里面至少有一个线程,控制进程执行.
    */
    /*对刚才代码,封装到线程里面*/
    package udp;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.IOException;
    import java.net.DatagramSocket;
    import java.net.DatagramPacket;
    import java.net.InetAddress;
    class Send implements Runnable{
        private DatagramSocket ds;
        public Send(DatagramSocket ds){
         this.ds=ds;
        }
        public void run(){
         try{
          BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in));
          String data=null;
          while((data=bfr.readLine())!=null){
           if(data.equals("886"))
             break;
            byte[] buf=data.getBytes();
            DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.101"),
                                                          10002);
         ds.send(dp);
           }
           ds.close(); 
         }
         catch(Exception e){
          throw new RuntimeException("发送失败");
         }
        }
    }
    class Receive implements Runnable{
        private DatagramSocket ds;
        public Receive(DatagramSocket ds){
         this.ds=ds;
        }
        public void run(){
         try{
               byte[] buf=new byte[1024];//发送的数据<=64K,也就是64*1024
               while(true){
                DatagramPacket dp=new DatagramPacket(buf,buf.length);
                ds.receive(dp); 
                System.out.println(dp.getAddress().getHostAddress()+" "+dp.getPort()+" "
                                   +new String(buf,0,dp.getLength()));
             }
         }
         catch(Exception e){
          throw new RuntimeException("接收失败");
         }
        }
    }
    class UDPTalk{
     public static void main(String[] args)throws Exception{
      new Thread(new Send(new DatagramSocket())).start(); 
      new Thread(new Receive(new DatagramSocket(10002))).start();
     }
    }

    UDPTalk

    7.TCP传输

    /*
    TCP传输:
    Socket和ServerSocket
    1.建立客户端和服务器端
    2.建立连接后,通过Socket中的IO流进行数据的传输
    3.关闭Socket
    4.同样,客户端与服务端是两个独立的应用程序
    Socket:
        此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
    ServerSocket:
        此类实现服务器套接字。服务器套接字等待请求通过网络传入。
        它基于该请求执行某些操作,然后可能向请求者返回结果。 
    */

    示例:

    客户端:

    package tcp;
    import java.net.Socket;
    import java.net.ServerSocket;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    class Client{
     public static void main(String[] args)throws Exception{
      //1.创建客户端的scoket服务,指定目的的主机和端口
       Socket s=new Socket("192.168.1.101",10002);// Socket(String host, int port) 
        /*
         因为TCP是面向连接的,所以在建立socket服务时,
         就要有服务端存在,并连接成功.形成通路后,在该通道进行数据传输
       */
     //2.为了发送数据,应该获取socket流中的输出流.                                            
       OutputStream os=s.getOutputStream();
       os.write("TCP".getBytes());
       s.close();
     }
    }

    服务端:

    /*
    为了避免冲突(服务端把应该发送A的数据发送给了B)
    在连接成功后,服务端通过客户端的流对象进行通信
    1.建立服务端的Socekt服务,ServerSocket().
    2.获取连接过来的客户端对象
      通过ServerSocket的accept方法,没有建立连接,此方法将一直堵塞
    3.客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端
      对象的读取流读取发过来的数据
    4.关闭服务端(一般不关闭,在一个客户端连接结束,还有其它客户端)
    */
    class Server{
         public static void main(String[] args)throws Exception{
           //1.建立服务端的Socket服务
           ServerSocket ss=new ServerSocket(10002);//绑定10002端口
           //2.通过accept方法获取连接过来的客户端对象
           Socket s=ss.accept();
           String ip=s.getInetAddress().getHostAddress();//此套接字连接到的远程 IP 地址 即 客户端主机的IP地址
           int port=s.getPort();//返回此套接字连接到的远程端口 即 客户端的端口
           //3.获取客户端发送过来的数据,那么要使用客户端对象的读取流来读取数据
           InputStream is=s.getInputStream();
           byte[] buf=new byte[1024];
           int bytes=is.read(buf);
           System.out.println(ip+" "+port+" "+new String(buf,0,bytes));
           
           ss.close();//如果仅进行一次通讯,关闭服务端
          s.close();//关闭连接的客户端,不关浪费服务端资源
         }
    }

    /*
    注意:
    必须先运行服务端程序,在运行客户端程序
    这是因为TCP通信之前,必须建立连接成功,如果不先开启服务端,客户端无法连接成功

    */

    示意图:

    TCP传输

    8.服务端反馈信息:

    /*
    客户端给服务端发送数据,服务端收到后,给客户端反馈信息
    
    */
    package tcp;
    import java.net.Socket;
    import java.net.ServerSocket;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.*;
    class Client2{
        public static void main(String[] args)throws Exception{
          Socket s=new Socket("192.168.1.101",10002);
          //客户端向服务端发送数据
          OutputStream os=s.getOutputStream();
          os.write("服务端你好".getBytes());
          
          //客户端接收服务端发送(反馈)数据
          InputStream is=s.getInputStream();
          byte[] buf=new byte[1024];
          int bytes=is.read(buf);//read()为堵塞方法,如果流中没有数据,该方法一致等待,
                                 //也就是说服务端未发送数据,一直堵塞
          System.out.println(new String(buf,0,bytes));
          s.close();
        }
    }
    class Server2{
        public static void main(String[] args)throws Exception{
         ServerSocket ss=new ServerSocket(10002);
         //服务端接收客户端发来的数据
          Socket s=ss.accept();
          String ip=s.getInetAddress().getHostAddress();
          InputStream is=s.getInputStream();
          byte[] buf=new byte[1024];
          int bytes=is.read(buf);
          System.out.println(ip+" "+new String(buf,0,bytes));
        
        //服务端向客户端发送数据
          OutputStream os=s.getOutputStream();
          os.write("我已收到".getBytes());
          
          s.close();//关闭客户端,防止浪费服务端资源
          ss.close();//关闭服务端
         
         }
      }

    Server2

    Client2

    9.TCP练习:

    /*
     将客户端发送的小写字母在服务端转换成大写后反馈给客户端
    客户端:
      源:键盘录入
      目的:Socket输出流
      操作的是文本数据,使用字符流操作方便
    服务端:
       源:Socket读取流
       目的:Socket写入流
      操作的是文本数据,使用字符流操作方便
    */
    package tcp;
    import java.net.Socket;
    import java.net.ServerSocket;
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    class TransClient{
        public static void main(String[] args)throws Exception{
           Socket s=new Socket("192.168.1.101",10003);
           BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in));
           BufferedWriter bfwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//对Socket的输出流使用缓冲技术
         BufferedReader bfrIn=new BufferedReader(new InputStreamReader(s.getInputStream()));//读取服务器的反馈信息
           String line=null;
           while((line=bfr.readLine())!=null){
             if(line.equals("over"))
                 break;
             bfwOut.write(line);
             bfwOut.newLine();
    //如果不写入行终止符,导致服务端的bfrIn.readLine()一直堵塞 bfwOut.flush();//写入缓冲区的数据需要刷新
             System.out.println(bfrIn.readLine());//打印服务端发回的反馈信息
           }
           bfr.close();
           s.close();
        }
    }
    class TransServer{
       public static void main(String[] args)throws Exception{
         ServerSocket ss=new ServerSocket(10003);
         Socket s=ss.accept();
         String ip=s.getInetAddress().getHostAddress();
         System.out.println(ip+" Connect Success");
         BufferedReader bfrIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
         BufferedWriter bfwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
         String line=null;
         while((line=bfrIn.readLine())!=null){
             System.out.println(line);//打印客户端发过来的信息
             bfwOut.write(line.toUpperCase());
             bfwOut.newLine();
    //如果不写入行终止符,导致客户端的bfrIn.readLine()方法一直堵塞
             bfwOut.flush();
          }
         s.close();
         ss.close();
       } 
    }
    /*
    注意:
    1.为什么客户端输入over,服务器端也结束?
       客户端的s.close();在Socket流中加了一个-1(结束标记)
       服务端的bfrIn.readLine()底层调用read方法,读到结束标记
       表示读到流末尾,循环结束
    2.使用PrintWriter/PrintStream简化代码
               BufferedWriter bfwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//对Socket的输出流使用缓冲技术
             bfwOut.write(line);
              bfwOut.newLine();//如果不写入行终止符,导致服务端的bfrIn.readLine()一直堵塞
             bfwOut.flush();//写入缓冲区的数据需要刷新
      
           PrintWriter pw=new PrintWriter(Socket.getOutputStream(),true)//自动刷新
           pw.println(line.toUpperCase());
    */

    10.TCP上传文件:

    package tcp;
    import java.net.Socket;
    import java.net.ServerSocket;
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.PrintWriter;
    import java.io.InputStreamReader;
    import java.io.FileReader;
    import java.io.FileWriter;
    class TCPUpLoadClient{
       public static void main(String[] args)throws Exception{
        Socket s=new Socket("192.168.1.101",10003);  
        BufferedReader buf=new BufferedReader(new FileReader("C:\Users\ZhangHongQ\Desktop\Client.txt"));
        PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
        String line=null;
        while((line=buf.readLine())!=null)
          pw.println(line);
        s.shutdownOutput();
        BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
        System.out.println(bufIn.readLine());
        buf.close();
        s.close();
       }
    }
    class TCPUpLoadServer{
      public static void main(String[] args)throws Exception{
        ServerSocket ss=new ServerSocket(10003);
        Socket s=ss.accept();
        PrintWriter pw=new PrintWriter(new FileWriter("C:\Users\ZhangHongQ\Desktop\Server.txt"),true);
        BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
        String line=null;
        while((line=bufIn.readLine())!=null)
            pw.println(line);
        pw=new PrintWriter(s.getOutputStream(),true);
        pw.println("上传成功");
        s.close();
        ss.close();
      }
    }
    /*
    以上程序运行发现客户端和服务端均等待:
    客户端在bufIn.readLine()等待,
    而服务端由于客户端pw.println(line);并未写入任何结束标记
    导致bufIn.readLine()一直等待读入下一行
    解决:在客户端读完文件后,加入s.shutdownOutput();语句:关闭客户端输出流
         相当于在OutputStream流末尾加入结束标记(-1)
    
    */
  • 相关阅读:
    写代码容易,编程并不容易
    微信小程序教学第四章第二节(含视频):小程序中级实战教程:详情-视图渲染...
    微信小程序教学第四章第二节(含视频):小程序中级实战教程:详情-视图渲染...
    微信小程序教学第四章第二节(含视频):小程序中级实战教程:详情-视图渲染...
    微信小程序教学第四章第二节(含视频):小程序中级实战教程:详情-视图渲染...
    Git经典学习指南
    Git经典学习指南
    Git经典学习指南
    Git经典学习指南
    合伙做生意长久的原则
  • 原文地址:https://www.cnblogs.com/yiqiu2324/p/3181952.html
Copyright © 2011-2022 走看看