Java笔记 - 网络编程

基本知识

OSI与TCP/IP参考模型

这里写图片描述
1. 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后再转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫比特
2. 数据链路层:主要将从物理层接受的数据进行MAC地址(网卡地址,网卡可以配置IP地址和物理地址。物理地址就是网卡出厂是带着一个编号,这个编号全球唯一。)的封装与解封装。常把这一层的数据叫做。在这一层工作的设备是交换机,数据通过交换机来传输。
3. 网络层:主要将从下层接收到的数据进行IP地址(例192.168.0.1)的封装与解封装。这一层封装完就知道了我们的数据到底要发向哪一台主机。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。交换机实现了互联,路由器实现了数据包方向的定义。
4. 传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。主要是将从下层接收的数据按照传输协议进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。
5. 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起回话或者接受会话请求(设备之间需要互相认识,可以是IP也可以是MAC或者是主机名)
6. 表示层:主要是进行对接收的数据进行解释、加密、与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够是别的东西(如图片、声音等))。明确到底是什么类型的数据。
7. 应用层:主要是一些终端的应用,比如FTP(各种文件下载),WEB(IE浏览),QQ之类的(可以把它理解成我们在电脑屏幕上可以看到的东西,就是终端应用)。

当我们进行网络传输的时候,数据在源处从应用层到物理层,这个过程叫封包。然后数据通过网络传输到目的地,在目的地处数据从物理层到应用层,这个过程叫拆包。这样就完成了数据在网络的传输。

网络通讯要素

IP地址

如果两台电脑想要进行通讯,就必须要先找到对方。想要找到对方,就要给每台计算机一个标识,类似于电话号码,这就是IP地址。
IP地址是一个32位的二进制数,通常被分割成4个8位,就是4个字节,每个字节的范围都是0~255。所以IP地址的数量是有限制的,这种4个字节来表示的IP地址叫做IPv4,它已经在2013年被全部分配完毕。后期便出现了IPv6版本的IP地址,扩大了地址空间。
IP地址分为A、B、C、D、E5类,它们适用的类型分别为:大型网络;中型网络;小型网络;多目地址;备用。常用的是B和C两类。
其中A、B、C类中又有一部分IP地址被单独列出来,专门为科研等领域使用,这种IP地址类型叫做私有地址。

类别最大网络数IP地址范围最大主机数私有IP地址范围
A126(2^7-2)0.0.0.0-127.255.255.2551677721410.0.0.0-10.255.255.255
B16384(2^14)128.0.0.0-191.255.255.25565534172.16.0.0-172.31.255.255
C2097152(2^21)192.0.0.0-223.255.255.255254192.168.0.0-192.168.255.255

广播地址:是专门用于同时向网络中所有工作站进行发送的一个地址。255就是广播地址。
在IP地址段走C类地址的话,子网掩码是255.255.255.0得话,IP地址的前三位就是网络位,第四位是IP地址位,0~255,0不能用,0代表的网络位就是192.168.1.0网段。IP地址从1有效到254,255不是IP地址,255是广播地址。就是说如果发到192.168.1.255上就是发给192.168.1网段所有存活的机器上

本地回环地址:不属于任何一个有类别地址类。它代表设备的本地虚拟接口,所以默认被看作是永远不会宕掉的接口。在windows操作系统中也有相似的定义,所以通常在不安装网卡前就可以ping通这个本地回环地址。一般都会用来检查本地网络协议、基本数据接口等是否正常的。

因为IP地址不容易记忆,根据实际情况给电脑起一个名字,就是
主机名,方便记忆。IP地址和主机名都是一台计算机的标识
localhost:本地主机

端口号

如果想要从一台电脑A的QQ发消息给电脑B的QQ,首先要通过B的IP地址(假设IP地址是192.168.1.1)找到这台电脑,然后电脑A的QQ发送数据,电脑B收到数据后应该解析数据,解析是靠应用程序来解析,但是电脑B上有许多应用程序,怎么分别到底找哪个程序来解析?这就需要端口号了,它给每个程序都加上一个数字标识,比如QQ这个软件的标识是4000,所以电脑A的QQ发消息时就是发到192.168.1.1地址的4000应用程序上,标识为192.168.1.1:4000。
有效端口范围:0~65535,其中0~1024是系统使用或保留端口,一般不占用。

