Java网络编程

Java网络编程

1、InetAddress

JDK 中提供了一个 InetAdderss 类,该类用于封装一个 IP 地址,并提供了一系列与 IP 地址相关的方法,列

举了 InetAddress 类的一些常用方法。

  • InetAddress getByName(String host)

参数 host 表示指定的主机,该方法用于在给定主机名的情况下确定主机的 IP 地址。

  • InetAddress getLocalHost()

创建一个表示本地主机的 InetAddress 对象。

  • String getHostName()

得到 IP 地址的主机名,如果是本机则是计算机名,不是本机则是主机名,如果没有域名则是IP地址。

  • boolean isReachable(int timeout)

判断指定的时间内地址是否可以到达。

  • String getHostAddress()

得到字符串格式的原始 IP 地址。

列举了 InetAddress 的五个常用方法。其中,前两个方法用于获得该类的实例对象,第一个方法用于获得表示指

定主机的 InetAddress 对象,第二个方法用于获得表示本地的 InetAddress 对象。通过 InetAddress 对象便

可获取指定主机名,IP 地址等。

package com.example;

import java.net.InetAddress;

/**
 * @author test
 */
public class Example01 {
    public static void main(String[] args) throws Exception {
        InetAddress localAddress=InetAddress.getLocalHost();
        InetAddress remoteAddress=InetAddress.getByName("www.baidu.com");
        System.out.println("本机的IP地址;"+localAddress.getHostAddress());
        System.out.println("百度的IP地址:"+remoteAddress.getHostAddress());
        System.out.println("3秒是否可达:"+remoteAddress.isReachable(3000));
        System.out.println("百度的主机名为:"+remoteAddress.getHostName());
        System.out.println("百度的原始IP地址为:"+remoteAddress.getHostAddress());
    }
}
# 输出
本机的IP地址;192.168.149.1
百度的IP地址:39.156.66.18
3秒是否可达:true
百度的主机名为:www.baidu.com
百度的原始IP地址为:39.156.66.14

2、UDP通信-DatagramPacket

UDP 是一种面向无连接的协议,因此,在通信时发送端和接收端不用建立连接。UDP 通信的过程就像是货运公司

在两个码头间发送货物一样。在码头发送和接收货物时都需要使用集装箱来装载货物,UDP 通信也是一样,发送

和接收的数据也需要使用集装箱进行打包。为此 JDK 中提供了一个 DatagramPacket 类,该类的实例对象就相当

于一个集装箱,用于封装 UDP 通信中发送或者接收的数据。

想要创建一个 DatagramPacket 对象,首先需要了解一下它的构造方法。在创建发送端和接收端的

DatagramPacket 对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组来存放接收到

的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端 IP 地址和端口号。接下

来根据 API 文档的内容,对 DatagramPacket 的构造方法进行详细地讲解。

  • DatagramPacket(byte[] buf,int length)

使用该构造方法在创建 DatagramPacket 对象时,指定了封装数据的字节数组和数据的大小,没有指定IP地址和

端口号。很明显,这样的对象只能用于接收端,不能用于发送端。因为发送端一定要明确指出数据的目的地(IP地

址和端口号),而接收端不需要明确知道数据的来源,只需要接收到数据即可。

  • DatagramPacket(byte[]buf,int length,InetAddress addr,int port)

使用该构造方法在创建 DatagramPacket 对象时,不仅指定了封装数据的字节数组和数据的大小,还指定了数据

包的目标IP地址(addr)和端口号(port)。该对象通常用于发送端,因为在发送数据时必须指定接收端的IP地址和端

口号,就好像发送货物的集装箱上面必须标明接收人的地址一样。

  • DatagramPacket(byte[]buf,int offset,int length)

该构造方法与第一个构造方法类似,同样用于接收端,只不过在第一个构造方法的基础上,增加了一个offset参

数,该参数用于指定接收到的数据在放人buf缓冲数组时是从offset处开始的。

  • DatagramPacket(byte[]buf,int offset,int length,InetAddress addr,int port)

该构造方法与第二个构造方法类似,同样用于发送端,只不过在第二个构造方法的基础上,增加了一个offset参

数,该参数用于指定一个数组中发送数据的偏移量为offset,即从offset位置开始发送数据。

