【Java】网络编程

2024-01-07 21:18:58


网络编程是什么?

计算机与计算机之间的程序进行数据传输


一、网络编程三要素

  • IP:
    计算机之间互相通信,作为每台计算机的指定标识。设备的标识

  • 端口:
    应用程序之间的通信,为每个应用程序的指定标识。应用程序的标识

  • 协议 :
    计算机之间通信时需要遵守的规则,对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
    比如UDP/TCP/HTTP/HTTPS 协议

二、IP

计算机设备在网络中的地址,唯一的标识。

IP地址分为两大类:

  • IPv4
    给每个连接在网络上的主机分配一个32bit地址。
    按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。

  • IPv6
    采用128位地址长度,每16个字节一组,分成8组十六进制数
    解决了网络地址资源数量不够的问题

DOS常用命令:

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

特殊IP地址:

  • 127.0.0.1:是回送地址,永远代表本机地址,一般用来测试使用

三、端口号

设备上应用程序的唯一标识。

两个字节表示的整数,它的取值范围是0~65535。
其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。
如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败

一个端口号一个应用程序

四、协议

网络通信协议
协议

UDP协议

  • 用户数据报协议(User Datagram Protocol)
  • 面向无连接通信协议。(也就是不会确定是否连接成功,直接甩数据过去)
  • 速度快,有大小限制一次最多发送64k,数据不安全,易丢失数据。

TCP协议

  • 传输控制协议 (Transmission Control Protocol)
  • 面向连接的通信协议。(传送数据之间,会先判断是否连接成功)
  • 速度慢,没有大小限制,数据安全。

五、InetAddress 工具类

此类表示互联网协议 (IP) 地址。 内部方法可以获取本机IP地址与设备名称等
工具类
设备

六、UDP协议

UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象

DatagramSocket类作为基于UDP协议的Socket

DatagramSocket API构造方法:
API
没有指定端口号,会随机指定一个空闲的端口。

DatagramPacket :此类表示数据报包。

数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
API

1. 发送数据

//测试类
        DatagramSocket ds = new DatagramSocket();//创建发送数据的端口 建立连接
        //数据 地址 端口号 
        String str = "你好!";
        byte[] bytes = str.getBytes();
        InetAddress address = InetAddress.getByName("127.0.0.1");
        int port = 10086;
        //传递
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        //发送
        ds.send(dp);
        ds.close();

2. 接收数据

        DatagramSocket ds = new DatagramSocket(10086);//接收数据的端口

        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

        ds.receive(dp);

        byte[] data = dp.getData();
        InetAddress address = dp.getAddress();
        int port = dp.getPort();
        int len = dp.getLength();

        System.out.println("数据为 " + new String(data, 0, len));
        System.out.println("从这个地址 " + address + " 这个端口 " + port + "发送的数据");

        ds.close();

先运行接收端(开启receive接收等待), 然后再运行发送端!
运行
发送端口是随机指定空闲的端口 ,我们 只需要确定 它发送给了10086 这个端口(并且我们从10086这个端口接收数据)即可。

使用UDP建立简单聊天室

需求:
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收

//接收端
        DatagramSocket ds = new DatagramSocket(15050);

        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

        while (true) {
            ds.receive(dp);

            byte[] data = dp.getData();
            int len = dp.getLength();
            int port = dp.getPort();
            InetAddress address = dp.getAddress(); // 地址包括(主机名+IP)
            String name = address.getHostName(); // 主机名
            String ip = address.getHostAddress(); //IP

            System.out.println("数据: " + new String(data, 0, len));
            System.out.println("这个名字 "+name+" IP "+ip+" 从这个地址 " + address + " 来自这个端口 " + port + " 发送的数据");
        }
        //ds.close();
// 发送端
        DatagramSocket ds = new DatagramSocket();

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("输入:");
            String line = sc.nextLine();

            if("886".equals(line)){
                break;
            }

            byte[] bytes = line.getBytes();
            InetAddress address = InetAddress.getByName("127.0.0.1");
            int port = 15050;

            DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);

            ds.send(dp);
        }
        ds.close();

在这里插入图片描述
开启运行多个实例的模式,打开run多个发送端,就变成聊天室了
效果

UDP的三种通信方式

  1. 单播
    以上的代码 单个IP 就是 单播

  2. 组播
    也就是用同一IP的多个接收端,一个发送端发送数据
    组播

  3. 广播
    一个主机对整个局域网上所有主机上的数据通信
    广播

七、TCP协议

可靠的网络协议,在通信的两端建立Socket,在发送数据之前,会先确定连接成功。

1. 发送数据

客户端:

        /*
        * 创建socket对象
        * 在创建对象的同时会连接服务器,如果连接不上,报错
        * */
        Socket socket = new Socket("127.0.0.1", 15050);
        //从连接通道获取输出流
        OutputStream oss = socket.getOutputStream();

        //写数据
        oss.write("你好!套接字Socket!hello!123".getBytes());

        oss.close();
        socket.close();

2. 接收数据

