28Java基础之网络编程

网络编程

什么是网络编程?

  • 可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的)。

基本的通信架构

  • 基本的通信架构有两种形式:CS架构(Client客户端/Server服务端)、BS架构(Brower浏览器/Server服务端)。
    image
    image
    无论是CS架构,还是BS架构的软件都必须依赖网络编程!

网络通信的三要素

image

IP地址

  • IP(Internet Protocol):全称"互联网协议地址",是分配给上网设备的唯一标志。
  • IP地址的两种形式:IPv4、IPv6

IPv4
image

IPv6地址

  • IPv6:共128位,号称可以为地球每一粒沙子编号。
  • IPv6分成8段表示,每段每四位编码成一个16进制位表示,数之间用冒号(:)分开。
    image

IP域名
www.baidu.com

公网IP、内网IP

  • 公网IP:是可以连接互联网的IP地址;内网IP:也叫局域网IP,只能组织机构内部使用。
  • 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0——192.168.255.255,专门为组织机构内部使用。

特殊IP地址
127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机。

IP常用的命令

  • ipconfig:查看本机IP地址。
  • ping IP地址:检查网络是否连通。

InetAddress

  • 代表IP地址

InetAddress的常用方法如下:
image
案例

// 目标:InetAddress类代表IP地址对象,用来操作IP地址
public class InetAddressDemo01 {
    public static void main(String[] args) throws Exception {
        //1. 获取本机IP地址对象
        InetAddress ip = InetAddress.getLocalHost();
        System.out.println(ip.getHostAddress());
        System.out.println(ip.getHostName());
        System.out.println(ip.getHostName());

        //2. 指定获取对方主机的IP地址对象
        InetAddress ip1 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip1.getHostName());
        System.out.println(ip1.getHostAddress());

        //3. 判断本机与该主机是否能够联通:ping
        System.out.println(ip1.isReachable(5000));
    }
}

端口号

  • 端口:标记着在计算机设备上运行的应用程序的,被规定为一个16位的二进制,范围0~65535。

分类

  • 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
  • 注册端口:1024~49151,分配给用户进程或某些应用程序。
  • 动态端口:49152~65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配。

注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。

协议

  • 通信协议:网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。

开放式网络互联标准:OSI网络参考模型

  • OSI网络参考模型:全球网络互联标准。
  • TCP/IP网络模型:事实上的国际标准。
    image

传输层的2连个通信协议

  • UDP(User Datagram Protocol):用户数据包协议;TCP(Transmission Control Protocol):传输控制协议。

UDP协议

  • 特点:无连接、不可靠通信、通信效率高。
  • 不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口、目的IP、程序端口和数据(限制在64KB内)等。
  • 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠。
  • 应用场景:语音通话,视频直播等等。

TCP协议

  • 特点:面向连接、可靠通信、通信效率相对不高。
  • TCP的最终目的:要保证在不可靠的信道上实现可靠的传输。
  • TCP主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接。
  • 场景:网页、文件下载、支付

TCP协议:三次握手建立可靠连接

  • 可靠连接:确定通信双方,收发消息都是正常无问题的!(全双工)
    image
  • 传输数据会进行确认,以保证数据传输的可靠性。

TCP协议:四次握手断开连接
image

UDP通信

  • 特点:无连接、不可靠通信
  • 不事先建立连接;发送端每次把药发送的数据(限制在64KB内)、接收端IP等信息封装成一个数据包,发出去就不管了。
  • Java提供了一个Java.net.DatagramSocket类来实现UDP通信。

DatagramSocket:用于创建客户端、服务端
image
DatagramPacket:创建数据包
image
案例