一些其它的方法:

  • InetAddress getAddress()

该方法用于返回发送端或者接收端的IP地址,如果是发送端的DatagramPacket对象,就返回接收端的IP地址,反

之,就返回发送端的IP地址。

  • int getPort()

该方法用于返回发送端或者接收端的端口号,如果是发送端的DatagramPacket对象,就返回接收端的端口号,反

之,就返回发送端的端口号。

  • byte[] getData()

该方法用于返回将要接收或者将要发送的数据,如果是发送端的 DatagramPacket对象,就返回将要发送的数据,

反之,就返回接收到的数据。

  • int getLength()

该方法用于返回接收或者将要发送数据的长度,如果是发送端的 DatagramPacket对象,就返回将要发送的数据长

度,反之,就返回接收到数据的长度。

列举了 DatagramPacket 类的四个常用方法及其功能,通过这四个方法,可以得到发送或者接收到的

DatagramPacket 数据包中的信息。

3、UDP通信-DatagramSocket

DatagramPacket 数据包的作用就如同是集装箱,可以将发送端或者接收端的数据封装起来。然而,运输货物只

有集装箱是不够的,还需要有码头。在程序中需要实现通信只有 DatagramPacket 数据包也同样不行,为此 JDK

提供了一个 DatagramSocket 类。DatagramSocket 类的作用就类似于码头,使用这个类的实例对象就可以发送

和接收 DatagramPacket 数据包。

在这里插入图片描述

在创建发送端和接收端的 DatagramSocket 对象时,使用的构造方法也有所不同,下面对 DatagramSocket 类中

常用的构造方法进行讲解。

  • DatagramSocket()

该构造方法用于创建发送端的 DatagramSocket 对象,在创建 DatagramSocket 对象时,并没有指定端口号,此

时,系统会分配一个没有被其他网络程序所使用的端口号。

  • DatagramSocket(int port)

该构造方法既可用于创建接收端的 DatagramSocket 对象,又可以创建发送端的DatagramSocket对象,在创建接

收端的DatagramSocket对象时,必须要指定一个端口号,这样就可以监听指定的端口。

  • DatagramSocket(int port,InetAddress addr)

使用该构造方法在创建 DatagramSocket 时,不仅指定了端口号,还指定了相关的IP地址,这种情况适用于计算

机上有多块网卡的情况,可以明确规定数据通过哪块网卡向外发送和接收哪块网卡的数据。由于计算机中针对不同

的网卡会分配不同的IP,因此在创建DatagramSocket对象时需要通过指定IP地址来确定使用哪块网卡进行通信。

对DatagramSocket类中的常用方法进行详细地讲解:

  • void receive(DatagramPacket p)

该方法用于将接收到的数据填充到DatagramPacket数据包中,在接收到数据之前会一直处于阻塞状态,只有当接

收到数据包时,该方法才会返回。

  • void send(DatagramPacket p)

该方法用于发送 DatagramPacket 数据包,发送的数据包中包含将要发送的数据、数据的长度、远程机的IP地址

和端口号。

  • void close()

关闭当前的Socket,通知驱动程序释放为这个Socket保留的资源。

4、UDP网络程序

讲解了 DatagramPacketDatagramSocket 的作用,接下来通过一个案例来学习一下它们在程序中的具体用

法。要实现 UDP 通信需要创建一个发送端程序和一个接收端程序,很明显,在通信时只有接收端程序先运行,才

能避免因发送端发送的数据无法接收,而造成数据丢失。

接收端程序:

package com.example;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * @author test
 */
public class Example02 {
    public static void main(String[] args) throws Exception {
        // 创建一个长度为1024的字节数组,用于接收数据
        byte[] buf = new byte[1024];
        // 定义一个DatagramSocket对象,监听的端口号为8954
        DatagramSocket ds = new DatagramSocket(8954);
        // 定义一个DatagramPacket对象,用于接收数据
        DatagramPacket dp = new DatagramPacket(buf, 1024);
        System.out.println("等待接收数据...");
        // 等待接收数据,如果没有数据则会阻塞
        ds.receive(dp);
        // 调用DatagramPacket的方法获得接收到的信息,包括数据的内容、长度、IP地址和端口号
        String str = new String(dp.getData(), 0, dp.getLength()) + "from" +
                dp.getAddress().getHostAddress() + ":" + dp.getPort();
        // 打印接收到的信息
        System.out.println(str);
        // 释放资源
        ds.close();
    }
}
package com.example;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * @author test
 */
