网络编程API

一.Java 网络工具类

1.1 获取 IP 地址

        在 TCP/IP 协议中,是通过 IP 地址来标识网络上的一台主机的。通过查阅 JDK API 文档获悉,在 Java 中,使用 java.net 包下的 InetAddress 类表示互联网协议的 IP 地址。

下面列出了 InetAddress 类的静态方法:

InetAddress[] getAllByName(String host):在给定主机名的情况下,根据系统上配置的名称服务 host 返回其 IP 地址所组成的数组。


InetAddress getByAddress(byte[] addr):给定字节数组形式的 IP 地址,返回 InetAddress 对象。


InetAddress getByAddress(String host, byte[] addr):根据提供的主机名 host 和字节数组形式的 IP 地址 addr,创建 InetAddress 对象。


InetAddress getByName(String host):给定主机名 host,返回 InetAddress 对象。


InetAddress getLocalHost():返回本地主机 InetAddress 对象。

InetAddress 类其他常用方法有: 

byte[] getAddress():返回此 InetAddress 对象的原始 IP 地址。


String getCanonicalHostName():返回此 IP 地址的完全限定域名。完全限定域名是指主机名加上全路径,全路径中列出了序列中所有域成员。


String getHostAddress():返回 IP 地址字符串。


String getHostName():返回此 IP 地址的主机名。

import java.net.InetAddress;
import java.net.UnknownHostException;
/**
 * 获取本机 IP 地址
 */