客户端:
//目标:客户端实现(发送端)
public class Client {
    public static void main(String[] args) throws Exception {
        //1.创建发送端对象
        DatagramSocket socket = new DatagramSocket(); // 默认会随机分配一个端口

        //2. 创建一个数据包对象,负责封装要发送的数据
        /*
        * 参数1:要发送的数据,字节数组
        * 参数2:要发送的数据的长度
        * 参数3:接收端的IP地址对象
        * 参数4:接收端的端口号
        * */
        byte[] buffer = "今晚一起啤酒、小龙虾、小烧烤,约吗??".getBytes();
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getLocalHost(), 8888);

        //3. 把这一包数据发送出去
        socket.send(packet);

        //4. 释放资源
        socket.close();
        System.out.println("客户端已发送完毕!");
    }
}

服务端:
public class Server {
    public static void main(String[] args) throws Exception {
        //1. 创建接收端对象
        DatagramSocket socket = new DatagramSocket(8888);

        //2. 创建一个数据包对象
        byte[] buf = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);

        //3. 接收收据
        socket.receive(packet);

        //4. 把数据输出
        int len = packet.getLength();
        String msg = new String(buf, 0, len);
        System.out.println("接收到的消息:" + msg);

        //获取发送端的IP和端口
        InetAddress ip = packet.getAddress();
        System.out.println("对方IP:" + ip.getHostAddress());
        int port = packet.getPort();
        System.out.println("对方端口:" + port);

        //5. 释放资源
        socket.close();
    }
}

TCP通信

  • 特点:面向连接、可靠通信
  • 通信双方事先会采用"三次握手"方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端。
  • Java提供了一个java.net.Socket类来实现TCP通信。
    image

TCP通信之-客户端开发

  • 客户端程序就是通过java.net包下的Socket类来实现的。
    image
    image

案例

//目标:完成TCP通信快速入门-客户端开发:实现1发1收。
public class Client {
    public static void main(String[] args) throws Exception {
        //1. 创建Socket对象,并同时请求与服务端程序的连接。
        Socket skt = new Socket(InetAddress.getLocalHost(), 8888);

        //2. 从Socket通信管道中得到一个字节输出流,用来发送数据给服务端程序。
        OutputStream os = skt.getOutputStream();

        //3. 把低级的字节输出流包装成数据输出流
        DataOutputStream dos = new DataOutputStream(os);

        //4. 开始写数据出去了
        dos.writeUTF("hello world!");

        //5. 关闭资源
        dos.close();
        skt.close();
    }
}

TCP通信-服务端程序的开发

  • 服务端是通过java.net包下的ServerSocket类来实现的。

ServerSocket
image

案例:TCP单发通信

ServerReaderThread线程类:
public class ServerReaderThread extends Thread {
    private Socket skt;

    public ServerReaderThread(Socket skt) {
        this.skt = skt;
    }
    @Override
    public void run() {
       try{
            //3. 从Socket通信管道中得到一个字节输入流,用来读取数据。
            InputStream is = skt.getInputStream();

            //4. 把低级的字节输入流包装成缓冲字符输入流
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                try {
                    //5. 开始读数据了
                    String msg = dis.readUTF();
                    System.out.println("服务端接收到的消息:" + msg);
                    // 我们可以获取客户端的IP地址
                    System.out.println("客户端的IP地址:" + skt.getInetAddress().getHostAddress()+":"+skt.getPort());
                } catch (Exception e) {
                    //6. 关闭资源
                    System.out.println(skt.getRemoteSocketAddress()+ "客户端已退出!");
                    dis.close();
                    skt.close();
                    break;
                }
            }
       }
        catch (Exception e) {
           e.printStackTrace();
        }
    }
}


客户端:
//目标:完成TCP通信快速入门-客户端开发:实现1发1收。
public class Client {
    public static void main(String[] args) throws Exception {
        //1. 创建Socket对象,并同时请求与服务端程序的连接。
        Socket skt = new Socket(InetAddress.getLocalHost(), 8888);

        //2. 从Socket通信管道中得到一个字节输出流,用来发送数据给服务端程序。
        OutputStream os = skt.getOutputStream();

        //3. 把低级的字节输出流包装成数据输出流
        DataOutputStream dos = new DataOutputStream(os);

        //4. 开始写数据出去了
        dos.writeUTF("hello world!");

        //5. 关闭资源
        dos.close();
        skt.close();
    }
}