public class Example03 {
    public static void main(String[] args) throws Exception{
        // 创建一个DatagramSocket对象
        DatagramSocket ds=new DatagramSocket(3000);
        // 要发送的数据
        String str="hello world";
        DatagramPacket dp=new DatagramPacket(str.getBytes(),str.length(),
                InetAddress.getByName("localhost"),8954);
        System.out.println("发送信息");
        //发送数据
        ds.send(dp);
        //释放资源
        ds.close();
    }
}
发送信息
等待接收数据...
hello worldfrom127.0.0.1:3000

需要注意的时,在创建发送端的 DatagramSocket 对象时,可以不指定端口号,而指定端口号的目的就是,为了

每次运行时接收端的 getPort() 方法返回值都是一致的,否则发送端的端口号由系统自动分配,接收端的getPort()

方法的返回值每次都不同。

5、TCP通信

TCP 通信同 UDP 通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建 Socket 对象。区别在于,

UDP 中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。而 TCP 通信是严格区

分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户

端,并且服务器端程序需要事先启动,等待客户端的连接。

在 JDK 中提供了两个类用于实现 TCP 程序,一个是 ServerSocket 类,用于表示服务器端,一个是 Socket 类,

用于表示客户端。通信时,首先创建代表服务器端的 ServerSocket 对象,该对象相当于开启一个服务,并等待

客户端的连接,然后创建代表客户端的 Socket 对象向服务器端发出连接请求,服务器端响应请求,两者建立连

接开始通信。

5.1 TCP通信-ServerSocket

通过前面的学习知道,在开发 TCP 程序时,首先需要创建服务器端程序。JDK 的 java.net 包中提供了一个

ServerSocket 类,该类的实例对象可以实现一个服务器端的程序。通过查阅 API 文档可知,ServerSocket

提供了多种构造方法,接下来就对 ServerSocket 的构造方法进行逐一地讲解。

  • ServerSocket()

使用该构造方法在创建 ServerSocket 对象时并没有绑定端口号,这样的对象创建的服务器端没有监听任何端口,

不能直接使用,还需要继续调用 bind(Socket Address endpoint) 方法将其绑定到指定的端口号上,才可以正

常使用。

  • ServerSocket(int port)

使用该构造方法在创建 ServerSocket对象时,就可以将其绑定到一个指定的端口号上(参数port就是端口号)。端口

号可以指定为0,此时系统就会分配一个还没有被其他网络程序所使用的端口号。由于客户端需要根据指定的端口

号来访问服务器端程序,因此端口号随机分配的情况并不常用,通常都会让服务器端程序监听一个指定的端口号。

  • ServerSocket(int port,int backlog)

该构造方法就是在第二个构造方法的基础上,增加了一个 backlog 参数。该参数用于指定在服务器忙时,可以与

之保持连接请求的等待客户数量,如果没有指定这个参数,默认为50。

  • ServerSocket(int port,int backlog,Inet Address bindAddr)

该构造方法就是在第三个构造方法的基础上,还指定了相关的P地址,这种情况适用于计算机上有多块网卡和多个

IP的情况,我们可以明确规定ServerSocket在哪块网卡或IP地址上等待客户的连接请求。显然,对于一般只有一块

网卡的情况,就不用专门的指定了。

在以上介绍的构造方法中,第二个构造方法是最常使用的。了解了如何通过ServerSocket的构造方法创建对象,

接下来学习一下ServerSocket 的常用方法:

  • Socket accept()

该方法用于等待客户端的连接,在客户端连接之前一直处于阻塞状态,如果有客户端连接就会返回一个与之对应的

Socket对象。

  • InetAddress getInetAddress()

该方法用于返回一个 InetAddress 对象,该对象中封装了ServerSocket 绑定的 IP地址

  • boolean isClosed()

该方法用于判断 ServerSocket 对象是否为关闭状态,如果是关闭状态则返回true,反之则返回false。

  • void bind(SocketAddress endpoint)