传输协议

在我们进行数据传输的过程中,要按照什么样的方式进行数据的传递?这就是传输协议,数据传输的规则。
UDP数据报文协议
1. 将数据及源和目的封装成数据包中,然后直接丢向目的地,不需要建立连接
2. 每个数据报的大小在限制在64k内
3. 因无连接,是不可靠协议。数据包已经丢掉,但是对方有可能并没有收到。
4. 不需要建立连接,速度快
TCP传输控制协议
1. 建立连接,形成传输数据的通道。如果对方不在则不进行数据传输
2. 在连接中进行大数据量传输
3. 通过三次握手完成连接,是可靠协议
4. 必须建立连接,效率会稍低

IP地址

IP地址的内容比较复杂,并且还有与之相对应的主机名,所以在Java中IP地址被封装成了对象。
类InetAddress表示互联网协议(IP)地址。有两个子类Inet4Address(IPv4)和Inet6Address(IPv6),其中IPv6是16进制的。
它处于参考模型的网际层。

InetAddress ip1 = InetAddress.getLocalHost();
System.out.println("name="+ip1.getHostName());
System.out.println("IP="+ip1.getHostAddress());

InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println("百度IP="+ip1.getHostAddress());

输出结果:

name=huhao-PC
IP=192.168.1.102
百度IP=192.168.1.102

获取InetAddress对象的方法有许多。其中getByName方法可以通过主机名或者IP地址来获取IP地址对象。所以可以通过这个方法来获得百度、新浪等网站主机的IP地址,不过这些网站有的IP地址不唯一,因为他们是服务器集群。

域名解析
如果我们想访问某一个网站的话,无非是网站的一台计算机上存储了新浪的网页数据,而这个主机一定有一个IP地址,所以我们想访问它的主机的话就要访问这个IP地址,直接在浏览器输入IP地址就能访问了。但是互联网上的IP地址非常多,我们不可能记住每一个IP地址,而想要确定一个计算机的地址,除了使用IP地址之外还可以使用主机名。所以我们只要记住这个主机名就可以了。

IP地址和主机名就产生了对应关系,在互联网上有许多公共服务器,在他们上面就存放着这些IP地址和主机名的对应关系,如果我们在浏览器输入www.sina.com一回车,就会先去这些公共服务器上的列表内寻找IP地址和主机名的对应关系,然后获取IP地址后再通过IP地址来连接新浪主机,这就是域名解析

这些公共服务器就是DNS:域名解析服务器

如果我们不在网卡设置中指定DNS,那么宽带服务商会帮我们指定一个DNS,我们的信息会发给宽带服务商,然后他帮我们转到DNS上。

如果想要提高解析速度,我们可以本地解析,可以在本机创建一张域名解析列表,其实不是我们创建的,计算机内本身就有,我们可以在里面自己配置一些域名解析列表。就是hosts文件。这样解析速度会快,因为域名解析的时候最先走的不是域名解析服务器,而是本地hosts文件。

传输协议

UDP传输协议

如果要进行网络传输,就必须要有两个端点,每个端点存在后如果想要进行通讯就要有一个Socket,可以把Socket理解成通讯的两端,数据在Socket之间进行传输。
Socket:套接字。套接字是两台机器间通信的端点。

UDP是一种传输协议,我们没有办法直接对其进行操作,于是Java建立了一些对象,对UDP进行了封装,方便我们操作。UDP传输协议对应的端点服务对象就是DatagramSocket类。它表示用来发送和接收数据报包的套接字。它既可以发送也可以接收。发送和接收的都是数据报包,数据报包中有多信息,所以Java把数据报包也封装成了对象,就是DatagramPacket类。数据报包用来实现无连接的包投递服务。

数据报包在构造的时候是有区别的,因为DatagramSocket既可以发送也可以接收,所以有一下数据报包是用来发送的,有一些是用来接收,因为发送是需要目的地的,所以只要构造函数参数中包含IP地址对象,就是发送包。

