Java 学习笔记(十五)

本文详细介绍了Java中InetAddress类的使用,包括获取本机和远程主机信息。接着讲解了Socket在TCP网络通信中的应用,展示了基于TCP的客户端-服务端交互的实例,包括数据的发送和接收。还提到了TCP编程的特点,如可靠性。最后,文章通过实例演示了UDP通信的基本操作,并给出TCP文件下载的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

InetAddress 类

public static void main(String[] args) throws UnknownHostException {
        // 获取本机的 InetAddress 对象
        InetAddress localhost = InetAddress.getLocalHost();
        System.out.println("localhost = " + localhost);

        // 根据主机名获取 InetAddress 对象
        InetAddress host1 = InetAddress.getByName("BLACK"); // 此处为我的主机名
        System.out.println("host1 = " + host1);

        // 根据域名返回 InetAddress 对象
        // 比如百度的
        InetAddress host2 = InetAddress.getByName("www.baidu.com");
        System.out.println("host2 = " + host2);

        // 通过 InetAddress 对象获取对应主机的地址
        String hostAddress = host2.getHostAddress();
        System.out.println("hostAddress = " + hostAddress);

        // 通过 InetAddress 对象获取对应主机的域名/主机名
        String hostName = host2.getHostName();
        System.out.println("hostName = " + hostName);
    }

输出结果:

localhost = Black/192.168.199.99
host1 = Black/192.168.199.99
host2 = www.baidu.com/14.215.177.39
hostAddress = 14.215.177.39
hostName = www.baidu.com

Socket

Socket 套接字应用广泛,是事实上网络通信的标准

  • 通信的两端都要有 Socket,是两台机器通信的端点
  • 网络通信其实就是 Socket 的通信
  • Socket 允许程序把网络连接当成一个流,数据在两个 Socket直接通过 IO 传输
  • 一般主动发起通信的应用程序属于客户端,等待通信请求的为服务端

在这里插入图片描述

编程方式有两种

  • TCP 编程,可靠
  • UDP 编程,不可靠

TCP 网络通信编程

  • 基于客户端-服务端的网络通信
  • 底层使用 TCP/IP 协议
  • 是基于 Socket 的 TCP 编程在这里插入图片描述

例子1:
服务器监听 9999 端口,客户端连接服务端,向服务端发送字节流的消息,然后服务端显示请求内容
服务端:

public class SocketTCPServer01 {
    public static void main(String[] args) throws IOException {
        // 监听本机 9999 端口,等待连接
        // 注意,不能有其它服务也在监听 9999 端口
        // ServerSocket 流可以对应多个 Socket 流,就像多个客户端连接服务器
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端在监听 9999 端口,等待连接");

        // 当没有客户端连接时,程序会阻塞
        // 如果有程序连接,则会返回 Socket 对象,程序继续
        Socket socket = serverSocket.accept();
        System.out.println("服务器端 socket = " + socket);

        // 连接成功后,获取输入流对象
        InputStream inputStream = socket.getInputStream();
        // IO 读取
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            // 根据读取到的长度显示内容
            System.out.println(new String(buf, 0, readLen));
        }
        // 关闭流
        inputStream.close();
        socket.close();
        // 服务器流也关闭
        serverSocket.close();
        System.out.println("服务器退出");
    }
}

客户端:

public class SocketTCPClient01 {
    public static void main(String[] args) throws IOException {
        // 连接服务器
        // 连接 InetAddress.getLocalHost() 主机的 9999 端口
        // 连接成功,返回 Socket 对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端 socket = " + socket);

        // 连接上后,生成 Socket
        // 通过 getOutputStream() 获取输出流对象
        OutputStream outputStream = socket.getOutputStream();
        // 通过输出流写入数据到通道
        outputStream.write("杰瑞狗666".getBytes());
        // 使用完毕后关闭流
        outputStream.close();
        socket.close();
        System.out.println("客户端退出");
    }
}

例子2:
在上个例子的基础上,让服务端收到客户端的字节流信息后,给客户端回复字节流的信息,客户端将收到的内容打印出来。注意:需要在输出流之后加上结束标记,关闭TCP连接