服务端:
//目标:完成TCP通信快速入门-服务端开发:实现1发1收。
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("----------服务端启动,等待客户端连接------------");
        //1. 创建ServerSocket对象,指定服务端程序的端口号。
        ServerSocket ss = new ServerSocket(8888);

        //2. 调用ServerSocket对象的accept()方法,等待客户端程序的连接。
        Socket skt = ss.accept();

        //3. 从Socket通信管道中得到一个字节输入流,用来读取数据。
        InputStream is = skt.getInputStream();

        //4. 把低级的字节输入流包装成缓冲字符输入流
        DataInputStream dis = new DataInputStream(is);
        //5. 开始读数据了
        String msg = dis.readUTF();
        System.out.println("服务端接收到的消息:" + msg);

        // 我们可以获取客户端的IP地址
        System.out.println("客户端的IP地址:" + skt.getInetAddress().getHostAddress()+":"+skt.getPort());
        //6. 关闭资源
        dis.close();
        skt.close();
    }
}

案例:TCP的多发多收

ServerReaderThread线程类:
public class ServerReaderThread extends Thread {
    private Socket skt;

    public ServerReaderThread(Socket skt) {
        this.skt = skt;
    }
    @Override
    public void run() {
       try{
            //3. 从Socket通信管道中得到一个字节输入流,用来读取数据。
            InputStream is = skt.getInputStream();

            //4. 把低级的字节输入流包装成缓冲字符输入流
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                try {
                    //5. 开始读数据了
                    String msg = dis.readUTF();
                    System.out.println("服务端接收到的消息:" + msg);
                    // 我们可以获取客户端的IP地址
                    System.out.println("客户端的IP地址:" + skt.getInetAddress().getHostAddress()+":"+skt.getPort());
                } catch (Exception e) {
                    //6. 关闭资源
                    System.out.println(skt.getRemoteSocketAddress()+ "客户端已退出!");
                    dis.close();
                    skt.close();
                    break;
                }
            }
       }
        catch (Exception e) {
           e.printStackTrace();
        }
    }
}


客户端:
//目标:完成TCP通信快速入门-客户端开发:实现1发1收。
public class Client {
    public static void main(String[] args) throws Exception {
        //1. 创建Socket对象,并同时请求与服务端程序的连接。
        Socket skt = new Socket(InetAddress.getLocalHost(), 8888);

        //2. 从Socket通信管道中得到一个字节输出流,用来发送数据给服务端程序。
        OutputStream os = skt.getOutputStream();

        //3. 把低级的字节输出流包装成数据输出流
        DataOutputStream dos = new DataOutputStream(os);

        //用户输入消息
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入消息(exit为退出): ");

        while (true) {
            String msg = sc.nextLine();
            if(msg.equals("exit")){
                System.out.println("客户端已退出!");
                //5. 关闭资源
                dos.close();
                skt.close();
                break;
            }
            //4. 开始写数据出去了
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

服务端:
//目标:完成TCP通信快速入门-服务端开发:实现1发1收。
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("----------服务端启动,等待客户端连接------------");
        //1. 创建ServerSocket对象,指定服务端程序的端口号。
        ServerSocket ss = new ServerSocket(8888);

        //2. 调用ServerSocket对象的accept()方法,等待客户端程序的连接。
        Socket skt = ss.accept();

        //3. 从Socket通信管道中得到一个字节输入流,用来读取数据。
        InputStream is = skt.getInputStream();

        //4. 把低级的字节输入流包装成缓冲字符输入流
        DataInputStream dis = new DataInputStream(is);
        while (true) {
            try {
                //5. 开始读数据了
                String msg = dis.readUTF();
                System.out.println("服务端接收到的消息:" + msg);
                // 我们可以获取客户端的IP地址
                System.out.println("客户端的IP地址:" + skt.getInetAddress().getHostAddress()+":"+skt.getPort());
            } catch (Exception e) {
                System.out.println(skt.getRemoteSocketAddress()+ "客户端已退出!");
                //6. 关闭资源
                dis.close();
                skt.close();
                break;
            }
        }
    }
}

image

案例:即时通信-群聊

ServerReaderThread线程类:
public class ServerReaderThread extends Thread {
    private Socket skt;