//发送
public class UDPSendDemo {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket();

        byte[] buf = "发送端on".getBytes();
        DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 10000);
        ds.send(dp);
        System.out.println("发送成功");

        ds.close();
    }
}
//接收
public class UDPRecDemo {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket(10000);

        byte[] buf = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buf, buf.length);
        ds.receive(dp);

        String ip = dp.getAddress().getHostAddress();
        int port = dp.getPort();
        String text = new String(dp.getData(),0,dp.getLength());

        System.out.println(ip+":"+port+":"+text);

        ds.close();
    }
}

输出结果:

//发送端
发送成功
//接收端
192.168.1.102:64594:发送端on

这里的端口号是使用发送端的数据包中封装的方法获得IP地址对象,所以获得的是发送端的端口,不是接收端的端口,接收端的端口是10000,因为发送端没有指定端口,所以就随机了一个端口,发送端不用管是什么端口,只要把包的目的端口明确,目的地不发错,接收端也只负责接收发给我的数据,哪里发的不用管。

receive方法是阻塞式方法,如果发送端的数据没有发来,接收端会一直等待。

TCP传输协议

TCP协议的解释与使用

如果我们想使用TCP协议进行传输,就需要两个对象,Socket(客户端套接字)和ServerSocket(服务器套接字)。既然是客户端与服务端,所以客户端一般不止一个。
客户端创建流程:
1. 首先建立Socket对象,建立对象的时候构造函数可以使用IP对象+端口或IP地址的字符串+端口来明确要连接的主机(或者使用空参数构造函数,就是通过系统默认的SocketImpl来创建未连接的Socket对象,再使用connect方法来连接主机,connect方法的参数是一个IP地址和端口的封装)。当Socke对象创建完成,就代表着服务端与客户端连接建立成功,数据传输通道已建立。
2. 数据传输通道就是Socket流,它既有输入又有输出,因为是网络传输所以不只是文本,应该是字节流,分别通过Socket类的getInputStream和getOutputStream方法来获取。
3. 通过字节输出流向Socket流中写数据
4. 关闭资源

服务端创建流程:
1. 创建ServerSocket对象。
2. 服务端和客户端想要连接,服务端相当于开启了一个应用程序来提供服务,我们需要的只是这个服务,如果客户端想要连接到服务端,需要明确服务端的IP地址和这个应用程序的端口,所以服务端的ServerSocket对象需要通过构造函数对外暴露端口。
3. 如果我们想给客户端传输数据,查看ServerSocket类,发现它没有流。服务端其实并不需要流,因为服务端上面存的都是数据,我们只要能够把这些数据返回给客户端就行了,只要可以做到返回给客户端,他自己就不需要流。因为服务端只有一个,而客户端有可能会有多个, 当多个客户端同时连到主机发送数据的时候,主机为了明确A客户端的数据就是返回给A客户端而不是B客户端的,所以它如果想和A客户端进行通信,就会取得A客户端上的socket流,用A的流来和A通信,这样肯定就不会乱了。所以需要获取A的socket对象。
4. 通过A的socket对象获取它的输入输出流,使用A客户端的读取流读取客户端发来的数据,使用A客户端的输出流发送给A客户端数据
5. 关闭资源。

示例1:客户端与服务端之间进行数据传输

//先运行服务端再运行客户端
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("192.168.1.100",10004);
        OutputStream out = socket.getOutputStream();

        out.write("客户端发送的数据".getBytes());
        System.out.println("客户端已发送数据");
        socket.shutdownOutput();//告诉服务端,客户端已经写完了

        //读取服务端的返回数据
        InputStream in = socket.getInputStream();

        byte[] buf = new byte[1024];
        int len = 0;
        while((len = in.read(buf))!=-1){
            System.out.println(new String(buf, 0, len));
        }

        socket.close();
    }
}

public class ServerDemo {
    public static void main(String[] args) throws IOException { 
        ServerSocket ss = new ServerSocket(10004);
        Socket s = ss.accept();
        InputStream in = s.getInputStream();

        String ip = s.getInetAddress().getHostAddress();
        byte[] buf = new byte[1024];
        int len = 0;

        while((len = in.read(buf))!=-1){
            String text = new String(buf, 0, len);
            System.out.println(ip+":"+text);
        }

        //服务器给客户端返回值
        OutputStream out = s.getOutputStream();
        out.write("服务器返回的数据".getBytes());
        System.out.println("服务器已返回数据");
        s.close();
        ss.close();
    }
}