服务端:

        ServerSocket ss = new ServerSocket(15050);

        //监听客户端连接
        Socket socket = ss.accept();

        //从连接通道获取数据 输入流
        //InputStream iss = socket.getInputStream();//字节流 输入中文会乱码 要转换为 字符流
        //InputStreamReader isr = new InputStreamReader(iss);//使用转换流 将字节转换为字符流
        //BufferedReader br = new BufferedReader(isr);//缓冲流 提高效率

        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//以上代码直接嵌套

        int b;
        while ((b = br.read()) != -1) {
            System.out.print((char) b);
        }

        socket.close();
        ss.close();

三四

三次握手,四次挥手

握手 确定关系
挥手 即分手 say goodbye
三次握手
四次挥手


综合练习

接收到数据 并 返回一个反馈

socket.shutdownOutput(); 关闭输出流,结束的标志

        ServerSocket ss = new ServerSocket(15050);
        Socket socket = ss.accept();

        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        int len;
         // 需要结束标记 要确定已经结束传输数据
        //否则while后面的代码不会执行 ,一直在循环中
        while ((len = br.read()) != -1) {
            System.out.print((char) len);
        }

        OutputStream os = socket.getOutputStream();
        os.write("已收到!".getBytes());

        socket.close();
        ss.close();

客户端发送数据,并且接收返回的数据

        Socket socket = new Socket("127.0.0.1", 15050);
        OutputStream os = socket.getOutputStream();

        os.write("你好啊".getBytes());

        socket.shutdownOutput();//关闭输出流,防止服务端一致卡在读取数据的while循环

        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        int len;
        while ((len = br.read())!=-1){
            System.out.print((char) len);
        }

        socket.close();

上传文件

将文件上传到客户端,提取里面的数据传送给服务端,服务端生成文件输出

客户端:

/**
 * @Description: 文件上传客户端 传送给服务端
 */
public class FileUpdateTCP {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 10000);
        //上传文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("A-SocketNet\\123.jpg"));
        //打开输出流
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());

        //文件读取 与 传输
        byte[] bytes = new byte[1024*1024];
        int len;
        while ((len = bis.read(bytes))!=-1){
            bos.write(bytes,0,len);
        }

        //结束标识,文件已经传送完毕
        socket.shutdownOutput();

        //接收服务器给予的反馈 打印
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String s = br.readLine();
        System.out.println(s);

        socket.close();
    }
}

服务端:

/**
 * @Description: 接收客户端文件 下载 给予反馈
 */
public class FileDownloadTCP {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        Socket socket = ss.accept();

        //打开输入流 接受客户端传送文件
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        //创建文件下载位置
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A-SocketNet\\server\\a.jpg"));
        //文件的接收 与 下载
        byte[] bytes = new byte[1024*1024];
        int len;
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }

        // 接收到数据后 发送 反馈给客户端
        BufferedWriter bosToServer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bosToServer.write("服务端反馈:已收到,上传成功!");
        bosToServer.newLine();
        bosToServer.flush();

        socket.close();
        ss.close();
    }
}

文件重名 UUID

针对上一题,下载的文件名字 重名 会导致覆盖文件。
使用 uuid 生成唯一的随机字符串。(表示通用唯一标识符 (UUID) 的类)
UUID
只要修改服务端的代码即可:

 //使用UUID随机字符串生成文件名字
 String str = UUID.randomUUID().toString().replace("-", "");
 
 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A-SocketNet\\server\\"+str+".jpg"));

效果

上传文件(多线程版)

服务器不关闭,持续接收文件

单线程:一个用户文件没有上传完,下一个用户不能上传 需要等上一个用户结束。
多线程:用户之间互不打扰,一个用户在上传,另外一个也能上传。

思路:使用循环 + 多线程
服务端代码修改:

public class FileDownloadTCP {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);

        while (true){
            Socket socket = ss.accept();
            new Thread(new MyRunnable(socket)).start();
        }
        //ss.close();
    }
}

多线程版:

public class MyRunnable implements Runnable{
    Socket socket;
	//使用构造接收socket
    public MyRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        synchronized (MyRunnable.class){
            try {
                //使用UUID随机字符串生成文件名字
                String str = UUID.randomUUID().toString().replace("-", "");

                //打开输入流 接受客户端传送文件
                BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
                //创建文件下载位置
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A-SocketNet\\server\\"+str+".jpg"));
                //文件的接收 与 下载
                byte[] bytes = new byte[1024*1024];
                int len;
                while ((len = bis.read(bytes)) != -1) {
                    bos.write(bytes, 0, len);
                }

                // 接收到数据后 发送 反馈给客户端
                BufferedWriter bosToServer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                bosToServer.write("服务端反馈:已收到,上传成功!");
                bosToServer.newLine();
                bosToServer.flush();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

上传文件(线程池版)

服务端添加线程池代码:

public class FileDownloadTCP {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        //创建线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3, // 核心线程数量
                16,//线程池总大小
                60,//空闲时间
                TimeUnit.SECONDS,//空闲时间单位
                new ArrayBlockingQueue<>(2),//队列
                Executors.defaultThreadFactory(),//线程工厂
                new ThreadPoolExecutor.AbortPolicy()//阻塞队列
        );


        while (true){
            Socket socket = ss.accept();
            //new Thread(new MyRunnable(socket)).start();
            pool.submit(new MyRunnable(socket));//开启线程池
        }
        //ss.close();
    }
}

总结

文章来源:https://blog.csdn.net/GavinGroves/article/details/135396507
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。