该方法用于将 ServerSocket 对象绑定到指定的 IP地址和端口号,其中参数 endpoint 封装了IP地址和端口号。

ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的

accept()方法,接收来自客户端的请求。当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出

连接请求,accept()方法才会返回一个Socket对象用于和客户端实现通信,程序才能继续向下执行。

5.2 TCP通信-Socket

上一小节中讲解了 ServerSocket 对象可以实现服务端程序,但只实现服务器端程序还不能完成通信,此时还需要

一个客户端程序与之交互。为此 JDK 提供了一个 Socket 类,用于实现 TCP 客户端程序。通过查阅 API 文档可知

Socket 类同样提供了多种构造方法,接下来就对 Socket 的常用构造方法进行详细讲解。

  • Socket()

使用该构造方法在创建 Socket 对象时,并没有指定IP地址和端口号,也就意味着只创建了客户端对象,并没有去

连接任何服务器。通过该构造方法创建对象后还需调用 connect(SocketAddress endpoint) 方法,才能完成与

指定服务器端的连接,其中参数endpoint用于封装IP地址和端口号。

  • Socket(String host,int port)

使用该构造方法在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host

接收的是一个字符串类型的IP地址。

  • Socket(Inet Address address,int port)

该方法在使用上与第二个构造方法类似,参数address用于接收一个InetAddress类型的对象,该对象用于封装一

个IP地址。

在以上Socket的构造方法中,最常用的是第一个构造方法。了解了Socket 的构造方法,接下来学习一下Socket的

常用方法:

  • int getPort()

该方法返回一个int类型对象,该对象是Socket对象与服务器端连接的端口号。

  • InetAddress getLocalAddress()

该方法用于获取Socket对象绑定的本地IP地址,并将IP地址封装成 InetAddress类型的对象返回。

  • void close()

该方法用于关闭Socket连接,结束本次通信。在关闭Socket之前,应将与Socket相关的所有的输人输出流全部关

闭,这是因为一个良好的程序应该在执行完毕时释放所有的资源。

  • InputStream getInputStream()

该方法返回一个 InputStream 类型的输入流对象,如果该对象是由服务器端的Socket返回,就用于读取客户端发

送的数据,反之,用于读取服务器端发送的数据。

  • OutputStream getOutputStream()

该方法返回一个 OutputStream 类型的输出流对象,如果该对象是由服务器端的Socket返回,就用于向客户端发

送数据,反之,用于向服务器端发送数据。

其中 getInputStream()getOutStream() 方法分别用于获取输人流和输出流。当客户端和服务端建立连接

后,数据是以IO流的形式进行交互的,从而实现通信。

在这里插入图片描述

6、TCP网络程序

要实现 TCP 通信需要创建一个服务器端程序和一个客户端程序。

服务端程序:

package com.example;

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

/**
 * @author test
 */
public class Example04 {
    public static void main(String[] args) throws Exception {
        new TcpServer().listen();
    }
}

/**
 *  TCP服务端
 */
class TcpServer {
    private static final int PORT = 7788;
    public void listen() throws Exception {
        ServerSocket serverSocket = new ServerSocket(PORT);
        Socket client = serverSocket.accept();
        // 获取客户端的输出流
        OutputStream os = client.getOutputStream();
        System.out.println("开始与客户端交互数据");
        os.write(("Hello World!").getBytes());
        Thread.sleep(5000);
        System.out.println("结束与客户端交互数据");
        os.close();
        client.close();
    }
}

客户端程序:

package com.example;

import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author test
 */
public class Example05 {
    public static void main(String[] args) throws Exception {
        new TcpClient().connect();
    }
}

/**
 * TCP客户端
 */
class TcpClient {
    private static final int PORT = 7788;

    public void connect() throws Exception {
        Socket client = new Socket(InetAddress.getLocalHost(), PORT);
        InputStream is = client.getInputStream();
        byte[] buf = new byte[1024];
        int len = is.read(buf);
        System.out.println(new String(buf, 0, len));
        client.close();
    }
}
开始与客户端交互数据
结束与客户端交互数据
Hello World!

7、多线程的TCP网络程序