输出结果:

//客户端
客户端已发送数据
服务器返回的数据
//服务端
192.168.1.100:客户端发送的数据
服务器已返回数据

示例2:客户端输入字母,发给服务端,服务端变成大写返回给客户端

//客户端
public class TransClient {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("192.168.1.100",10005);

        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

        BufferedReader bufrIn = new BufferedReader(new InputStreamReader(s.getInputStream()));

        String line = null;
        while((line = bufr.readLine())!=null){
            if(line.equals("over"))
                break;
            out.write(line+LINE_SEPARATOR);
            out.flush();
            System.out.println("客户端输入:"+line);
            //读取服务端返回的数据
            String text = bufrIn.readLine();
            System.out.println("客户端收到:"+text);
        }

        bufr.close();
        s.close();
    }
}

//服务端
public class TransServer {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10005);
        Socket s = ss.accept();

        BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));

        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

        String line = null;
        while((line = in.readLine())!=null){
            System.out.println("服务端收到:"+line);
            String str = line.toUpperCase();
            out.write(str+LINE_SEPARATOR);
            out.flush();
            System.out.println("服务端发出:"+str);
        }

        s.close();
        ss.close();
    }
}

注意:
1. 客户端输入over后服务端也结束了,因为客户端输入over后while循环break,然后执行s.close方法,就在socket流中植入了结束标记,所以服务端的readLine方法就读到了-1,返回null,于是服务端程序也就结束了
2. 如果程序发生了等待,那一定是有阻塞式方法没有结束造成的。有时是因为数据写到缓冲区之后没有刷新出去,所以对方读不到造成等待,这种就要加刷新。或者是对readLine方法来说,读到换行标记才算是读完了一行,也就是说加换行就可以了。

示例3:客户端从文本文件中读取,然后发送给服务端,服务端变成大写之后保存到另一个文本文件中,也就是上传。

//客户端
public class UploadClient {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    public static void main(String[] args) throws IOException {
        File file = new File("E:\\upload.txt");

        BufferedReader bufr = new BufferedReader(new FileReader(file));

        Socket s = new Socket("192.168.1.100",10006);
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

        String line = null;
        while((line = bufr.readLine())!=null){
            out.write(line+LINE_SEPARATOR);
            out.flush();
        }
        //告诉服务端已经发送完了
        s.shutdownOutput();
        //接收服务端的返回内容
        BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String str = in.readLine();
        System.out.println(str);

        bufr.close();
        s.close();
    }
}

//服务端
public class UploadServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10006);
        Socket s = ss.accept();
        BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));

        File file = new File("E:\\upload1.txt");
        BufferedWriter bufw = new BufferedWriter(new FileWriter(file));

        String line = null;
        while((line = in.readLine())!=null){

            String str = line.toUpperCase();
            bufw.write(str);
            bufw.newLine();
            bufw.flush();
        }
        //将上传结果返回给客户端
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        out.write("上传成功");
        out.flush();

        s.close();
        ss.close();
        bufw.close();
    }
}

注意:当客户端的循环结束后,服务端的循环没有结束,因为服务端读的是socket流,发来一句就度一句,然后写到缓冲区,当客户端读完后,客户端readLine返回null,客户端知道内容已经读完了,但是服务端不知道,所以服务端的循环就一直在等待了。这时候就要告诉服务端已经读完了,就是用shutdownOutput方法。

如果服务端把数据写到缓冲区没有刷新,服务端会有数据,大小是8k,这是缓冲区BufferedWriter默认大小,装满后就不再装了,然后会内容刷新出去,如果第二次没有装满,就不会自动刷新,所以即使不加刷新,服务端也会有数据,但是数据大小不对,缺内容。