public class TestGetIP {
    public static void main(String[] args) {
        try {
            // 通过InetAddress类的静态方法,返回本地主机对象
            InetAddress myIP = InetAddress.getLocalHost();
            // 通过InetAddress类的getHostAddress()方法获得IP地址字符串
            System.out.println(myIP.getHostAddress());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
 * 输入域名获取对应的 IP 地址
 */
public class TestGetIP2 {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("请输入要查询IP地址的域名:");
        String dName = input.next();
        try {
            // 通过InetAddress类的静态方法,返回指定域名的IP地址对象
            InetAddress assignIP = InetAddress.getByName(dName);
            // 输出域名和对应的 IP 地址
            System.out.println("域名:" + dName + " 对应的IP地址为:" + assignIP.getHostAddress());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } finally{
            input.close();
        }
    }
}

 总结

通过实验我们使用:

        getByName() 和 getLocalHost() 方法,获取 InetAddress 对象。
        getHostAddress() 方法获取 IP 地址字符串。
        也可以通过 getByAddress() 方法获取 InetAddress 对象。getCanonicalHostName() 和         getHostName() 方法获取 IP 相应信息。

1.2 URL 类的使用(very nice)

        通过 java.net 包中 URL 类获取网络上指定资源的信息。URL 类代表一个统一资源定位符,它是指向互联网资源的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。在 java.net 包中,URL 类是被 final 修饰。

URL API 中提供的构造方法有:

public URL(String spec) throws MalformedURLException

根据提供的地址创建一个 URL 对象。

public URL(String protocol, String host, int port, String file) throws MalformedURLException

根据提供的协议名、主机名、端口号和文件名,创建一个 URL 对象。

public URL(String protocol, String host, String file) throws MalformedURLException

根据提供的协议名、主机名和文件名,创建一个 URL 对象。

常用的方法有:

方法                                 说明
openStream()             打开与该 URL 的连接,并返回 InputStream 对象从该连接读取。
getFile()                      获取该 URL 的文件信息。
getHost()                    获取该 URL 的主机信息。
getPath()                    获取该 URL 的路径信息。
getPort()                     获取该 URL 的端口信息。
getProtocol()              获取该 URL 的协议信息。
getContent()               获取该 URL 的内容信息。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Scanner;
/**
 * URL 类的使用
 */
public class TestURL {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("请输入要定位的URL地址:");
        String url = input.next();
        System.out.print("请输入要显示哪个页面标签元素的内容:");
        String iStr = input.next();
        BufferedReader in = null;
        try {
            // 通过 url 字符串创建 URL 对象
            URL tURL = new URL(url);
            // 通过 IO 流读取信息
            in = new BufferedReader(new InputStreamReader(tURL.openStream()));
            String s;
            while((s = in.readLine()) != null){
                // 匹配指定的标签内容
                if(s.contains(iStr)){
                    System.out.println(s);
                }
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            // 资源释放
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            input.close();
        }
    }
}

如果不查询直接输出s 可以看到网页具体内容 

 总结

使用 URL 的构造方法创建 URL 对象,需要给出正确的 url 地址,否则会抛出异常。

URL 类读取网页数据信息,需要通过 IO 流才可以进行方法,所有在 URL 类中提供了 openStream() 方法。

1.3 URLConnection 类的使用

        前面介绍的 URL 类代表的是一个网络资源的位置,而接下来要介绍的 URLConnection 代表的是一种连接。

通常,创建一个到 URL 的连接 URLConnection 的对象需要以下几个步骤:

        通过在 URL 上调用 openConnection() 方法创建连接对象。
        设置参数和一般请求属性。
        使用 connect() 方法建立到远程对象的实际连接。
        远程对象变为可用,其中远程对象的头字段和内容变为可访问。

以下,列出一些 URLConnection 类的属性及其含义。

boolean doInput :将 doInput 标志设置为 true,指示应用程序要从 URL 连接读取数据,此属性的默认值为 true。

此属性由 setDoInput() 方法设置,其值由 getDoInput() 方法返回。

boolean doOutput :将 doOutput 标志设置为 true,指示应用程序要将数据写入 URL 连接,此属性的默认值为 false。

此属性由 setDoOutput() 方法设置,其值由 getDoOutput() 方法返回。

boolean useCaches :如果其值为 true,则只要有条件就允许协议使用缓存;如果其值为 false,则该协议始终必须获得此对象的新副本,其默认值为上一次调用

setDefaultUseCaches() 方法时给定的值。

此属性由 setUseCaches() 方法设置,其值由 getUseCaches() 方法返回。

URLConnection 类还有两个属性 connected 和 url。

connected 表示是否连接到了指定 url 对象,url 表示该 URLConnection 类在互联网上打开的网络对象。

另外,可以使用 setRequestProperty(String key, String value) 方法以覆盖的方式设置请求属性。

        本代码中会涉及一些暂未讲解的 API,在阅读下面的代码时,如果有不明白的地方,可暂时跳过。 

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
/**
 * URLConnection 类的使用
 */
public class TestURLConnection {
    public static void main(String[] args) {
        try {
            // (1)通过在URL上调用openConnection()方法创建连接对象
            URL url = new URL("http://chenjianzhang.top/");
            // 根据URL获取URLConnection对象
            URLConnection urlC = url.openConnection();
            // 请求协议是HTTP协议,故可转换为HttpURLConnection对象
            HttpURLConnection hUrlC = (HttpURLConnection) urlC;
            // (2)设置参数和一般请求属性
            // 请求方法如果是POST,参数要放在请求体里,所以要向hUrlC输出参数
            hUrlC.setDoOutput(true);
            // 设置是否从httpUrlConnection读入,默认情况下是true
            hUrlC.setDoInput(true);
            // 请求方法如果是POST,不能使用缓存
            hUrlC.setUseCaches(false);
            // 设置Content-Type属性
            hUrlC.setRequestProperty("Content-Type", "text/plain; charset=utf-8");
            // 设定请求的方法为POST,默认是GET
            hUrlC.setRequestMethod("POST");
            // (3)使用connect方法建立到远程对象的实际连接
            hUrlC.connect();
            // (4)远程对象变为可用
            // 通过HttpURLConnection获取输出输入流,可根据需求进一步操作
            InputStream inStrm = hUrlC.getInputStream();
            // 判断请求是否成功
            if(hUrlC.getResponseCode() == 200){
                System.out.println(inStrm.toString());
            }else{
                System.out.println("POST请求失败");
            }
            // 关闭连接
            hUrlC.disconnect();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 总结:

URLConnection 的对象需要的以下几个步骤:

        通过在 URL 上调用 openConnection() 方法创建连接对象。
        设置参数和一般请求属性。
        使用 connect() 方法建立到远程对象的实际连接。
        远程对象变为可用,其中远程对象的头字段和内容变为可访问。

二. Socket 编程

2.1 基于 TCP 的 Socket 通信

        Socket 通常也称为套接字,应用程序通常通过套接字向网络发出请求或者应答网络请求。Java 语言中的 Socket 编程常用到 Socket 和 ServerSocket 这两个类, Socket 用于端对端的通信,而 ServerSocket 常用于服务端对象的创建,它们都位于 java.net 包中。Socket 首先用于建立网络连接,在连接成功后,应用程序两端都会产生一个 Socket 实例,操作这个实例,完成所需的会话。

        Socket 编程是由服务器端和客户端两部分组成的,并且二者是相互交错通信的,因此在编写代码时就不能像以前那样先把一个类写完毕之后再编写第二个类,而应该是交错式的同步编写。换句话说,在 Socket 编程中,服务端和客户端互为依赖,在编写代码时二者都需要借助对方的已有代码。

服务器端程序

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

/**
 * 服务器端
 */
public class TestServer {
    public static void main(String[] args) {
        ServerSocket server = null;
        try {
            // 创建一个ServerSocket对象,并指定端口号为8888
            server = new ServerSocket(8888);
            // 侦听并接受到此套接字的连接
            while(true){
                Socket s = server.accept();
                DataOutputStream dos = new DataOutputStream(s.getOutputStream());
                dos.writeUTF("客户端IP:" + s.getInetAddress().getHostAddress()
                        + "客户端端口号:" + s.getPort());
                // 关闭数据流对象
                dos.close();
                // 关闭 Socket 对象
                s.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(server != null){
                try {
                    // 关闭 ServerSocket 对象
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

该服务器端程序的作用就是监听 8888 端口,当有发送到本机 8888 端口的 Socket 请求时,建立输出流,将通过 accept() 方法创建的 Socket 对象的 IP 地址和端口号输出到客户端。编译运行程序,使服务器启动并处于监听状态。

客户端程序

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.Socket;
/**
 * 客户端
 */
public class TestClient {
    public static void main(String args[]) {
        try {
            // 通过IP地址和端口号,创建一个Socket对象
            Socket s = new Socket("127.0.0.1", 8888);
            // 建立输入数据流
            DataInputStream dis = new DataInputStream(s.getInputStream());
            System.out.println(dis.readUTF());
              // 关闭数据流对象
            dis.close();
              // 关闭 Socket 对象
            s.close();
        } catch (ConnectException e) {
            e.printStackTrace();
            System.err.println("服务器连接失败!");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端程序通过 IP 地址 127.0.0.1 和端口号 8888,创建一个客户端 Socket 对象,建立输入数据流,通过输入数据流读取指定 IP 地址和端口号上服务器端程序的输出,并在控制台将服务器的输出显示出来。

总结

本实验主要讲解了 Socket 通信的一个简单过程:

        通过 Java Socket 编程实现的客户端、服务器端程序中,客户端仅仅是向服务端发起了一个请求,但并没有具体的请求内容;服务端是在接收到请求后,将写死在程序中的内容发送给了客户端,之后客户端将接收的内容打印出来,仅仅只是模拟了一下实现过程。不管是服务器端还是客户端都需要有 Socket 对象存在,完成操作后,都需要关闭 Socket 对象。

2.2 使用多线程优化 Socket 通信

        本实验将学习多线程结合Socket完成通信,在上一个实验的基础上,使服务端和客户端均可以接收或向对方发送数据。此外,为了提高效率,同时需要在服务端程序中加入了多线程技术,实现了一个客户端与服务器端的双向聊天。

服务器端程序

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * 聊天室服务器端
 */
public class TestSockServer {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(8888);
            while (true) {
                Socket socket = serverSocket.accept();
                ServerThread serverThread = new ServerThread(socket);
                serverThread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭 ServerSocket 对象
            try {
                if(serverSocket != null){
                    serverSocket.close();
                }               
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
class ServerThread extends Thread {
    Socket socket;
    Scanner input = new Scanner(System.in);

    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        DataInputStream dis = null;
        DataOutputStream dos = null;
        try {
            dos = new DataOutputStream(socket.getOutputStream());
            dis = new DataInputStream(socket.getInputStream());
            String str = null;

            while (true) {
                if ((str = dis.readUTF()) != null) {
                    if ("bye".equals(str)){
                        break;
                    }
                    System.out.println("客户端发来的内容:" + str);
                }

                // 服务端向客户器端发送响应内容
                System.out.println("请输入要向客户端发送的信息:");
                String msg = input.nextLine();
                dos.writeUTF(msg);
                System.out.println();
            }
            // 资源释放
            dis.close();
            dos.close();
            socket.close();
            input.close();
        } catch (EOFException e) {
            System.out.println("客户端" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort() + "退出!");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端程序

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
 * 聊天室客户端
 */
public class TestSockClient {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        Socket socket = null;
        DataInputStream dis = null;
        DataOutputStream dos = null;
        String receive = null;
        try {
            // 根据提供的 IP 地址和端口号,创建 Socket 对象
            socket = new Socket("localhost", 8888);

            while (true) {
                dis = new DataInputStream(socket.getInputStream());
                dos = new DataOutputStream(socket.getOutputStream());
                //客户端向服务器端发送请求内容
                System.out.println("请输入要向服务端发送的信息(输入字符串 bye 结束会话):");
                String msg = input.nextLine();
                if ("bye".equals(msg)) {
                    System.out.println("已退出聊天!");
                    break;
                }

                dos.writeUTF(msg);
                if ((receive = dis.readUTF()) != null){
                    System.out.println("\n服务端发来的内容:" + receive);
                }
            }
            // 资源释放
            dis.close();
            dos.close();
            socket.close();
            input.close();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

每当有一个客户端连接到服务端时,服务端都会创建一个线程去处理这个客户端的请求。

在客户端和服务端双向聊天时,程序的基本流程是:

客户端通过 Socket 输出流向服务端发送一句聊天内容,之后服务端通过 Socket 输入流接收到这条聊天内容并打印显示,然后再通过 Socket 输出流向客户端反馈一条聊天内容,之后客户端再通过 Socket 输入流接收这条聊天内容。可以一直这样进行数据交互操作。当客户端发送字符串 bye 时,客户端会显示 ”已退出聊天“ ,服务器端也会有客户端退出的提示信息。

2.3 基于 UDP 的 Socket 通信

UDP 是用户数据报协议,提供的是无连接、不可靠信息传送服务。

Java 主要提供了两个类来实现基于 UDP 的 Socket 编程。

DatagramSocket :此类表示用来发送和接收数据报包的套接字。

数据报套接字是包投递服务的发送或接收点,每个在数据报套接字上发送或接收的包都是单独编址和路由的。

从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。

在 DatagramSocket 上总是启用 UDP 广播发送。

DatagramPacket :此类表示数据报包,它用来实现无连接包投递服务。

DatagramPacket 会根据该包中包含的地址和端口等信息,将报文从一台机器路由到另一台机器。

DatagramPacket 类主要有两个构造方法:

DatagramPacket(byte[] recyBuf, int readLength) :用来接收数据。

用一个字节数组接收 UDP 包,recyBuf 数组在传递给构造方法时是空的,而 readLength 值用来设定要读取的字节数。

DatagramPacket(byte[] sendBuf, int sendLength, InetAddress iaddr, int port) :用来发送数据。

建立将要传输的 UDP 包,并指定 IP 地址和端口号。

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
 * Socket 通信接收端
 */
public class TestUDPServer {
    public static void main(String args[]) throws Exception {
        //创建数据报包的套接字,端口号8888
        DatagramSocket ds = new DatagramSocket(8888);
        byte buf[] = new byte[1024];
        //创建接收的数据报包
        DatagramPacket dp = new DatagramPacket(buf, buf.length);
        System.out.println("Socket通信接收端");
        while (true) {
            //从此套接字接收数据报包
            ds.receive(dp);
            ByteArrayInputStream bais = new ByteArrayInputStream(buf);
            DataInputStream dis = new DataInputStream(bais);
            System.out.println("接收数据为:" + dis.readLong());
        }  
    }
}

 

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
 * Socket 通信发送端
 */
public class TestUDPClient {
    public static void main(String args[]) throws Exception {
        long n = 10000L;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        dos.writeLong(n);
        byte[] buf = baos.toByteArray();
        System.out.println("Socket通信发送端");
        System.out.println("发送数据长度为:" + buf.length);
        //创建数据报包的套接字,端口号9999
        DatagramSocket ds = new DatagramSocket(9999);
        //创建发送的数据报包
        DatagramPacket dp = new DatagramPacket(buf, buf.length,
                InetAddress.getByName("127.0.0.1"), 8888);
        //从此套接字发送数据报包
        ds.send(dp);
        ds.close();
    }
}

总结

本实验主要讲解的是基于 UDP 的 Socket 通信实现。

接收端和发送端都需要创建 DatagramSocket 对象。
通过不同的 DatagramPacket 构造方法来发送和接收数据信息。
DatagramPacket 类中提供了 receive() 方法接收数据,send() 方法发送数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

猪八戒1.0

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值