JavaSE 第十九章 网络编程

Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的 网络应用程序。

Java提供的网络类库,可以实现无缝的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。

网络编程的目的就是指直接或间接地通过网络协议与其它计算机进行通讯。

网络编程中有两个主要的问题:

如何准确地定位网络上一台或多台主机

找到主机后如何可靠高效地进行数据传输

要想让处于网络中的主机互相通信,只是知道通信双方地址还是不够的,还必须遵循一定的规则。有两 套参考模型:

OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广

TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。

  1. TCP/IP协议

TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,包括多 个具有不同功能且互为关联的协议。

TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即网络接口层、IP层、传输层和应 用层。下图表示了TCP/IP的分层结构和与OSI参考模型的对应关系。

 

传输层协议中有两个非常重要的协议:

  • 传输控制协议TCP(Transmission Control Protocol)

  • 用户数据报协议UDP(User Datagram Protocol)。

传输控制协议TCP是面向连接的传输层协议。即应用进程(或程序)在使用TCP协议之前,必须先建立TCP 连接,在传输完毕后,释放已经建立的连接。利用TCP协议进行通信的两个应用进程,一个是服务器进 程。另一个是客户进程。

• 用户数据报协议UDP是面向无连接的运输层协议。即应用进程(或程序)在使用UDP协议之前,不必先建 立连接。自然,发送数据结束时也没有连接需要释放。因此,减少了开销和发送数据之前的时延。 Internet上的主机有两种方式表示地址:

域名:www. baidu.com

IP 地址:180.97.33.108(动态IP地址)

  1. InetAddress简介

InetAddress 类对象含有一个 Internet 主机地址的域名和IP地址:www.baidu.com/180.97.33.108

域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地 址,这样才能和主机建立连接。

获取Internet上主机的地址:使用InetAddress类的静态方法:

getByName(String s):将一个域名或 IP 地址传递给该方法的参数,获得一个 InetAddress对象,该对 象含有主机地址的域名和IP地址,该对象用如下格式表示它包含的信息: www.baidu.com/180.97.33.108

