【网络编程】

网络编程

计算机网络体系结构分层

OSI 7层模型 TCP/IP 4层模型 TCP/IP协议簇
应用层 应用层 SMTP、FTP、DNS、SNMP、NFS、HTTP、TELNET
表示层
会话层
传输层 传输层 TCP、UDP
网络层 网际互联层(网络层) ICMP、IGMP、IP、ARP、RARP
数据链路层 网络接口层(也称为链路层) LAN、MAN、WAN
物理层

TCP/IP网络四层体系结构

  • 应用层
    • 对应于 OSI 参考模型的高层,为用户提供所需要的各种服务。
    • 运行在计算机操作系统中的各种网络软件就工作在应用层,我们如果用Java开发一个具有网络功能的应用, 也工作在应用层。例如浏览器就是一个在应用层可以解析 HTTP 协议的应用程序。
    • 应用层的各种网络应用能够真正实现通信是以下层为基础的。
  • 传输层
    • 对应于 OSI 参考模型的传输层, 为应用层实现提供端到端的通信功能, 保证了数据包的顺序传送及数据的完整性: 传输控制协议( TCP ) 和用户数据报协议( UDP )。
    • TCP 协议提供的是一种可靠的、 通过“三次握手”来连接的数据传输服务; 而UDP协议提供的是不保证可靠(并不是不可靠)、无连接的数据传输服务。
  • 网际互联层
    • 对应于 OSI 参考模型的网络层,主要解决主机到主机的通信问题。它所包含的协议设计数据包在整个网络上的逻辑传输。注重重新赋予主机一个 IP 地址来完成对主机的寻址, 他还负责数据包在多种网络中的路由。
    • 该层有三个主要协议:网际协议( IP )、互联网组管理协议( IGMP )和互联网控制报文协议 ( ICMP ) 。IP 协议是网际互联层最重要的协议, 他提供的是一个可靠、无连接的数据报传递服务。
  • 网络接口层
    • 与 OSI 参考模型的物理层和数据链路层相对应。 它负责监视数据在主机和网络之间的交换。 事实上, TCP/IP 协议本身并未定义该层的协议 ,而由参与互连的各网络使用自己的物理层和数据链路层协议, 然后与 TCP/IP 的网络接入层进行连接。 地址解析协议( ARP ) 工作在该层, 即 OSI 参考模型的数据链路层。

TCP 协议和 UDP 协议的区别

  • 面向连接。 TCP 协议是一种基于连接的协议,发送数据前先建立连接( TCP 有三次握手);UDP 协议是面向无连接的,即发送数据之前不需要建立连接。
  • 数据通信的可靠性。TCP 协议可在两台计算机之间提供可靠的数据流,为需要可靠通信的应用应用程序提供了一个点对点通道,没有数据包丢失;UDP 协议提供两个应用程序之间不可靠的通信,有可能会有数据包丢失。
  • 对系统资源要求不同。 TCP 协议建立连接需要额外的系统开销; 而 UDP 协议不需要建立连接,但是减少了维持连接的系统开销。

掌握开发网络应用的Java包

java.net

基于 TCP 协议的类

  • URL
  • URLConnection
  • Socket
  • ServerSocket

基于 UDP 协议的类

  • DatagramPacket
  • DatagramSocket
  • MulticastSocket

