Java基础(下)之网络编程

网络编程

什么是网络编程

在网络通信协议下,不同设备上运行的程序,进行数据传输。Java中可以使用Java.net包开发网络应用程序。
image

网络编程三要素

  • IP:设备在网络中的地址,是唯一标识
  • 端口号:程序在设备中的唯一标识
  • 协议:数据在网络中传输的规则,如TCP、UDP、HTTP、FTP、MQTT等

InetAddress 表示IP地址的类

class Main {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress device = InetAddress.getByName("ZJQ");//获取网络上设备的对象,名称可以是机器名称,也可以是IP地址
        
        System.out.println(device);
        System.out.println(device.getHostName());//获取该设备对象的主机名,若设备不存在/获取不到,则返回IP地址
        System.out.println(device.getHostAddress());//获取该设备对象的IP地址
    }
}

UDP通信程序

单播

发送数据

image

public class UDPSender {
    static void main(String[] args) throws IOException, InterruptedException {
        
        //1.创建本机的套接字对象 DatagramSocket
        DatagramSocket ds = new DatagramSocket(5678);//参数为本机发出数据的端口,空参:随机选择一个可用端口;有参:指定端口进行绑定

        //2.打包要发送的数据报 Packet:填写内容及目标套接字(IP:Port)
        String str = "Hello,NetWork!";
        byte[] data = str.getBytes();
        InetAddress targetAdd = InetAddress.getByName("ZJQ");
        int targetPort = 1234;//目标地址的端口,即目标机器监听并接收的端口
        DatagramPacket dp = new DatagramPacket(data,data.length,targetAdd,targetPort);

        //3.发送数据
        for (int i = 0; i < 10; i++) {
            ds.send(dp);
            System.out.println("Send a packet");
            Thread.sleep(500);
        }

        //4.释放资源
        ds.close();
    }
}

接收数据

public class UDPReceiver {
    static void main(String[] args) throws IOException {

        //1.创建本机上接收/监听的套接字对象DatagramSocket
        int listenPort = 1234;//监听本机的端口,数据会发到这个端口来
        DatagramSocket ds = new DatagramSocket(listenPort);

        //2.创建用于接收数据的数据报对象DatagramPacket,
        byte[] data = new byte[1024];
        InetAddress listenAddr = InetAddress.getByName("ZJQ");
        //接收端的数据报仅需分配内存空间,无需指定地址和端口
        DatagramPacket dp = new DatagramPacket(data,data.length);

        //3.接收并解析数据
        while(true){
            ds.receive(dp);//接收数据报,同步方法,收不到就阻塞,直到收到数据
            byte[] rev_data = dp.getData();//数据报中的数据
            int len = dp.getLength();//数据长度
            InetAddress send_addr = dp.getAddress(); //数据报发送方的IP
            int send_port = dp.getPort(); //数据报发送方的端口
            String str = new String(rev_data,0,len);//编码为字符串
            System.out.println("接收到来自" + send_addr + ":" + send_port + "的数据:" + str);
        }
    }
}

组播

组播地址:224.0.0.0 ~ 239.255.255.255,其中224.0.0.0 ~ 224.0.0.255为预留的组播地址。
示例:通过组播方式写一个群发程序
UDPSender.java

package com.example.helloworld;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UDPSender {
    static void main(String[] args) throws IOException, InterruptedException {

        //1.创建本机的套接字对象 MulticastSocket
        MulticastSocket ms = new MulticastSocket(12345);//参数为本机发出数据的端口,空参:随机选择一个可用端口;有参:指定端口进行绑定
        Scanner sc = new Scanner(System.in);

        //2.打包要发送的数据报 Packet:填写内容及目标套接字(IP:Port)
        InetAddress targetAdd = InetAddress.getByName("224.0.0.1");
        int targetPort = 54321;
        while(true){
            String str = sc.nextLine();
            if("886".equals(str)){
                break;
            }
            byte[] data = str.getBytes();
            DatagramPacket dp = new DatagramPacket(data,data.length,targetAdd,targetPort);
            ms.send(dp);
        }
        System.out.println("欢迎下次使用!");
        ms.close();

    }
}

UDPReceiver.java

package com.example.helloworld;

import java.io.IOException;
import java.net.*;