服务端

public class SocketTCPServer02 {
    public static void main(String[] args) throws IOException {
        // 监听本机 9999 端口,等待连接
        // 注意,不能有其它服务也在监听 9999 端口
        // ServerSocket 流可以对应多个 Socket 流,就像多个客户端连接服务器
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端在监听 9999 端口,等待连接");

        // 当没有客户端连接时,程序会阻塞
        // 如果有程序连接,则会返回 Socket 对象,程序继续
        Socket socket = serverSocket.accept();
        System.out.println("服务器端 socket = " + socket);

        // 连接成功后,获取输入流对象
        InputStream inputStream = socket.getInputStream();
        // IO 读取
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            // 根据读取到的长度显示内容
            System.out.println(new String(buf, 0, readLen));
        }
        // 服务端向客户端发送回复
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello杰瑞狗".getBytes());
        // 设置结束标记
        socket.shutdownOutput();
        // 关闭流
        inputStream.close();
        outputStream.close();
        socket.close();
        // 服务器流也关闭
        serverSocket.close();
        System.out.println("服务器退出");
    }
}

客户端

public class SocketTCPClient02 {
    public static void main(String[] args) throws IOException {
        // 连接服务器
        // 连接 InetAddress.getLocalHost() 主机的 9999 端口
        // 连接成功,返回 Socket 对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端 socket = " + socket);

        // 连接上后,生成 Socket
        // 通过 getOutputStream() 获取输出流对象
        OutputStream outputStream = socket.getOutputStream();
        // 通过输出流写入数据到通道
        outputStream.write("杰瑞狗666".getBytes());
        // 设置结束标记,防止程序陷入无限的等待
        socket.shutdownOutput();
        // 获取服务端的回复
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));
        }
        // 使用完毕后关闭流
        outputStream.close();
        inputStream.close();
        socket.close();
        System.out.println("客户端退出");
    }
}

例子3:
在第二个例子的基础上,使用字符流完成消息的发送接收

服务端:

public class SocketTCPServer03 {
    public static void main(String[] args) throws IOException {
        // 监听本机 9999 端口,等待连接
        // 注意,不能有其它服务也在监听 9999 端口
        // ServerSocket 流可以对应多个 Socket 流,就像多个客户端连接服务器
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端在监听 9999 端口,等待连接");

        // 当没有客户端连接时,程序会阻塞
        // 如果有程序连接,则会返回 Socket 对象,程序继续
        Socket socket = serverSocket.accept();
        System.out.println("服务器端 socket = " + socket);

        // 连接成功后,获取输入流对象
        InputStream inputStream = socket.getInputStream();
        // 使用字符流读取
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s = bufferedReader.readLine();
        System.out.println("s = " + s);

        // 服务端向客户端发送回复
        OutputStream outputStream = socket.getOutputStream();
        // 使用字符流回复信息
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write("回复杰瑞狗777 字符流");
        bufferedWriter.newLine();// 插入换行符,表示写入内容结束
        // 需要手动刷新,否则数据不会写入通道
        bufferedWriter.flush();

        // 关闭流
        bufferedWriter.close();
        bufferedReader.close();
        socket.close();
        // 服务器流也关闭
        serverSocket.close();
        System.out.println("服务器退出");
    }
}

客户端:

public class SocketTCPClient03 {
    public static void main(String[] args) throws IOException {
        // 连接服务器
        // 连接 InetAddress.getLocalHost() 主机的 9999 端口
        // 连接成功,返回 Socket 对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端 socket = " + socket);

        // 连接上后,生成 Socket
        // 通过 getOutputStream() 获取输出流对象
        OutputStream outputStream = socket.getOutputStream();
        // 通过输出流写入数据到通道
        // 使用字符流
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write("杰瑞狗666 字符流");
        bufferedWriter.newLine();// 插入换行符,表示写入内容结束
        // 需要手动刷新,否则数据不会写入通道
        bufferedWriter.flush();


        // 获取服务端的回复
        InputStream inputStream = socket.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s = bufferedReader.readLine();
        System.out.println("s = " + s);

        // 使用完毕后关闭流
        bufferedReader.close();
        bufferedWriter.close();
        socket.close();
        System.out.println("客户端退出");
    }
}