URL,URLConnection类的编程应用

  • 从 URL 读取信息

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.MalformedURLException;
    import java.net.URL;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/23 16:00
     * @Description:
     */
    public class Main {
          
          
    
        public static void main(String[] args) {
          
          
            URL baidu;
            try {
          
          
                baidu = new URL("http://www.baidu.com");
                // 通过 URL 对象获得输入流, 并封装到一个缓存字符输入流中
                BufferedReader in = new BufferedReader(new InputStreamReader(baidu.openStream()));
                String inputLine;
                // 每次从字符流读取一行字符串
                while ((inputLine = in.readLine()) != null) {
          
          
                    // 输入到屏幕
                    System.out.println(inputLine);
                }
                in.close();
            } catch (MalformedURLException e) {
          
          
                // 处理 URL 构造方法可能抛出的异常
                e.printStackTrace();
            } catch (IOException e) {
          
          
                // 处理 openStream()方法可能抛出的异常
                e.printStackTrace();
            }
        }
    }
    
    

    image-20210523172803444

  • 从 URLConnection 读取信息和写信息

    • 用网络图片的url地址,从网络下载一张图片,并保存在本地硬盘的D盘的images2021文件夹,图片重命名为“IMAGE.jsp”(读网络信息,保存成文件,保存文件时使用字符流和字节流的方式)
    import java.io.*;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLConnection;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/23 16:00
     * @Description:
     */
    public class Main {
          
          
    
        public static void main(String[] args) {
          
          
            // 图片网络资源的URL字符串
            String urlStr = "https://gitee.com/fancoding/blog-image/raw/master/Image/20210523212903.jpg";
            // 下载的图片保存到的本地位置
            String filePath = "D:/images2021/";
            // 图片重命名
            String fileName = "523.jpg";
            try {
          
          
                // 创建 URL 对象
                URL url = new URL(urlStr);
                // 获取 URLConnection 对象
                URLConnection conn = url.openConnection();
                // 设置连接超时时间为 3秒
                conn.setConnectTimeout(3 * 1000);
                // 获取网络输入流
                InputStream inputStream = conn.getInputStream();
                // 从网络输入流中读取字节数组(从网络下载的内容)
                byte[] buffer = new byte[1024];
                int len = 0;
                // 字节数组输出流, 用来存储追加从网络输入流中读取的内容
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                // 从输入流读取到buffer数组中
                while((len = inputStream.read(buffer)) != -1){
          
          
                    bos.write(buffer, 0, len);
                }
                // 获取字节数组输出流中的字节数组, 该字节数组存储到本地
                byte[] getData = bos.toByteArray();
                bos.close();
    
                // 创建文件保存位置, 即文件夹对象
                File fileDir = new File(filePath);
                // 如果文件夹不存在
                if (!fileDir.exists()) {
          
          
                    // 创建文件夹
                    fileDir.mkdir();
                }
                // 通过文件夹和文件名创建文件对象
                File file = new File(fileDir, fileName);
                // 创建文件输出流, 用于写文件
                FileOutputStream fos = new FileOutputStream(file);
                // 把之前从网络中下载的字节数组写入文件
                fos.write(getData);
                if (fos != null) {
          
          
                    fos.close();
                }
                if (inputStream != null) {
          
          
                    inputStream.close();
                }
                System.out.println("tips: " + url + " 下载成功") ;
            } catch (MalformedURLException e) {
          
          
                e.printStackTrace();
            } catch (IOException e) {
          
          
                e.printStackTrace();
            }
        }
    }
    
    

    image-20210523214756498

    image-20210523214725961

    • 发送信息给网络, 读网络信息。该例子包含两个程序,一个是客户端,用于连接服务器端,发送数据给服务器端,并接收服务器端的数据;服务端的作用是接收客户端发送的字符串,反序后,发送回客户端,即客户端发送出一个字符串,收到该字符串的反序。
      客户端代码
    import java.io.*;
    import java.net.URL;
    import java.net.URLConnection;
    import java.util.Scanner;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/23 16:00
     * @Description:
     */
    public class Main {
          
          
    
        public static void main(String[] args) throws Exception {
          
          
            Scanner sc = new Scanner(System.in);
            URL url = new URL("http://localhost:9098/2021523_war_exploded/");
            // 创建 URL, 用于连接本地运行的一个Web应用程序
            URLConnection conn = url.openConnection();
            // 允许连接进行输出操作
            conn.setDoOutput(true);
            // 通过 URLConnection 对象获取网络输出流, 用于向 URL 所代表的Web服务器发送数据
            OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
            out.write("string=" + sc.nextLine());
            out.close();
    
            // 通过 URLConnection 对象获取网络输入流, 用于接收Web服务器返回的数据
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String decodeString;
            while ((decodeString = in.readLine()) != null) {
          
          
                System.out.println(decodeString);
            }
            in.close();
            sc.close();
        }
    }
    
    

    服务器端:WEB项目(一个Servlet),

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/23 22:01
     * @Description:
     */
    public class ReverseServlet extends HttpServlet {
          
          
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          
          
            req.setCharacterEncoding("utf-8");
            String reverseStr = "no string";
            // 获取客户端传过来的数据
            String string = req.getParameter("string");
            if (string != null) {
          
          
                // 反转字符串
                reverseStr = new StringBuffer(string).reverse().toString();
            }
            // 输出反转后的字符串
            System.out.println(reverseStr);
            resp.setContentType("text/html");
            resp.setCharacterEncoding("utf-8");
            PrintWriter out = resp.getWriter();
            // 返回到客户端
            out.print(reverseStr);
            out.flush();
            out.close();
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          
          
            doGet(req, resp);
        }
    }
    
    

    Tips:先运行服务端,再运行客户端