示例4:服务端多线程技术
如果我们想上传一张图片,按照示例3的代码来写就会产生一个问题,同时只能有一个客户端上传,当一个服务区获取到一个客户端的socket之后其他的客户端就不能连上来了,只有处理完这个客户端之后才能继续连接其他的客户端。要解决这个问题就要用到多线程。

//客户端
public class UploadPicClient {
    public static void main(String[] args) throws IOException{
        Socket s = new Socket("192.168.1.100",10007);

        FileInputStream fis = new FileInputStream("E:\\0.jpg");
        OutputStream out = s.getOutputStream();

        byte[] buf = new byte[1024];
        int len = 0;

        while((len = fis.read(buf))!=-1){   
            out.write(buf, 0, len);
            out.flush();
        }
        //告诉服务器数据已写完
        s.shutdownOutput();

        //读取服务器返回的数据
        InputStream in = s.getInputStream();
        byte[] bufIn = new byte[1024];
        int lenIn = 0;
        while((lenIn = in.read(bufIn))!= -1){   
            System.out.println(new String(bufIn,0,lenIn));
        }

        s.close();
        fis.close();
    }
}

//服务端
public class UploadPicServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10007);

        while(true){    
            Socket s = ss.accept();
            new Thread(new UploadTask(s)).start();
        }   
    }
}

//封装线程任务
public class UploadTask implements Runnable {
    private Socket s;

    public UploadTask(Socket s) {
        super();
        this.s = s;
    }

    public void run() {
        String ip = s.getInetAddress().getHostAddress();
        System.out.println(ip+".....connect");

        try {
            int count = 1;
            InputStream in = s.getInputStream();
            //把上传的图片都保存在一个文件夹中,如果不存在则创建
            File dir = new File("e:\\pic");
            if(!dir.exists()){
                dir.mkdirs();
            }
            //如果多次上传同名文件,则文件名后加括号数字
            File file = new File(dir,ip+".jpg");
            while(file.exists()){
                file = new File(dir, ip+"("+count+")"+".jpg");
                count++;
            }

            FileOutputStream fos = new FileOutputStream(file);

            byte[] buf = new byte[1024];
            int len = 0;
            while((len = in.read(buf))!=-1){

                fos.write(buf, 0, len);
            }

            //服务器收到后给客户端返回
            OutputStream out = s.getOutputStream();
            out.write("服务器已收到".getBytes());
            //服务端不用关,因为在不断循环接收客户端的连接
            s.close();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

常见客户端与服务端

我们最常用的客户端就是IE浏览器,服务端就是Tomcat。
我们平时使用浏览器的时候是在浏览器地址栏输入网页地址,比如输入地址http://www.sina.com.cn/index.html,回车后会先通过DNS获取到服务器的IP地址,然后通过IP地址访问服务器,服务器装有提供web服务的软件比如Tomcat,如果没有指定端口的话, 默认的WEB服务都是80端口,但Tomcat默认的端口是8080端口。这样客户端就找到了服务器上提供服务的应用程序,就可以开始进行数据传输了。

因为客户端IE浏览器和服务端提供服务的软件Tomcat是不同的厂商制作的,如果他们之间想要进行数据的通讯,就要遵循同一个规则,就是http超文本传输协议,它是应用层的一种通讯协议,它使用html超文本标记语言。它定义了web浏览器和web服务端的通讯规则,浏览器和服务器两端都要遵循这个协议,web服务器对外提供了超文本数据,浏览器解析超文本数据。客户端按照http协议进行主机的访问,所以客户端根据http协议要求的内容和格式开始发送请求消息,这些请求消息就是请求报文

当客户端上发来请求的数据后,Tomcat就是服务器处理请求和应答的应用程序。Tomcat服务器对外提供了接口,我们按照他的接口规则间接或直接实现就可以,这个接口就是Servlet接口

服务端如果有数据,就会开始读取数据并把应答数据根据http协议发给客户端的浏览器,这里的应答数据就是响应报文。浏览器本身已经内置了能够解析http协议的解析程序,我们称之为解析引擎。所以就开始解析,然后我们就看到了网页上的内容。浏览器与服务器底层数据传输用的就是Socket技术。

请求报文

如果我们想查看客户端浏览器在向服务器发送的具体内容,我们可以自己做一个Tomcat服务器来接收浏览器发送的数据

public class MyTomcat {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10008);

        Socket s = ss.accept();

        String ip = s.getInetAddress().getHostAddress();
        System.out.println(ip+"....connect");

        InputStream in = s.getInputStream();

        byte[] buf = new byte[1024];
        int len = in.read(buf);

        System.out.println(new String(buf, 0, len));

        //给客户端一个反馈
        PrintWriter out = new PrintWriter(s.getOutputStream(),true);//记得刷新
        out.println("欢迎光临");

        ss.close();
        s.close();
    }
}

运行程序,在IE浏览器访问http://192.168.1.100:10008,输出结果:

192.168.1.110....connect
GET / HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Win64; x64; Trident/4.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
UA-CPU: AMD64
Accept-Encoding: gzip, deflate
Host: 192.168.1.110:10008
Connection: Keep-Alive

这里写图片描述

请求报文主要分成三部分,请求行、请求头部和请求数据。
请求行:请求方法有GET和POST两种。请求方法后面有/myweb/1.html请求的资源路径,再后面是协议版本,http主要有两个版本1.0和1.1。
请求消息头:分为属性名:属性值,就是键值对。它的存在是为了在遵守http协议的基础上告诉服务器一些信息。
Accept:客户端可识别的内容类型列表。
Accept-Language:支持的语言
Accept-Encoding:支持的压缩方式,如果一个网页的内容较多,就可以先在服务器端压缩,然后发送到客户端再解压缩。
User-Agent:一些系统信息
Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。
Connection:Keep-Alive保持存活
请求体:如果我们想向服务器发送数据的时候带着自己的东西,就要卸载请求体。请求体和请求头必须分开,中间有一个空行。

响应报文

当Tomcat服务器收到客户端浏览器发来的请求后,会给出响应,如果我们想知道响应的具体内容,可以自己做一个浏览器应用程序来查看Tomcat返回的信息。

public class MyBrowser {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("192.168.1.110",8080);
        PrintWriter out = new PrintWriter(s.getOutputStream(),true);

