【Java进阶】04-网络编程(上)

2021/4/26 20:28:55

本文主要是介绍【Java进阶】04-网络编程(上),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

基础概念

网络编程操作的方便是 Java 的一个特性,或者说 Java 是一门面向网络的编程语言。JDK提供的 java.net 包里面提供了许多工具,可以满足我们很多基于网络通讯开发的要求。

复习一下网络的概念

这里只重新说一下端口号的概念。保留端口号为 1 - 1024,主要功能是标定一些公认的应用层协议,如 80 端口对应 http 协议,21 - ftp,23 - telnet,25 - smtp(smtp是一种发送邮件的协议)。实际上,一个端口就对应一个进程,一个进程对应一种服务,我们请求网络资源的时候,实际上是将我们的请求发送给网络上的一个主机中的一个进程来处理。1 - 1024 已经规定了许多我们需要共同遵守的协议,所以我们开发网络程序的时候,需要避开这些端口号,一般采取 1025 和 65535(端口号是16位) 之间的端口号。

Java 下载网页 html 文件的例子:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.net.*;

class Test{
    public static void URLReader() throws Exception{
        URL cs = new URL("https://www.sohu.com/");
        BufferedReader in = new BufferedReader(new InputStreamReader(cs.openStream()));
        BufferedWriter out = new BufferedWriter(new FileWriter("./doc/sohu.html"));

        String intputLine;
        while((intputLine = in.readLine())!=null){
            System.out.println(intputLine);
            out.write(intputLine);
            out.newLine();
        }
        in.close();
        out.close();
    }
}

这里我们读入搜狐首页的内容,然后输出到屏幕上和保存到 sohu.html 文件中。上面的 URL 对象的 openStream() 方法的作用就是打开网络文件并且产生一个输入字节流。

URL

  • URL(Uniform Resource Locator):同一资源定位符。表示互联网上的某一个资源的地址,任意资源的 URL 都是不同的。
  • URL 的组成:
    protocal: resourceName
    协议名表示资源所属的传输协议,如 http、ftp、file 等,资源名为资源的完整地址,包括主机名、端口号、文件名或者问价内部的一个引用。

举例如下:

  • http://www.sun.com
  • http://home.netscape.com/home/welcome.html
  • httlp://www.gamelan.com:80/Gamelan/network.html#BOTTOM
  • file:///e:\download\Fop.html
    第一个里面 http 是协议名,www 是主机名,sun 是公司名,com 是顶级域名,里面没有指定 html 文件,就默认返回主页面,也就是 index 页面。
    第二个里面 home/welcome.html 中 home 是文件夹的名称,welcome.html 是页面的名称
    第三个里面 80 是端口号,Gamelan 是文件夹的名称,network.html 是页面,BOTTOM 是页面的一个部分
    第四个是访问本机的一个网页,使用 file 协议,使用本机的文件地址即可。