public class UDPReceiver {
    static void main(String[] args) throws IOException {

        //1.创建本机上接收/监听组播数据的套接字对象MulticastSocket
        InetAddress listenAddr = InetAddress.getByName("224.0.0.1");
        int listenPort = 54321;//监听本机的端口,数据会发到这个端口来
        MulticastSocket ms = new MulticastSocket(listenPort);

        //2.将当前本机添加到组播这一组当中
        ms.joinGroup(listenAddr);

        //3.创建用于接收数据的数据报对象DatagramPacket,接收端的数据报仅需分配内存空间,无需指定地址和端口
        byte[] data = new byte[1024];
        DatagramPacket dp = new DatagramPacket(data,data.length);

        //4.接收并解析数据
        while(true){
            ms.receive(dp);//接收数据包,同步方法,收不到就阻塞,直到收到数据
            byte[] rev_data = dp.getData();//数据包中的数据
            int len = dp.getLength();//数据长度
            InetAddress send_addr = dp.getAddress(); //数据包发送方的IP
            int send_port = dp.getPort(); //数据包发送方的端口
            String str = new String(rev_data,0,len);//编码为字符串
            System.out.println("接收到来自" + send_addr + ":" + send_port + "的数据:" + str);
        }
    }
}

广播

广播地址:255.255.255.255
广播不需要修改socket类,将单播代码中发送地址修改即可

TCP通信程序

image

基本流程

image

示例程序

客户端发送,服务器接收

TCPServer.java

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {

    public static void main(String[] args) throws IOException {

        //1.创建ServerSocket对象,等待客户端来连接,因此无需传入IP地址
        ServerSocket ss = new ServerSocket(12345);

        while(true){
            boolean exit = false;

            //2.监听端口,等待客户端连接,若没有则阻塞直到有客户端来连接,此时会获取到客户端的socket对象
            Socket socket = ss.accept();
            System.out.println("有客户端连接:" + socket.getInetAddress().getHostAddress());

            //3.有客户端连接后,需要获取socket对象的输入流,本例中传输的是字符串信息,因此使用缓冲字符流
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            //4.从流中读取数据
            char[] data = new char[1024];
            int len;
            while((len = br.read(data)) != -1){
                String str = new String(data,0,len);
                System.out.println("收到客户端" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort() + "的请求数据:" + str);
                if("886".equals(str)){
                    exit = true;
                    break;
                }
            }
            if(exit) break;
        }
        // 5.断开连接,关闭服务器ServerSocket对象,释放资源
        ss.close();
        System.out.println("Bye");
    }
}

TCPClient.java

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;

public class TCPClient {

    public static void main(String[] args) throws IOException {

        //1.创建Socket对象,此时会与服务器试图建立连接,若连接失败则返回ConnectException
        Socket socket = new Socket("192.168.3.108",12345);

        //2. 连接成功后需要获取socket的输出流,通过输出流发送数据,本例中传输的是字符串信息,因此使用缓冲字符流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

        //3.发送数据
        Scanner sc = new Scanner(System.in);
        while(true){
            String str = sc.nextLine();
            bw.write(str);
            bw.flush();//注意:必须立刻刷新,否则数据不能及时传输
            if("886".equals(str)) break;
        }

        //4.断开连接,释放资源
        socket.close();
        System.out.println("Bye");
    }
}

改进:客户端和服务器均可收发,且客户端断开连接后服务器仍继续监听,等待下一个客户端连接

TCPServer.java

package com.example.helloworld;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TCPServer {
    static boolean connecting = false;
    static boolean running = true;
    public static void main(String[] args) throws IOException {

        //1.创建ServerSocket对象,等待客户端来连接,因此无需传入IP地址
        ServerSocket ss = new ServerSocket(12345);
        Scanner sc = new Scanner(System.in);

        while(running){
            Socket socket = ss.accept();
            System.out.println("有客户端连接:" + socket.getInetAddress().getHostAddress());
            TCPServer.connecting = true;
            //启动监听线程,以接收服务器消息
            Thread th_listen = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        listen(socket);
                    } catch (IOException e) {
                        if (TCPServer.connecting) {
                            System.err.println("网络读取异常: " + e.getMessage());
                        } else {
                            System.out.println("监听线程正常结束。");
                        }
                    }
                }
            });
            th_listen.start();
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

            while (TCPServer.connecting) {
                String str = sc.nextLine();
                bw.write(str);
                bw.newLine();
                bw.flush();
                if ("886".equals(str)) {
                    running = false;
                    break;
                }
            }
            socket.close();
        }


        // 5.断开连接,关闭服务器ServerSocket对象,释放资源
        ss.close();
        System.out.println("Bye");
    }

    public static void listen(Socket socket) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String str;

        while (TCPClient.running && (str = br.readLine()) != null) { //对 TCP 连接 EOF(服务端正常关闭 FIN)的严谨判断
            System.out.println("收到客户端" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort() + "消息:" + str);
            if ("886".equals(str)) {
                TCPServer.connecting = false;
            }
        }
        System.out.println("监听线程已退出");
    }
}