多个客户端访问同一个服务器端,服务器端为每个客户端创建一个对应的Socket,并且开启一个新的线程使两个

Socket建立专线进行通信。

客户端程序不变,服务器端程序:

package com.example;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author test
 */
public class Example06 {
    public static void main(String[] args){
        new TcpServer1().listen();
    }
}

/**
 * TCP服务端
 */
class TcpServer1 {
    private static final int PORT = 7788;

    public void listen() {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            while (true) {
                final Socket client = serverSocket.accept();
                new Thread(()->{
                    OutputStream os;
                    try {
                        os = client.getOutputStream();
                        System.out.println("开始与客户端交互数据");
                        os.write(("Hello World!").getBytes());
                        Thread.sleep(5000);
                        System.out.println("结束与客户端交互数据");
                        os.close();
                        client.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
# 多次调用输出
开始与客户端交互数据
结束与客户端交互数据
开始与客户端交互数据
结束与客户端交互数据
开始与客户端交互数据
结束与客户端交互数据

8、TCP案例-文件上传

目前大多数服务器都会提供文件上传的功能,由于文件上传需要数据的安全性和完整性,很明显需要使用 TCP 协

议来实现。接下来通过一个案例来实现图片上传的功能,首先编写服务器端程序,用来接收图片。

服务端:

package com.example;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author test
 */
public class Example07 {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(10001);
        // 创建ServerSocket对象
        while (true) {
            // 调用accept()方法接收客户端请求,得到Socket对象
            Socket s = serverSocket.accept();
            // 每当和客户端建立Socket连接后,单独开启一个线程处理和客户端的交互
            new Thread(new ServerThread(s)).start();
        }
    }
}

class ServerThread implements Runnable {
    /**
     * 持有一个Socket类型的属性
     */
    private Socket socket;

    /**
     * 构造方法中把Socket对象作为实参传人
     *
     * @param socket
     */
    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        //获取客户端的 IP地址
        String ip = socket.getInetAddress().getHostAddress();
        int count = 1;
        //上传图片个数
        try {
            InputStream in = socket.getInputStream();
            // 创建上传图片目录的File对象
            File parentFile = new File(".\\upload");
            if (!parentFile.exists()) {
                // 如果不存在,就创建这个目录
                parentFile.mkdir();
            }
            // 把客户端的 IP地址作为上传文件的文件名
            File file = new File(parentFile, ip + "(" + count + ").jpg");
            while (file.exists()) {
                // 如果文件名存在,则把 count++
                file = new File(parentFile, ip + "(" + (count++) + ").jpg");
            }
            // 创建 FileOutputStream对象
            FileOutputStream fos = new FileOutputStream(file);
            // 定义一个字节数组
            byte[] buf = new byte[1024];
            // 定义一个int类型的变量len,初始值为0
            int len = 0;
            // 循环读取数据
            while ((len = in.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
            // 获取服务端的输出流
            OutputStream out = socket.getOutputStream();
            out.write("上传成功".getBytes());
            // 上传成功后向客户端写出上传成功
            fos.close();
            // 关闭输出流对象
            // 关闭Socket对象
            socket.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
package com.example;

import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @author test
 */
public class Example08 {
    public static void main(String[] args) throws Exception {
        // 创建客户端Socket
        Socket socket = new Socket("127.0.0.1", 10001);
        // 获取Socket的输出流对象
        OutputStream out = socket.getOutputStream();
        // 创建FileInputStream对象
        FileInputStream fis = new FileInputStream("src.png");
        // 定义一个字节数组
        byte[] buf = new byte[1024];
        // 定义一个int类型的变量 len
        int len;
        while ((len = fis.read(buf)) != -1) {
            //循环读取数据
            out.write(buf, 0, len);
        }
        // 关闭客户端输出流
        socket.shutdownOutput();
        // 获取Socket的输人流对象
        InputStream in = socket.getInputStream();
        // 定义一个字节数组
        byte[] bufMsg = new byte[1024];
        int num = in.read(bufMsg);
        // 接收服务端的信息
        String Msg = new String(bufMsg, 0, num);
        System.out.println(Msg);
        fis.close();
        //关键输入流对象
        socket.close();
        //关闭 Socket对象
    }
}
  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值