例子 4:
使用客户端读取图片发送给服务端,服务端收到后回复字符流的消息

服务端:

public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务端在监听 8888 端口");

        // 等待连接
        Socket accept = serverSocket.accept();

        // 读取客户端发送的数据
        BufferedInputStream bufferedInputStream = new BufferedInputStream(accept.getInputStream());
        // 把数据转化为 byte 数组
        byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);

        // 把 byte 数组写入到文件
        String filePath = "src\\cat.jpg";
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(filePath));
        bufferedOutputStream.write(bytes);

        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bufferedWriter.write("服务器收到图片");
        bufferedWriter.newLine();
        bufferedWriter.flush();

        // 关闭流
        bufferedWriter.close();
        bufferedOutputStream.close();
        bufferedInputStream.close();
        accept.close();
        serverSocket.close();
    }

客户端:

public static void main(String[] args) throws Exception {
        // 客户端连接 8888 服务端
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);

        // 创建读取磁盘文件的输入流
        String filePath = "e:\\cat.jpg";
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
        // 使用工具类方法,把文件输入流转为 byte 数组
        byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);

        // 通过 socket 获取输出流,把 byte 数组发给服务端
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
        // 把 byte 数组发给服务端
        bufferedOutputStream.write(bytes);
        // 结束标记
        socket.shutdownOutput();

        BufferedInputStream bufferedInputStream1 = new BufferedInputStream(socket.getInputStream());
        String s = StreamUtils.streamToString(bufferedInputStream1);
        System.out.println("客户端收到回复:" + s);
        // 关闭流
        bufferedInputStream.close();
        bufferedInputStream1.close();
        bufferedInputStream.close();
        socket.close();
    }

工具类 StreamUtils:

public static byte[] streamToByteArray(InputStream is) throws Exception{
		ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
		byte[] b = new byte[1024];
		int len;
		while((len=is.read(b))!=-1){
			bos.write(b, 0, len);
		}
		byte[] array = bos.toByteArray();
		bos.close();
		return array;
	}
	/**
	 * 功能:将InputStream转换成String
	 * @param is
	 * @return
	 * @throws Exception
	 */

	public static String streamToString(InputStream is) throws Exception{
		BufferedReader reader = new BufferedReader(new InputStreamReader(is));
		StringBuilder builder= new StringBuilder();
		String line;
		while((line=reader.readLine())!=null){ //当读取到 null时,就表示结束
			builder.append(line+"\r\n");
		}
		return builder.toString();

	}

TCP 其它知识点

  • 客户端也是通过端口与服务器进行通信的,端口是 TCP/IP 协议分配的,不是确定的

比如在上面的例子 4 中,在获取客户端连接后加上一句输出语句,可以看到客户端的 socket 的信息,里面包含端口号:
在这里插入图片描述

UDP

  • 类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议的网络程序
  • UDP 数据报通过数据报套接字 DatagramSocket 发送和接受,系统不保证一定能到达目的地,也不保证到达的时间
  • DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端和接收端的 IP 地址以及端口
  • UDP 协议中每个数据报都给出了完整的地址信息,所以无需建立连接

原理示意图:
在这里插入图片描述

例子 1:
发送端向接收到发送消息,接收端收到消息后发送回复后退出,发送端收到回复后退出

发送端:

public class UDPSender01 {
    public static void main(String[] args) throws IOException {
        // 创建一个 DatagramSocket 对象,在端口 7777 准备接收数据
        DatagramSocket datagramSocket = new DatagramSocket(7777);

        // 将需要发送的数据封装到 DatagramPacket 对象中
        byte[] data = "杰瑞狗7777".getBytes();
        // params: 内容字节数组 数组长度 目的IP地址 端口
        DatagramPacket datagramPacket = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.125.29"), 9999);
        // 发送
        datagramSocket.send(datagramPacket);

        // 设置获取回复的数据包
        byte[] replyData = new byte[1024];
        DatagramPacket packet = new DatagramPacket(replyData, replyData.length);
        // 获取回复
        datagramSocket.receive(packet);

        // 拆包
        int len = packet.getLength();
        byte[] data2 = packet.getData();
        String s = new String(data2, 0, len);
        System.out.println("s = " + s);

        datagramSocket.close();
        System.out.println("发送端退出");
    }
}

接收端:

public class UDPReciever01 {
    public static void main(String[] args) throws IOException {
        // 创建一个 DatagramSocket 准备在端口 9999 接收数据
        DatagramSocket socket = new DatagramSocket(9999);
        // 创建一个 DatagramPacket 准备接受数据包
        // UDP 报文最大为 64 k
        byte[] buf = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);

        // 调用接收方法,将网络上传输的 DatagramPacket 对象填充到 packet 对象
        // 如果没有数据包发送到 9999 端口。就会一直阻塞
        System.out.println("接收端等待接收数据");
        socket.receive(packet);

        // 拆包,取出数据并显示
        // 实际收到的数据长度
        int len = packet.getLength();
        // 获取数据
        byte[] data = packet.getData();
        String s = new String(data, 0, len);
        System.out.println("s = " + s);

        // 接收端发送回复
        byte[] replyData = "已收到杰瑞给777".getBytes();
        // 新建一个数据包
        DatagramPacket packet1 = new DatagramPacket(replyData, replyData.length, InetAddress.getByName("192.168.125.29"), 7777);
        socket.send(packet1);

        System.out.println("接收端退出");
        socket.close();


    }
}

TCP 文件下载

客户端发送要获取的文件名,服务端根据文件名发送文件

服务端:

public class TCPFileUploadServerHW03 {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务端在监听 8888 端口");

        // 等待连接
        Socket accept = serverSocket.accept();

        // 读取客户端发送的数据
        InputStream inputStream = accept.getInputStream();
        byte[] buf = new byte[1024];
        int len = 0;
        String downloadName = "";
        while ((len = inputStream.read(buf)) != -1) {
            downloadName += new String(buf, 0, len);
        }
        System.out.println("客户端请求下载的文件名:" + downloadName);

        // 选择返回的文件名
        String resFileName = downloadName.equals("高山流水") ? "src\\高山流水.mp3" : "src\\无名.mp3";

        // 创建输入流,读取磁盘文件
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(resFileName));
        // 转化为字节数组,用于传输
        byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);

        // 获取 socket 对应的输出流
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(accept.getOutputStream());
        // 发送给客户端
        bufferedOutputStream.write(bytes);
        // 设置结束标记
        accept.shutdownOutput();


        // 关闭流
        bufferedOutputStream.close();
        inputStream.close();
        bufferedInputStream.close();
        accept.close();
        serverSocket.close();
    }
}

客户端:

public class TCPFileUploadClientHW03 {
    public static void main(String[] args) throws Exception {
        // 客户端连接 8888 服务端
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);

        // 接受用户输入,指定下载的文件名
        System.out.println("输出要下载的文件名:");
        Scanner scanner = new Scanner(System.in);
        String msg = scanner.next();

        // 获取 socket 的输出流
        OutputStream outputStream = socket.getOutputStream();
        // 把消息发送给服务端
        outputStream.write(msg.getBytes());
        // 设置结束标记
        socket.shutdownOutput();

        // 读取服务端返回的文件
        BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream());
        // 输入流转化为字节数组
        byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);

        // 获取一个输出流,把字节数组写到磁盘
        String desFilePath = "e:\\" + msg + ".mp3";
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(desFilePath));
        bufferedOutputStream.write(bytes);
        System.out.println("客户端成功保存!");

        // 关闭流
        bufferedInputStream.close();
        bufferedOutputStream.close();
        outputStream.close();
        socket.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三更鬼

谢谢老板!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值