TCPClient.java

package com.example.helloworld;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TCPClient {
    static volatile boolean  running = true;
    public static void main(String[] args) throws IOException {

        //1.创建Socket对象,此时会与服务器试图建立连接,若连接失败则返回ConnectException
        Socket socket = new Socket("192.168.3.107",12345);

        //2. 连接成功后需要获取socket的输出流,通过输出流发送数据,本例中传输的是字符串信息,因此使用缓冲字符流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

        //启动监听线程,以接收服务器消息
        Thread th_listen = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    listen(socket);
                } catch (IOException e) {
                    if (running) {
                        System.err.println("网络读取异常: " + e.getMessage());
                    } else {
                        System.out.println("监听线程正常结束。");
                    }
                }
            }
        });
        th_listen.start();
        //3.发送数据
        Scanner sc = new Scanner(System.in);
        while(TCPClient.running){
            String str = sc.nextLine();
            bw.write(str);
            bw.newLine();
            bw.flush();//注意:必须立刻刷新,否则数据不能及时传输
            if("886".equals(str)) TCPClient.running = false;
        }

        //4.断开连接,释放资源
        socket.close();
        System.out.println("Bye");
    }

    public static void listen(Socket socket) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String str;

        while (TCPClient.running && (str = br.readLine()) != null) {
            System.out.println("收到服务器消息:" + str);
        }
        br.close();
    }

}

客户端上传文件至服务器

客户端:将本地文件上传到服务器,接收服务器的反馈信息
服务器:接收客户端上传的文件并保存,接收完成后给出反馈信息,且能够支持多个客户端同时上传文件(使用多线程或线程池)
TCPClient.java

package com.example.helloworld;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TCPClient {
    static volatile boolean  running = true;
    public static void main(String[] args) throws IOException {

        //1.创建Socket对象,此时会与服务器试图建立连接,若连接失败则返回ConnectException
        Socket socket = new Socket("192.168.3.107",12345);
        System.out.println("Conneced to " + socket.getInetAddress().getHostAddress() + ":" + socket.getPort());
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());

        File file = new File("data\\a.txt");
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));

        byte[] buf = new byte[1024];
        int len;
        while((len = bis.read(buf)) != -1){
            bos.write(buf,0,len);
            bos.flush();
        }
        System.out.println("File uploaded!");
        bis.close();
        socket.shutdownOutput();//向服务器写出结束标记
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String str = br.readLine();
        System.out.println("服务器消息:" + str);

        socket.close();
    }



}

TCPServer.java

package com.example.helloworld;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(12345);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                9,
                9,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        while(true){
            Socket socket = ss.accept();
            System.out.println("Request from " + socket.getInetAddress().getHostAddress());
            MyThread thread = new MyThread(socket);
            pool.submit(thread);
        }

    }
}

class MyThread implements Runnable{
    public MyThread(Socket socket) {
        this.socket = socket;
    }

    private Socket socket;
    @Override
    public void run() {
        try {
            String filename = UUID.randomUUID().toString().replace("-","");
            File file = new File("data",filename + ".jar");
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
            BufferedInputStream net_bis = new BufferedInputStream(socket.getInputStream());
            BufferedOutputStream net_bos = new BufferedOutputStream(socket.getOutputStream());

            byte[] buf = new byte[1024];
            int len;
            while((len = net_bis.read(buf)) != -1){
                bos.write(buf,0,len);
                bos.flush();
            }
            String str = "Finished!";
            net_bos.write(str.getBytes());
            net_bos.flush();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                socket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

建立连接过程:三次握手

image
思考:为什么需要第三次握手?

断开连接过程:四次挥手

image
思考:网上需要第四次挥手?

posted @ 2026-03-04 18:18  安河桥北i  阅读(5)  评论(0)    收藏  举报