        out.println("GET /myweb/1.html HTTP/1.1");
        out.println("Accept: */*");
        out.println("Accept-Language: zh-CN");
        out.println("Accept-Encoding: gzip, deflate");
        out.println("Host: 192.168.1.110:10008");
        out.println("Connection: close");
        out.println();
        out.println();

        InputStream in = s.getInputStream();
        byte[] buf = new byte[1024*1024];
        int len = in.read(buf);

        String str =new String(buf,0,len);
        System.out.println(str);

        s.close();
    }
}

运行Tomcat服务器,输出结果:

HTTP/1.1 200 OK
Accept-Ranges: bytes
ETag: W/"10-1499136276522"
Last-Modified: Tue, 04 Jul 2017 02:44:36 GMT
Content-Type: text/html
Content-Length: 10
Date: Tue, 04 Jul 2017 06:30:02 GMT
Connection: close

<html>
    <head>
        <title>这是我的网页</title>
    </head>

    <body>

        <h1>欢迎光临</h1>

        <font size='5' color="red">这是一个tomcat服务器中的资源。是一个html网页。</font>
    </body>


</html>

注意:向Tomcat服务器写入内容后面有两个空行,第一个空行是请求消息头和请求体中间的空行,第二个空行是请求体
服务端发回的是应答消息,分为应答行、应答消息头,就是属性名:属性值的键值对和应答体。
应答行:应答行的内容包括http的协议版本,应答状态码,应答状态描述信息。这些内容和浏览器服务器都没什么关系,是http协议规定的内容。
状态代码由三位数字组成,第一个数字定义了响应的类别,且有五种可能取值。
1xx:指示信息–表示请求已接收,继续处理。
2xx:成功–表示请求已被成功接收、理解、接受。
3xx:重定向–要完成请求必须进行更进一步的操作。
4xx:客户端错误–请求有语法错误或请求无法实现。
5xx:服务器端错误–服务器未能实现合法的请求。