public static void main(String[] args) {
    try {
        InetAddress inter = InetAddress.getByName("www.baidu.com");
        String add = inter.getHostAddress();
        System.out.println(add);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

端口号:标识正在计算机上运行的进程。公认端口:0-1023。注册端口:1024-49151 。

TCP程序设计:

客户端-服务器模型是最常见的网络应用程序模型。当我们上网时,我们所使用的浏览器(例如IE)就是一 个客户端软件,而提供网页的站点必需运行一个WEB服务器。

一般而言,主动发起通信的应用程序属于客户端。而服务器则是等待通信请求,当服务器收到客户端的 请求,执行需要的运算然后向客户端返回结果。

IP 地址标识 Internet 上的计算机,端口号标识正在计算机上运行的进程(程序)。

端口号与IP地址的组合得出一个网络套接字。

端口号被规定为一个 16 位的整数 0~65535。其中,0~1023被预先定义的服务通信占用(如telnet占用 端口23,http占用端口80等)。除非我们需要访问这些特定服务,否则,就应该使用 1024~65535 这些 端口中的某一个进行通信,以免发生端口冲突。

利用套接字(Socket)接口开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。套接字能执 行7种基本操作:

• 连接到远程主机

• 绑定到端口

• 接收从远程机器来的连接请求

• 监听到达的数据

• 发送数据

• 接收数据

• 关闭连接

  1. Socket简介

两个Java应用程序可通过一个双向的网络通信连接实现数据交换,这个双向链路的一段称为一个Socket (套接字)。Socket通常用来实现Client/Server 连接。

Java语言的基于套接字编程分为服务器编程和客户端编程,其通信模型如图所示:

一个简单的案例:服务器端与客户端之间是通过流进行交互的。请回顾,输入流与输出流

该案例实现如下效果,客户端与服务器端进行交流,如果客户端输入的是hello,服务器端返回world. 传输普通 的文字

 public class ClientTest {
    public static void main(String[] args) {
        System.out.println("客户端启动");
        try {
            Socket socket = new Socket("localhost",8888);
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("你好,服务器".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 public class ServerTest {
    public static void main(String[] args) {
        try {
            System.out.println("客户端已经启动");
            ServerSocket serverSocket = new ServerSocket(8888);
            Socket socket = serverSocket.accept();
            InputStream is = socket.getInputStream();
            byte[]bs = new byte[1024];
            is.read(bs);
            String s = new String(bs);
            System.out.println("接收到的信息:"+s.trim());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Test1_Server {
    public static void main(String[] args) throws IOException {
        System.out.println("服务开启");
        ServerSocket server = new ServerSocket(12456);
        Socket socket = server.accept();
        InputStream inStream = socket.getInputStream();
        int i = -1;
        byte[] bs = new byte[1024];
        inStream.read(bs);
        String s = new String(bs);
        OutputStream os = null;
        System.out.println(s);
        if(null!=s&&s.trim().equals("hello")) {
            os = socket.getOutputStream();
            os.write("world".getBytes());
        }
        inStream.close();
        if(null!=os) {
            os.close();
        }
        socket.close();
        server.close();
    }
}
 

public class Test1_Client {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.println("请输入要传递的内容");
        String words = input.next();
        try {
            Socket socket = new Socket("localhost", 12456);
            OutputStream os = socket.getOutputStream();
            os.write(words.getBytes());
            InputStream inStream = socket.getInputStream();
            byte[] bs=new byte[1024];
            inStream.read(bs);
            String s = new String(bs);
            System.out.println(s.trim());
            os.close();
            inStream.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

传输普通的图片

public class ClientTest01 {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",8888);
            OutputStream out = socket.getOutputStream();
            FileInputStream inputStream = new FileInputStream("D:/info4.png");
            int i;
            while((i=inputStream.read())!=-1){
                out.write(i);
            }
            out.close();
            inputStream.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class ServerTest01{
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            FileOutputStream out = new FileOutputStream("bak.png");
            int i ;
            while((i=inputStream.read())!=-1){
                out.write(i);
            }
            out.close();
            inputStream.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 

package com.openlab.net;
import com.openlab.test.OtherClass;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class ClientTest01 {
    //客户端给服务器端发送图片,并收到服务器端的返回的信息
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",8888);
            OutputStream out = socket.getOutputStream();//output
            FileInputStream inputStream = new FileInputStream("D:/info4.png");
            int i;
            while((i=inputStream.read())!=-1){
                out.write(i);
            }
            socket.shutdownOutput();//关闭output
            InputStream inputStream1 = socket.getInputStream();
            byte[] bs = new byte[1024];
            inputStream1.read(bs) ;
            String s = new String(bs);
            System.out.println("收到服务器端信息:"+s.trim());
            out.close();
            inputStream.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class ServerTest01{
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            FileOutputStream out = new FileOutputStream("bak.png");
            int i ;
            while((i=inputStream.read())!=-1){
                out.write(i);
            }
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("已经收到信息".getBytes());
            outputStream.close();
            inputStream.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

UDP:

和TCP编程相比,UDP编程就简单得多,因为UDP没有创建连接,数据包也是一次收发一个,所以没有 流的概念。在Java中使用UDP编程,仍然需要使用Socket,因为应用程序在使用UDP时必须指定网络接 口(IP)和端口号。注意:UDP端口和TCP端口虽然都使用0~65535,但他们是两套独立的端口,即一 个应用程序用TCP占用了端口1234,不影响另一个应用程序用UDP占用端口1234。在服务器端,使用 UDP也需要监听指定的端口。Java提供了 DatagramSocket 来实现这个功能。UDP数据报通过数据报套 接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送达目的地,也不能确定什么 时候可以抵达。DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号 以及接收端的IP地址和端口号。

class Send{
    public static void main(String[] args) {
        try {
            DatagramSocket datagramSocket = new DatagramSocket();
            String s = "我的UDP方式发送的信息";
            byte[] bs = s.getBytes();
            InetAddress inetAddress = InetAddress.getLocalHost();
            DatagramPacket packet = new DatagramPacket(bs,0,bs.length,inetAddress,8888);
            datagramSocket.send(packet);
            datagramSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Receive{
    public static void main(String[] args) {
        try {
            DatagramSocket datagramSocket = new DatagramSocket(8888);
            byte[] bs = new byte[1024];
            DatagramPacket packet = new DatagramPacket(bs,0,bs.length);
            datagramSocket.receive(packet);
            System.out.println(new String(packet.getData(),0,packet.getLength()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 

URL编程:URL(Uniform Resource Locator)统一资源定位符,它标识Internet上某一资源的地址。 它是一种具体的URL,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。

public static void main(String[] args) {
    HttpURLConnection urlConnection = null;
    InputStream inputStream = null;
    FileOutputStream out = null;
    try {
        URL url = new URL("http://localhost:8080/jspdemo01_war_exploded/test.bmp");
        urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.connect();
        inputStream = urlConnection.getInputStream();
        int i ;
        out = new FileOutputStream("bak1.png");
        while((i=inputStream.read())!=-1){
            out.write(i);
        }
        out.close();
        inputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

HTTP:

什么是HTTP?HTTP就是目前使用最广泛的Web应用程序使用的基础协议,例如,浏览器访问网站,手 机App访问后台服务器,都是通过HTTP协议实现的。HTTP是HyperText Transfer Protocol的缩写,翻 译为超文本传输协议,它是基于TCP协议之上的一种请求-响应协议。

我们来看一下浏览器请求访问某个网站时发送的HTTP请求-响应。当浏览器希望访问某个网站时,浏览 器和网站服务器之间首先建立TCP连接,且服务器总是使用 80 端口和加密端口 443 ,然后,浏览器向 服务器发送一个HTTP请求,服务器收到后,返回一个HTTP响应,并且在响应中包含了HTML的网页内 容,这样,浏览器解析HTML后就可以给用户显示网页了。一个完整的HTTP请求-响应如下。

 

HTTP请求的格式是固定的,它由HTTP Header和HTTP Body两部分构成。第一行总是 请求方法 路径 HTTP版本 ,例如, GET / HTTP/1.1 表示使用 GET 请求,路径是 / ,版本是 HTTP/1.1 。

后续的每一行都是固定的 Header: Value 格式,我们称为HTTP Header,服务器依靠某些特定的 Header来识别客户端请求,例如:

Host:表示请求的域名,因为一台服务器上可能有多个网站,因此有必要依靠Host来识别用于请求;

User-Agent:表示客户端自身标识信息,不同的浏览器有不同的标识,服务器依靠User-Agent判断客户 端类型;

Accept:表示客户端能处理的HTTP响应格式, / 表示任意格式, text/* 表示任意文本, image/png 表 示PNG格式的图片;

Accept-Language:表示客户端接收的语言,多种语言按优先级排序,服务器依靠该字段给用户返回特 定语言的网页版本。

如果是 GET 请求,那么该HTTP请求只有HTTP Header,没有HTTP Body。如果是 POST 请求,那么该 HTTP请求带有Body,以一个空行分隔。一个典型的带Body的HTTP请求如下。

POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
username=hello&password=123456

POST 请求通常要设置 Content-Type 表示Body的类型, Content-Length 表示Body的长度,这样服务 器就可以根据请求的Header和Body做出正确的响应。

此外, GET 请求的参数必须附加在URL上,并以URLEncode方式编码,例如:http://www.example.co m/?a=1&b=K%26R ,参数分别是 a=1 和 b=K&R 。因为URL的长度限制,GET 请求的参数不能太多, 而 POST 请求的参数就没有长度限制,因为 POST 请求的参数必须放到Body中。并且, POST 请求的参 数不一定是URL编码,可以按任意格式编码,只需要在 Content-Type中正确设置即可。常见的发送 JSON的 POST 请求如下:

POST /login HTTP/1.1
Content-Type: application/json
Content-Length: 38
{"username":"bob","password":"123456"}

HTTP响应也是由Header和Body两部分组成,一个典型的HTTP响应如下:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 133251
<!DOCTYPE html>
    <html><body>
    <h1>Hello</h1>

响应的第一行总是 HTTP版本 响应代码 响应说明 ,例如, HTTP/1.1 200 OK 表示版本是HTTP/1.1 ,响 应代码是 200 ,响应说明是 OK 。客户端只依赖响应代码判断HTTP响应是否成功。

HTTP有固定的响应代码:

1xx:表示一个提示性响应,例如101表示将切换协议,常见于WebSocket连接;

2xx:表示一个成功的响应,例如200表示成功,206表示只发送了部分内容;

3xx:表示一个重定向的响应,例如301表示永久重定向,303表示客户端应该按指定路径重新发送请 求;

4xx:表示一个因为客户端问题导致的错误响应,例如400表示因为Content-Type等各种原因导致的无效 请求,404表示指定的路径不存在;

5xx:表示一个因为服务器问题导致的错误响应,例如500表示服务器内部故障,503表示服务器暂时无 法响应。

当浏览器收到第一个HTTP响应后,它解析HTML后,又会发送一系列HTTP请求,例如, GET /logo.jpg HTTP/1.1 请求一个图片,服务器响应图片请求后,会直接把二进制内容的图片发送给浏览器:

HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Length: 18391
????JFIFHH??XExifMM?i&??X?...(二进制的JPEG图片)

因此,服务器总是被动地接收客户端的一个HTTP请求,然后响应它。客户端则根据需要发送若干个 HTTP请求。

对于最早期的HTTP/1.0协议,每次发送一个HTTP请求,客户端都需要先创建一个新的TCP连接,然后, 收到服务器响应后,关闭这个TCP连接。由于建立TCP连接就比较耗时,因此,为了提高效率, HTTP/1.1协议允许在一个TCP连接中反复发送-响应,这样就能大大提高效率

 

因为HTTP协议是一个请求-响应协议,客户端在发送了一个HTTP请求后,必须等待服务器响应后,才能 发送下一个请求,这样一来,如果某个响应太慢,它就会堵住后面的请求。

所以,为了进一步提速,HTTP/2.0允许客户端在没有收到响应的时候,发送多个HTTP请求,服务器返 回响应的时候,不一定按顺序返回,只要双方能识别出哪个响应对应哪个请求,就可以做到并行发送和 接收: 

可见,HTTP/2.0进一步提高了效率。

HTTP编程

既然HTTP涉及到客户端和服务器端,和TCP类似,我们也需要针对客户端编程和针对服务器端编程。

本节我们不讨论服务器端的HTTP编程,因为服务器端的HTTP编程本质上就是编写Web服务器,这是一 个非常复杂的体系,也是JavaEE开发的核心内容,我们在后面的章节再仔细研究。

本节我们只讨论作为客户端的HTTP编程。

因为浏览器也是一种HTTP客户端,所以,客户端的HTTP编程,它的行为本质上和浏览器是一样的,即 发送一个HTTP请求,接收服务器响应后,获得响应内容。只不过浏览器进一步把响应内容解析后渲染并 展示给了用户,而我们使用Java进行HTTP客户端编程仅限于获得响应内容。

我们来看一下Java如果使用HTTP客户端编程。Java标准库提供了基于HTTP的包,但是要注意,早期的 JDK版本是通过 HttpURLConnection 访问

HTTP,典型代码如下:

public static void main(String[] args) {
    HttpURLConnection urlConnection = null;
    InputStream inputStream = null;
    FileOutputStream out = null;
    try {
        URL url = new URL("http://localhost:8080/jspdemo01_war_exploded/test.bmp");
        urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.connect();
        inputStream = urlConnection.getInputStream();
        int i ;
        out = new FileOutputStream("bak1.png");
        while((i=inputStream.read())!=-1){
            out.write(i);
        }
        out.close();
        inputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值