    public ServerReaderThread(Socket skt) {
        this.skt = skt;
    }
    @Override
    public void run() {
       try{
            //3. 从Socket通信管道中得到一个字节输入流,用来读取数据。
            InputStream is = skt.getInputStream();

            //4. 把低级的字节输入流包装成缓冲字符输入流
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                try {
                    //5. 开始读数据了
                    String msg = dis.readUTF();
                    System.out.println("服务端接收到的消息:" + msg);

                    //把这个消息分发给全部客户端进行接收。
                    sendToAll(msg);

                    // 我们可以获取客户端的IP地址
                    System.out.println("客户端的IP地址:" + skt.getInetAddress().getHostAddress()+":"+skt.getPort());
                } catch (Exception e) {
                    //6. 关闭资源
                    System.out.println(skt.getRemoteSocketAddress()+ "客户端已退出!");
                    Server.clients.remove(skt);
                    dis.close();
                    skt.close();
                    break;
                }
            }
       }
        catch (Exception e) {
           e.printStackTrace();
        }
    }

    private void sendToAll(String msg) throws Exception {
        // 发送给全部在线的socket管道接收。
        for(Socket skt: Server.clients){
            DataOutputStream dos= new DataOutputStream(skt.getOutputStream());
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

服务端:
//目标:完成TCP通信快速入门-服务端开发:实现1发1收。
public class Server {
    public static List<Socket> clients = new ArrayList<>();
    public static void main(String[] args) throws Exception {
        System.out.println("----------服务端启动,等待客户端连接------------");
        //1. 创建ServerSocket对象,指定服务端程序的端口号。
        ServerSocket ss = new ServerSocket(8888);

        while (true) {
            //2. 调用ServerSocket对象的accept()方法,等待客户端程序的连接。
            Socket skt = ss.accept();
            clients.add(skt);
            System.out.println("一个客户端连接了,IP地址为:"+ skt.getRemoteSocketAddress().toString());
            //3. 把这个客户端对应的socket对象,交给一个独立的线程负责处理
            new ServerReaderThread(skt).start();
        }
    }
}

ClientReaderThread线程类:
public class ClientReaderThread extends Thread {
    private Socket skt;
    public ClientReaderThread(Socket skt) {
        this.skt = skt;
    }

    @Override
    public void run() {
        try {
            DataInputStream dis = new DataInputStream(skt.getInputStream());
            while (true) {
                try {
                    String Msg = dis.readUTF();
                    System.out.println(skt.getRemoteSocketAddress() + "说:" + Msg);
                } catch (IOException e) {
                    System.out.println(skt.getRemoteSocketAddress() + "自己下线了!");
                    dis.close();
                    skt.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端:
//目标:完成TCP通信快速入门-客户端开发:实现1发1收。
public class Client {
    public static void main(String[] args) throws Exception {
        //1. 创建Socket对象,并同时请求与服务端程序的连接。
        Socket skt = new Socket(InetAddress.getLocalHost(), 8888);

        //创建一个独立的线程,负责随机从socket中接收服务端发送过来的消息
        new ClientReaderThread(skt).start();

        //2. 从Socket通信管道中得到一个字节输出流,用来发送数据给服务端程序。
        OutputStream os = skt.getOutputStream();

        //3. 把低级的字节输出流包装成数据输出流
        DataOutputStream dos = new DataOutputStream(os);

        //用户输入消息
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入消息(exit为退出): ");

        while (true) {
            String msg = sc.nextLine();
            if(msg.equals("exit")){
                System.out.println("客户端已退出!");
                //5. 关闭资源
                dos.close();
                skt.close();
                break;
            }
            //4. 开始写数据出去了
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

实现简易版BS架构

  • BS架构的基本原理
    image

注意:服务器必须给浏览器响应http协议规定的数据格式,否则浏览器不识别返回的数据。

  • HTTP协议规定:响应给浏览器的数据格式必须满足如下格式
    image

案例

ServerReaderThread线程类:
public class ServerReaderThread extends Thread {
    private Socket skt;

    public ServerReaderThread(Socket skt) {
        this.skt = skt;
    }
    @Override
    public void run() {
        try {
            //立即响应一个网页内容:你好呀baby
//            DataOutputStream dos = new DataOutputStream(skt.getOutputStream());
//            dos.writeUTF("你好呀,baby!");
            PrintStream ps = new PrintStream(skt.getOutputStream());
            String msg = "你好呀,baby!";
            sendHtml(ps, msg);

            ps.close();
            skt.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void sendHtml(PrintStream ps, String msg){
        ps.println("HTTP/1.1 200 OK");
        ps.println("Content-Type: text/html;charset=UTF-8");
        ps.println();//空行,标识响应头结束
        ps.println("<div style='color:red;font-size:120px;text-align:center;'>"+ msg + "</div>");
    }
}

服务端:
//目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通讯。
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("----------服务端启动,等待客户端连接------------");
        //1. 创建ServerSocket对象,指定服务端程序的端口号。
        ServerSocket ss = new ServerSocket(8080);

        while (true) {
            //2. 调用ServerSocket对象的accept()方法,等待客户端程序的连接。
            Socket skt = ss.accept();
            System.out.println("一有人上线了:"+ skt.getRemoteSocketAddress().toString());
            //3. 把这个客户端对应的socket对象,交给一个独立的线程负责处理
            new ServerReaderThread(skt).start();
        }
    }
}

每次请求都开一个新线程,到底好不好?

  • 如果客户端比较少的情况下没问题,但是高并发时,容易宕机!
  • 可以使用线程池来进行优化
    image

案例

服务端:
//目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通讯。
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("----------服务端启动,等待客户端连接------------");
        //1. 创建ServerSocket对象,指定服务端程序的端口号。
        ServerSocket ss = new ServerSocket(8080);

        //创建出一个线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(16 * 2, 16 * 2, 0,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());



        while (true) {
            //2. 调用ServerSocket对象的accept()方法,等待客户端程序的连接。
            Socket skt = ss.accept();
            System.out.println("一有人上线了:"+ skt.getRemoteSocketAddress().toString());
            //3. 把这个客户端对应的socket对象,交给一个独立的线程负责处理
            pool.execute(new ServerReaderRunnable(skt));
        }
    }
}

ServerReaderRunnable线程类:
public class ServerReaderRunnable implements Runnable {
    private Socket skt;

    public ServerReaderRunnable(Socket skt) {
        this.skt = skt;
    }
    @Override
    public void run() {
        try {
            //立即响应一个网页内容:你好呀baby
//            DataOutputStream dos = new DataOutputStream(skt.getOutputStream());
//            dos.writeUTF("你好呀,baby!");
            PrintStream ps = new PrintStream(skt.getOutputStream());
            String msg = "你好呀,baby!";
            sendHtml(ps, msg);

            ps.close();
            skt.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void sendHtml(PrintStream ps, String msg){
        ps.println("HTTP/1.1 200 OK");
        ps.println("Content-Type: text/html;charset=UTF-8");
        ps.println();//空行,标识响应头结束
        ps.println("<div style='color:red;font-size:120px;text-align:center;'>"+ msg + "</div>");
    }
}
posted @ 2025-11-05 15:33  狂风将军  阅读(14)  评论(0)    收藏  举报