常见状态代码、状态描述的说明如下。
200 OK:客户端请求成功。
400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。
403 Forbidden:服务器收到请求,但是拒绝提供服务。
404 Not Found:请求资源不存在,举个例子:输入了错误的URL。
500 Internal Server Error:服务器发生不可预期的错误。
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
应答消息:一些必要的属性信息
应答体:网页中的具体内容

URL和URLConnection

URL和URI
我们在浏览器中查看源文件是只有应答体的,但是我们自己做的浏览器myBrowser却显示全部应答报文。这是因为当服务器根据http协议返回应答消息后,浏览器由于是内置了http解析引擎的,所以他就把应答体解析出来然后显示在我们面前。而我们现在用的是自己写的浏览器MyBrowser,它没有解析引擎,所以就把应答报文全部显示出来了。所以我们需要一个可以解析http的东西。

我们以前操作的Socket、DatagramSocket都是在传输层,而http协议是在应用层。我们在浏览器输入网页地址的地址是一个很复杂的东西,于是Java就帮我们封装成了对象,这就是java.net包中的URL类和URI类。

URL:统一资源定位符。定位就是指能根据这个符号定位到网络上的一个资源(可以是网页资源文件或目录)。这里说的符号不是指单一符号,而是指http://192.168.1.100:8080/myweb/1.html这一串符号。所以URL里面是包含协议的

URI:统一资源标识符。凡是能标识统一资源的符号都是URI。URI 是统一资源标识符,而 URL 是统一资源定位符。因此,笼统地说,每个 URL 都是 URI,但不一定每个 URI 都是 URL。这是因为 URI 还包括一个子类,即统一资源名称 (URN),它命名资源但不指定如何定位资源。

String str_url = "http://192.168.1.110:8080/myweb/1.html?name=lisi";    
URL url = new URL(str_url);

System.out.println("getProtocol:"+url.getProtocol());
System.out.println("getHost:"+url.getHost());
System.out.println("getPort:"+url.getPort());
System.out.println("getFile:"+url.getFile());
System.out.println("getPath:"+url.getPath());
System.out.println("getQuery:"+url.getQuery());

输出结果:

getProtocol:http
getHost:192.168.1.110
getPort:8080
getFile:/myweb/1.html?name=lisi
getPath:/myweb/1.html
getQuery:name=lisi

?name=lisi是URL的查询部分Query,就是URL的参数信息,如果不加这部分,File和Path是一样的,但如果加上参数信息,文件会负责到参数,但是路径只负责到文件。

如果我们想只获取应答体,就要使用URL的openStream方法,打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。
它可以按照地址连出去,返回连接后的读取流。连上之后说明Socket流就有了,然后拿到读取流,就可以读取资源的数据

String str_url = "http://192.168.1.110:8080/myweb/1.html?name=lisi";
URL url = new URL(str_url);

InputStream in = url.openStream();
byte[] buf = new byte[1024];
int len = in.read(buf);

String text = new String(buf,0,len);
System.out.println(text);

in.close();

再运行就只拿到了应答体,因为URL帮我们按照http协议解析了

解析原理
其实openStream方法调用的是openConnection().getInputStream()
openConnection()返回的是URLConnection,URL连接器对象,它能连接到指定的统一资源定位符指向的资源上,将连接封装成了对象。如果我们想知道连接器中的内容,可以打印对象进行查看

String str_url = "http://192.168.1.110:8080/myweb/1.html?name=lisi";
URL url = new URL(str_url);
URLConnection conn = url.openConnection();
System.out.println(conn);

输出结果:

sun.net.www.protocol.http.HttpURLConnection:http://192.168.1.110:8080/myweb/1.html?name=lisi

http协议包中的HttpURLConnection,这个包是sun公司给我们提供的软件包,是一种底层实现,它帮我们封装了http的解析方式。URLConnection conn这个对象是通过openConnection()方法获取到的,我们new不了,包没有对外提供,这个对象就可以解析Tomcat服务器发回来的数据。

它还有方法能获取到InputStream和OutputStream,能拿这两个流的只有Socket。其实URL连接器对象就是用的就是socket流,再加上协议。

所以InputStream in = url.openStream();的原理其实是

URLConnection conn = url.openConnection();
InputStream in = conn.getInputStream();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值