image-20210523223455360

image-20210523223531197

Socket 和 ServerSocket 的编程应用

  • 基于TCP的单客户端/服务器端通信(服务器端如何绑定端口,监听连接,客户端如何连服务器,连上后双方如何通过Socket对象获取输入输出流进行通信)

    • 客户端
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/23 22:43
     * @Description: 单客户端,在应用中后运行
     */
    public class Client {
          
          
        public static void main(String[] args) {
          
          
            String hostName = "";// 服务器的IP地址,这里表示本机,相当于127.0.0.1
            int portNumber = 7;
            try{
          
          
                // 客户端通过创建Socket,通过服务器的IP地址和端口号,连接服务器
                Socket echoSocket = new Socket("127.0.0.1", 7);
                // 通过该Socket和服务器端通信,如果想向服务器发送消息,用Socket获取输出流
                // 如果想接收服务器端的消息,用Socket获取输入流
                PrintWriter out = new PrintWriter(echoSocket.getOutputStream(), true);
                BufferedReader in = new BufferedReader(new InputStreamReader(echoSocket.getInputStream()));
                BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
                String userInput;
                // 接收键盘传过来的数据
                while ((userInput = stdIn.readLine()) != null) {
          
          // 按ctrl+z结束键盘输入
                    // 向服务器发送消息
                    out.println(userInput);
                    // 接收服务器发过来的消息,并输出到屏幕
                    System.out.println("echo: " + in.readLine());
                }
            } catch (UnknownHostException e) {
          
          
                e.printStackTrace();
                System.exit(1);
            } catch (IOException e) {
          
          
                e.printStackTrace();
                System.exit(1);
            }
        }
    }
    
    
    • 服务器端
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/23 22:44
     * @Description: 单服务器端,先运行
     */
    public class Server {
          
          
        public static void main(String[] args) {
          
          
            try {
          
          
                //创建服务器端套接字,打开端口,用于监听来自客户端的连接请求
                ServerSocket serverSocket = new ServerSocket(7);
                //监听客户端的连接请求,如果监听到连接,则返回一个新的套接字
                Socket clientSocket = serverSocket.accept();
                //用新的套接字打开输出流
                PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
                //用新套接字打开输入流
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(clientSocket.getInputStream()));
                Scanner scanner=new Scanner(System.in);
                String inputLine;
                while ((inputLine = in.readLine()) != null) {
          
          //从输入流接收客户端传过来的数据
                    System.out.println("来自客户端: " + inputLine);
                    out.println(inputLine);//写入输出流中,即发送回客户端
                }
                scanner.close();
            } catch (IOException e) {
          
          
                e.printStackTrace();
            }
        }
    }
    
    

    运行时,先运行服务器端,再运行客户端,在客户端输入数据,发送给服务器,服务器接收后再返回给客户端,客户端接收后打印到屏幕。
    客户端界面

    image-20210523225236997

    服务器端界面

    image-20210523225241982

  • 基于TCP的多客户端/服务器端通信(实现客户端通过多线程收发消息)

    • 客户端
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/23 22:57
     * @Description: 多客户端,在服务器端程序启动后再启动,可以多次启动,实现多个客户端连接同一个服务器
     */
    public class MultiClient {
          
          
    
        public MultiClient(){
          
          //类的构造方法,主要执行代码在此方法中
            String hostName = "";
            int portNumber = 7;
            try  {
          
          
                //用服务器的IP地址和端口号创建Socket,连接服务器
                Socket echoSocket = new Socket(hostName, portNumber);
                //获取和服务器通信的Socket管道的输入流和输出流
                PrintWriter out = new PrintWriter(echoSocket.getOutputStream(), true);//输出流,用于发送数据给服务器
                BufferedReader in = new BufferedReader(new InputStreamReader(echoSocket.getInputStream()));//输入流,用于接收服务器端传来的数据
                BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
                System.out.println("客户端:"+echoSocket.getLocalAddress().getHostName()+":"
                        +echoSocket.getLocalPort());//输出当前客户端的IP地址和端口号
                //发送信息的线程,用输出流out实现发数据到服务器
                new Thread(new Runnable(){
          
          //实现一个匿名Runnable类,创建匿名对象,并用该匿名对象创建Thread线程对象
                    @Override
                    public void run() {
          
          
                        String userInput;
                        try {
          
          
                            //从键盘接收数据
                            while (( userInput = stdIn.readLine()) != null) {
          
          //按ctrl+z结束键盘输入
                                //输出数据到服务器
                                out.println(userInput);
                            }
                        } catch (IOException e) {
          
          
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                            System.exit(1);
                        }
                    }
                }).start();//线程对象调用start方法启动线程,自动执行Runnable对象中的run方法
                //接收服务器的信息的线程,用in对象实现接收服务器端传过来的数据
                new Thread(new Runnable(){
          
          
                    @Override
                    public void run() {
          
          
                        String receiveStr;
                        try {
          
          
                            //接收服务器发过来的数据
                            while ((receiveStr = in.readLine()) != null) {
          
          
                                //把接收的数据输出到控制台屏幕
                                System.out.println("echo: " + receiveStr);
                            }
                        } catch (IOException e) {
          
          
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                            System.exit(1);
                        }
                    }
                }).start();
    
            } catch (UnknownHostException e) {
          
          
                e.printStackTrace();
            } catch (IOException e) {
          
          
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
          
          
            // TODO Auto-generated method stub
            new MultiClient();
        }
    }
    
    
    • 服务器端
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/23 22:59
     * @Description: 服务器端,先于客户端启动,通过多线程实现接收多个客户端的连接
     */
    public class MultiServer {
          
          
        ServerSocket serverSocket;
        //clients用于存储连接到多个客户端的Socket
        List<Socket> clients = new ArrayList<Socket>();
        public MultiServer() {
          
          //构造方法,主要执行代码在此方法中
            try {
          
          
                //绑定端口,创建服务器端套接字。
                serverSocket = new ServerSocket(7);
                while (true) {
          
          
                    // 监听客户端连接,如果监听到,创建对应的新套接字
                    Socket clientSocket = serverSocket.accept();
                    // 把新套接字存储在列表中
                    clients.add(clientSocket);
                    // 启动新的线程,把当前连接到客户端的新套接字和所有连接到客户端的套接字
                    //通过线程的构造方法的参数传递进线程,
                    //在线程中实现与当前客户端的通信
                    new ServerThread(clientSocket, clients).start();
                }
            } catch (IOException e) {
          
          
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
          
          
            new MultiServer();
        }
    }
    
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    import java.util.List;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/23 22:59
     * @Description: 服务器端使用的线程
     */
    public class ServerThread extends Thread {
          
          
        Socket myClient;// 连接到当前客户端的Socket
        List<Socket> clients;// 连接到所有客户端的Socket
        String clientHostname;// 当前客户端的IP地址
        int clientPort;// 当前客户端的端口号
        // 构造方法,参数是连接当前客户端的Socket和连接到所有客户端的Socket列表
        public ServerThread(Socket myClient, List<Socket> clients) {
          
          
            super();
            this.myClient = myClient;
            this.clients = clients;
        }
        @Override
        public void run() {
          
          // 线程的执行主体
            // 接收来自当前客户端的信息,转发给列表中的所有客户端
            // 获取当前客户端的地址
            InetSocketAddress clientAddress = (InetSocketAddress)
                    myClient.getRemoteSocketAddress();
            // 获取客户端IP地址
            clientHostname = clientAddress.getHostName();
            // 获取客户端的端口号
            clientPort = clientAddress.getPort();
            System.out.println("第" + clients.size() + "个客户端:"
                    + clientHostname + ":" + clientPort + " 已连接");
            try {
          
          
                String inStr;
                // 获取套接字输入流
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(myClient.getInputStream()));
                // 用in对象接收来自当前客户端的信息
                while ((inStr = in.readLine()) != null) {
          
          
                    System.out.println(inStr + "。  来自" + clientHostname + ":" + clientPort + "");
                    // 用循环使用列表中的Socket把接收的信息再发出去(输出)
                    for (Socket client : clients) {
          
          
                        if (client != null) {
          
          
                            PrintWriter out = new PrintWriter(
                                    client.getOutputStream(), true);
                            out.println(inStr + "。  来自" + clientHostname + ":" + clientPort + "");
                        }
                    }
                }
            } catch (IOException e) {
          
          
                // 当客户端关闭连接,服务器端无法发送接收数据发生异常
                System.err.println(clientHostname + ":" + clientPort + "退出了。");
                //e.printStackTrace();
            }
        }
    }
    
    

DatagramPacket, DatagramSocket的编程应用

  • 基于UDP的单客户端/服务器端通信(掌握服务器端如何绑定端口,客户端和服务器端如何通过DatagramSocket对象收发数据报(DatagramPacket对象))

    • 客户端
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.*;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/24 1:07
     * @Description: 基于UDP协议的网络通信客户端,循环实现先发送一个数据报到服务器,再接收一个数据报
     */
    public class UdpClient {
          
          
        public static void main(String[] args) {
          
          
            DatagramSocket client = null;
            String sendStr;
            try {
          
          
                // 创建一个DatagramSocket,使用系统提供的端口
                client = new DatagramSocket();
                BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
                // 通过键盘输入字符串,如果结束循环输入ctrl+z
                while ((sendStr = stdIn.readLine()) != null) {
          
          
                    // 把键盘输入的字符串转成字节数组
                    byte[] buf = sendStr.getBytes();
                    // 把表示服务器端IP地址的字符串转换成 InetAddress 对象
                    InetAddress address = InetAddress.getByName("127.0.0.1");
                    // 使用要发送的字节数组,目标IP地址和端口号,构造数据报
                    DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 4455);
                    // 使用DatagramSocket对象发送数据报
                    client.send(packet);
    
                    byte[] buf1 = new byte[256];
                    // 使用空字节数组构造空数据报
                    packet = new DatagramPacket(buf1, buf1.length);
                    // 使用DatagramSocket对象接收数据到空数据报中
                    client.receive(packet);
                    // 根据数据报的数据和长度构造字符串
                    String received = new String(packet.getData(), 0, packet.getLength());
                    // 显示接收到的字符串和远端的IP地址和端口号,trim方法表示删除字符串前后的空格
                    System.out.println("接收到的信息: " + received.trim() + " 来自" + packet.getAddress().getHostAddress() + ":"
                            + packet.getPort());
                }
            } catch (SocketException e) {
          
          
                e.printStackTrace();
            } catch (UnknownHostException e) {
          
          
                e.printStackTrace();
            } catch (IOException e) {
          
          
                e.printStackTrace();
            } finally {
          
          
                if (client != null)
                    client.close();
            }
        }
    }
    
    
    • 服务器端
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.SocketException;
    import java.util.Scanner;
    
    /**
     * @Author: Travelmate
     * @CreateTime: 2021/5/24 1:08
     * @Description: 基于UDP通信的服务器端,绑定端口4455
     * 循环实现先接收一个数据报,再键盘输入一行字符串发送回去
     */
    public class UdpServer {
          
          
        public static void main(String[] args) {
          
          
            DatagramSocket server = null;
            Scanner scanner=null;
            try {
          
          
                //创建一个DatagramSocket对象
                server = new DatagramSocket(4455);
                scanner=new Scanner(System.in);
                while (true) {
          
          
                    byte[] buf = new byte[256];
                    // 创建用于接收数据的空数据报
                    DatagramPacket p = new DatagramPacket(buf,
                            buf.length);
                    server.receive(p);//用套接字接收数据包
                    //获取发送数据报的客户端的地址
                    InetAddress clientAddress = p.getAddress();
                    //获取发送数据报的客户端的端口号
                    int clientPort = p.getPort();
                    System.out.println(
                            new String(p.getData()).trim()
                                    + " 来自:" + clientAddress.getHostAddress()
                                    + ":" + clientPort);
    
                    byte[] buf2=scanner.nextLine().getBytes();
                    // 创建要发送的数据报(包含发送的数据,客户端地址和端口号),发送回客户端
                    DatagramPacket p2 = new DatagramPacket(buf2, buf2.length,
                            clientAddress, clientPort);
                    server.send(p2);
                }
            } catch (SocketException e) {
          
          
                e.printStackTrace();
            } catch (IOException e) {
          
          
                e.printStackTrace();
            }finally{
          
          
                if(server!=null)server.close();
                if(scanner!=null)scanner.close();
            }
        }
    }
    
    

    客户端界面

    image-20210524011908041

    服务器端界面

    image-20210524011917884

  • 基于 UDP 的多客户端/服务器端通信(客户端通过多线程收发数据)

猜你喜欢

转载自blog.csdn.net/weixin_44695700/article/details/117204685