URL 的构造方法

  1. public URL (String spec)
    URL urlBase = new URL(“http://www.gamelan.com/”);
    这种就是之前举例的,使用完整的域名访问 URL 资源
  2. public URL (URL context, String spec)
    URL gamelan = new URL(“http://www.gamelan.com/pages/”);
    URL gamelanGames = new URL(gamelan, “Gamelan.game.html”);
    URL gamalanNetwork = new URL(gamelan, “Gamelan.net.html”);
    这种使用的相对寻址方式,第一个参数是 URL 类型,一般给出了 URL 的文件夹,后面接一个 String 类型的参数,是一个相对的地址,两者结合就是完整的地址。
  3. public URL (String protocol, String host, String file)
    new URL(“http”, “www.gamelan.com”, “/pages/Gamelan.net.html”);
    这种的三个参数是 协议、主机地址、文件地址
  4. public URL (String protocol, String host, int port, String file)
    new URL(“http”, “www.gamelan.com”, 80, “pages/Gamelan.network.html”);
    这种在上面一个构造方法的基础上加了 协议名称

URL 在定位的时候有可能会出错,包括有可能格式出错等,可以放在一个异常里面处理

try{
	URL myURL = new URL(...);
} catch (MalformedURLException e){
	......
	// 处理的代码
}

如随便打开一个搜狐的新闻,代码如下所示

URL cs = new URL("https://www.sohu.com/a/462896217_267106?code=8aa8c30f5b21d6ad59515ff7728ba991&spm=smpc.home.top-news1.2.1619350679869E0iXkDv&_f=index_cpc_1");

System.out.println("Protocol: "+cs.getProtocol());
System.out.println("Host: "+cs.getHost());
System.out.println("Port: "+cs.getPort());
System.out.println("File: "+cs.getFile());
System.out.println("Reference: "+cs.getRef());

结果如下:

Protocol: https
Host: www.sohu.com
Port: -1
File: /a/462896217_267106?code=8aa8c30f5b21d6ad59515ff7728ba991&spm=smpc.home.top-news1.2.1619350679869E0iXkDv&_f=index_cpc_1
Reference: null

URLConnection 类

如果说 URL 代表了一个资源对象,那么一个 URLConnection 对象就代表了一个和 URL 资源的连接,可以通过它对这个 URL 资源进行读或者写。而 URL 对象只能对 URL 资源读。URLConnection 对象可以查看服务器响应消息的首部和设置客户端请求消息的首部。

使用 URLConnection 通信的一般步骤:

  1. 构造一个 URL 对象
  2. 调用这个 URL 对象的 openConnction() 方法获取对应的 URLConnection 对象
  3. 配置此 URLConnection 对象
  4. 读取一个消息的首部
  5. 获得输入流读取数据
  6. 获得输出流写入数据
  7. 关闭连接

一个使用 URLConnection 对象实现上面代码功能的例子

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

class Test{
    public static void testURLConnect(){
        try {
            URL cs = new URL("https://www.sohu.com");
            URLConnection tc = cs.openConnection();
            BufferedReader in = new BufferedReader(new InputStreamReader(tc.getInputStream()));
            String inputLine;
            while((inputLine = in.readLine())!=null){
                System.out.println(inputLine);
            }
            in.close();
        }catch (MalformedURLException e){
            System.out.println("A MalformedURLException happened");
        }catch (IOException e){
            System.out.println("An IOException happened");
        }
    }
}

public class Exp2 {
    public static void main(String[] args) {
        Test.testURLConnect();
    }
}

通过 URLConnection 对象实现 http 协议的 GET 和 POST 请求

使用 URLConnection 对象不仅可以接受网页的内容,也可以发送一些请求,这样就可以收到根据我们的请求响应的报文。

GET 和 POST 请求是 http 协议里面最常使用的两种请求,下面就来介绍如何使用 URLConnection 对象发送这些请求。

发送 GET 请求

举个例子

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

class Test{
    public static String sendGet(String url, String params){
        String result = "";
        BufferedReader in = null;
        try{
            String urlNameString = url + "?" +params;
            URL realURL = new URL(urlNameString);
            URLConnection connection = realURL.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.connect();

            // 通过 BufferedReader 输入流来读取 URL 的响应
            in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            while((line=in.readLine())!=null){
                result += line;
                System.out.println(line);
            }

        }catch (Exception e){
            System.out.println("Exception");
        }finally {
            try{
                if(in!=null){
                    in.close();
                }
            }catch (Exception e2){}
        }
        return result;
    }
}


public class Exp3 {
    public static void main(String[] args) {
        Test.sendGet("https://www.sohu.com/a/462860227_162522", "spm=smpc.home.top-news2.2.1619353774198FSmllE0&_f=index_news_1");
    }
}

其实这就是获取一个网页的内容,只不过可以使用 params 传递参数。一般参数就是网页链接中 ? 后面跟着的部分。

这里讲一下扩展的 URL,一个典型的 URL 如下
http://www.example.com/path/to/file?para1=foo&para2=bar#abc
问号之前的部分可以理解为文件的路径,它指向服务器上的一个文件。
问号之后 # 号之前是传递的一些参数。这些参数之间是用 & 符号分隔的。每个参数等号前是参数名,等号后是参数值。
# 号之后是页面内定位用的,指向页面内的锚点(一个有名称的 a 标签)。

上面的程序我们访问了新浪的一个网页并传递了一些参数。而且通过 setRequestProperty() 方法设置了一些通用的请求属性。

发送 POST 请求

POST 请求的参数需要通过 URLConnection 的输出流来写入参数

public static String sendPost(String url, String params){
    PrintWriter out = null;
    BufferedReader in = null;
    String result = "";
    try{
        URL realURL = new URL(url);
        URLConnection con = realURL.openConnection();
        // 设置一些通用的请求属性
        con.setRequestProperty("accept", "*/*");
        con.setRequestProperty("connection", "Keep-Alive");
        // 允许输出流
        con.setDoOutput(true);
        // 获取 URLConnection 对象对应的输出流
        out = new PrintWriter(con.getOutputStream());
        // 发送请求参数
        out.print(params);
        // flush 强制将缓冲区的流输出
        out.flush();

        // 读取返回信息
        in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String line;
        while((line = in.readLine())!=null){
            result += line;
            System.out.println(line);
        }
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        try{
            if(out!=null) out.close();
            if(in!=null) in.close();
        }catch (IOException e){e.printStackTrace();}
    }

    return result;
}

发送 POST 请求的时候必须使用 URLConnection 的 setDoOutput(true) 方法允许输出流才能发送。

HttpURLConnection 类

为了更加方便地处理 HTTP 请求,Java 在 URLConnection 的基础上定义了 HttpURLConnection 类,可以有一些方法方便针对 http 请求的内容。

包括可以

  • 设置 HTTP 状态码(如 HTTP_OK:200)
  • setRequestMethod() 方法可以设置请求的方法,如 GET/POST
  • getResponseCode() 方法可以获取 HTTP 响应

Socket 通讯原理

Java 中的 URL,URLConnection,Socket,ServerSocket 类都是使用 TCP 协议进行网络通讯。Socket 可以看作是一个定位的工具。某个 Client 的一个进程对应了一个端口,想要将一段信息发给远方的服务器。这个 Client 的进程就会用一个 socket 对这个信息进行打包,然后加入一些关于 Server 的端口的信息。当消息到达 Server 后,Server 中的 Sockets 就会检查是否是对应自己所在的应用进程,就根据里面的端口信息来判断,如果是的话,就将这段消息传给自己所对应的进程。一般来说想要两个双方建立连接进行数据的交互,需要在每一端各建立一个 Socket。

Socket 应用

创建 Socket

Socket 的构造方法:

  • Socket()
  • Socket(InetAddress address, int port)
    第一个参数是通讯对象的地址,第二个是对方进程的端口号
  • Socket(String host, int port)
    和上一种方式的区别是第一个参数是用字符串表示的地址,而不是用 InetAddress 对象
  • Socket(InetAddredss host, int port, InetAddress localAddr, int localPort)
    这种的变化是第三个和第四个参数分别指定了本地的地址和本地发送程序的端口号
  • Socket(String host, int port, InetAddress localAddr, int localPort)
    这种和上面一种的区别就是用字符串表示目标主机的地址

一个通过 Socket 实现两个进程交互聊天的例子

我们可以通过 Socket 对象发送消息,实现 Client 和 Server 交互消息的例子。这里我们再介绍一个类 ServerSocket。主要有一个构造方法
ServerSocket(int port), 用于定义这个进程对应的端口,也就是 Client 创建Socket 的时候需要传入的那个目标端口号。当一个 ServerSocket 对象创建了以后,应该要启动它的 accept() 方法,这是一个阻塞方法。它的行为是等待一个 Client 的Socket 请求连接,如果没有等到就一直等下去。当有一个Client 的 Socket 的请求到达后,它可以试图返回一个 Socket 对象,用于本地和 Client 的 Socket 进行连接。这样就表明我们想要实现 Server-Client 之间的交互,应该要先启动 Server 端的进程,创建一个 ServerClient 对象等待,然后启动一个 Client 端的进程,创建一个 Socket 企图和 Server 连接。

我们这里只实现一个简单的命令行聊天的工具,聊天的方式也是你发一句我发一句这样的同步方式,否则需要启动两个两个线程,一个用于发消息,一个用于对对方请求退出的请求作出响应。

实现代码如下:
客户端:

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

public class TalkClient {
    public static void main(String[] args) {
        try{
            Socket socket = new Socket("127.0.0.1", 4888);
            BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter os = new PrintWriter(socket.getOutputStream());
            BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            String readline;
            readline = sin.readLine();
            while(!readline.equals("bye")){
                os.println(readline);
                os.flush();
                System.out.println("Client: "+readline);
                System.out.println("Server: "+is.readLine());
                readline = sin.readLine();
            }
            os.close();
            is.close();
            sin.close();
            socket.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

服务器端:

import java.io.*;
import java.net.*;
import java.applet.Applet;

public class TalkServer {
    public static void main(String[] args) {
        try{
            ServerSocket server = null;
            try{
                server = new ServerSocket(4888);
            }catch (Exception e) {e.printStackTrace();}
            Socket socket = null;
            try{
                socket = server.accept();
            }catch (Exception e) {e.printStackTrace();}

            String line;
            BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter os = new PrintWriter(socket.getOutputStream());
            BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("Client: " + is.readLine());
            line = sin.readLine();
            while(!line.equals("bye")){
                os.println(line);
                os.flush();
                System.out.println("Server: "+line);
                System.out.println("Client: "+is.readLine());
                line = sin.readLine();
            }
            is.close(); os.close(); sin.close();
            socket.close(); server.close();
        }catch (Exception e) {e.printStackTrace();}
    }
}

运行结果如下:
客户端:

hello
Client: hello
Server: how are you
what is your name?
Client: what is your name?
Server: My name is Jack.
bye

服务器端:

Client: hello
how are you
Server: how are you
Client: what is your name?
My name is Jack.
Server: My name is Jack.
Client: null
bye

使用两个线程一个用来接收对方发来的信息,一个用来接收键盘输入,可以实现可单方连续发送多条消息。不过本人设置的是要 Client 先退出,Server 再退出,否则会报错
Client 代码:

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

public class TalkClient {
    private static String readLine = null;
    private static Socket socket = null;
    private static BufferedReader sin = null;
    private static PrintWriter os = null;
    private static BufferedReader is = null;

    public static void main(String[] args) {
        try{
            socket = new Socket("127.0.0.1", 4888);
            sin = new BufferedReader(new InputStreamReader(System.in));
            os = new PrintWriter(socket.getOutputStream());
            is = new BufferedReader(new InputStreamReader(socket.getInputStream()));


            Thread ct = new Thread(){
                @Override
                public void run() {
                    super.run();
                    try{
                        while(true){
                            readLine = sin.readLine();
                            if(readLine.equals("bye")){
                                break;
                            }
                            System.out.println("Client: "+readLine);
                            os.println(readLine);
                            os.flush();
                        }
                        os.close(); is.close();sin.close();
                        socket.close();
//                        System.out.println("Closed all");
                    }catch (Exception e) {e.printStackTrace();}
                }
            };

            Thread st = new Thread(){
                @Override
                public void run() {
                    super.run();
                    try{
                        while(true){
                            String line;
                            try{
                                line = is.readLine();
                            }catch (java.net.SocketException e) {break;}
                            System.out.println("Server: " + line);

                        }
                    }catch (Exception e) {e.printStackTrace();}
                }
            };

            ct.start(); st.start();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

Server 代码:

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

public class TalkServer {
    private static String line = null;
    private static Socket socket = null;
    private static BufferedReader sin = null;
    private static PrintWriter os = null;
    private static BufferedReader is = null;
    private static ServerSocket server = null;


    public static void main(String[] args) {
        try{
            try{
                server = new ServerSocket(4888);
            }catch (Exception e) {e.printStackTrace();}

            try{
                socket = server.accept();
            }catch (Exception e) {e.printStackTrace();}

            is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            os = new PrintWriter(socket.getOutputStream());
            sin = new BufferedReader(new InputStreamReader(System.in));

            Thread st = new Thread(){
                @Override
                public void run() {
                    super.run();
                    try{
                        while(true){
                            line = sin.readLine();
                            if(line.equals("bye")){
                                break;
                            }
                            System.out.println("Server: "+line);
                            os.println(line);
                            os.flush();
                        }
                        is.close(); os.close(); sin.close();
                        socket.close(); server.close();
//                        System.out.println("Closed all");
                    }catch (Exception e) {e.printStackTrace();}
                }
            };

            Thread ct = new Thread(){
                @Override
                public void run() {
                    super.run();
                    try{
                        while(true){
                            try{
                                String line = is.readLine();
                                if(line.equals("null")|| line==null);
                                System.out.println("Client: "+line);
                            }catch (java.lang.NullPointerException e){break;}

                        }
                    }catch (Exception e) {e.printStackTrace();}
                }
            };

            st.start(); ct.start();

        }catch (Exception e) {e.printStackTrace();}
    }
}

演示结果如下:
Server:

sfk
Server: sfk
sse
Server: sse
Client: hello
Client: what did you say
bye

Client:

Server: sfk
Server: sse
hello
Client: hello
what did you say
Client: what did you say
bye



这篇关于【Java进阶】04-网络编程(上)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程