【Java基础】11.网络编程

11.1 网络编程内容

11.1.1 软件结构

C/S结构:用户/服务器结构。例如QQ等

B/S结构:浏览器/服务器结构。

11.1.2 网络通信协议

  • 网络通信协议:通过计算机网络使多台计算机实现连接,连接通信时需要遵守一定的规则。
  • TCP/IP协议:传输控制协议/因特网互联协议,是最基本最广泛的协议。采用四层分层模型。

11.1.3 协议分类

  • UDP: User Datagram Protocol 用户数据报协议。【无连接通信】消耗资源少,通信效率高。不能保证数据完整性,在传输重要数据时不建议使用UDP协议。举例:广播电台,QQ的聊天、视频等功能。发送的数据限制在64kb以内。
  • TCP: Transmission Control Protocol 传输控制协议。是【面向连接】的通信协议,是无差错的可靠数据传输。
  • 三次握手
  • 第一次握手:客户端向服务器发出请求,等待确认
  • 第二次握手:服务器端向客户端发送响应
  • 第三次握手:客户端再次向服务器发送确认请求,确认后完成连接

11.1.4 网络编程三要素

1.协议

网络通信必须遵守的规则

2.IP地址

相当于电话号码

IPv4: 4的意思是4个字节,故总共32位二进制数。常用4段10进制表示法

IPv6: 由于互联网的发展,IP地址需求量越来越大,为了扩大地址空间,采用128位地址长度。每16个字节一组,8段16进制表示。

特殊的IP地址:

本机IP地址:127.0.0.1 或者 localhost

3.端口号

端口号是一个逻辑端口,无法直接看到,可以使用软件查看。

当网络软件打开时,OS会为它分配一个随机端口号。

或者软件打开时向OS获取指定端口号

端口号由两个字节组成:0~65535之间

【注意】

  1. 1024之前的端口号不能使用,已经被OS分配掉了
  2. 网络软件的端口号不能重复

【打比方】IP地址是你的收货地址,端口号就是你家的门牌号。

【常用端口号】

  1. 80端口 网络端口
  2. 数据库 mysql : 3306 oracle :1521
  3. Tomcat服务器:8080

11.2 TCP通信程序

11.2.1 概述

TCP通信是【面向连接】的通信,客户端和服务器端必须经过3次握手才能建立逻辑连接,建立通信。

两端通信时的步骤:

  1. 服务器端程序:先启动,但不会主动请求客户端。
  2. 客户端程序:先向客户端发出请求,与服务器端建立逻辑连接,这个连接包含一个IO对象。
  3. 两端利用IO对象进行通信,数据不仅是字符,所以IO对象是字节流对象

【两端进行一次数据交互】需要【4个IO流对象】写读写读

服务器端必须明确两件事

  1. 多客户端同时与服务器交互,服务器必须明确和谁在交互。(accept方法)
  2. 多客户端与服务器进行交互,需要多个IO流对象。但服务器没有IO流,但可以利用server.accept方法抓取客户端的IO流对象,从而跟对应客户端完成交互。【抓别人的,用别人的】

11.2.2 Socket类【客户端】

此类实现客户端套接字。也就是表示客户端的类。

TCP通信客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据。

【套接字】意思就是包含了IP地址和端口号的网络单位

构造方法:

public Socket(String host,int port)

host:服务器名称/服务器IP地址 port:端口号

成员方法:

public InputStream getInputStream()//返回此套接字的输入流。
public OutputStream getOutputStream()//返回此套接字的输出流。
public void close()//关闭该套接字

实现步骤

  1. 创建客户端对象,构造方法绑定服务器和端口号
  2. 使用socket对象方法getOutputStream获取网络字节输出流对象
  3. 使用字节输出流对象方法write,给服务器发送数据
  4. 使用Socket对象中方法gIS获取网络字节输入流对象
  5. 使用字节输入流对象方法read,读取服务器回写的数据
  6. 释放资源

【注意】

  1. 交互时,必须使用Socket提供的网络流,不能自己创建
  2. 当创建客户端对象时,会主动进行三次握手。若服务器此时没有启动,会抛出异常。

11.2.3 ServerSocket类【服务器】

TCP通信的服务器端:接受客户端的请求,读取客户端发送的数据,给客户端回写数据。

表示服务器的类:ServerSocket

构造方法:

public ServerSocket(int port)//创建绑定到特定端口的服务器套接字

port:端口号

服务器端必须明确:是哪个客户端请求的服务器,所以可以使用accept方法来获取给自己发送请求的客户端对象。

成员方法:

public Socket accept()//侦听并接受到此套接字的连接

服务器的实现步骤

  1. 创建服务器对象ServerSocket,向系统要指定端口号
  2. 使用对象方法accept,获取发送请求的客户端对象
  3. 使用socket对象方法getInputStream获取网络字节输入流对象
  4. 使用字节输入流对象方法read,读取客户端写的数据
  5. 使用Socket对象中方法gOS获取网络字节输出流对象
  6. 使用字节输入流对象方法writ e,写下回写给客户端的数据
  7. 释放资源 Socket,ServerSocket

11.3 文件上传案例

11.3.1 需求分析

客户端

原理:客户端读取本地文件,把文件上传到服务器,服务器把上传的文件保存到服务器的硬盘上。

  1. 客户端使用本地的字节输入流,读取要上传的文件
  2. 客户端使用网络字节输出流,把读取到的文件上传到服务器
  3. 服务器使用网络节输入流,读取客户端上传的文件
  4. 服务器使用本地字节输出流,把读取到的文件保存到服务器的硬盘上
  5. 服务器使用网络字节输出流,给客户端回写一个“上传成功”
  6. 客户端使用网络字节输入流,读取服务器回写的数据
  7. 释放资源

【注意】

  1. 客户端和服务器和本地硬盘进行读写,需要使用自己创建的字节流对象(本地流)
  2. 客户端和服务器之间进行读写,必须使用Socket中提供的字节流对象(网络流)

文件上传的底层原理:就是文件的复制。需要明确【数据源】和【数据目的地】

实现步骤:

  1. 创建一个本地字节输入流FIS对象,构造方法中绑定要读取的数据源
  2. 创建一个客户端Socket对象,构造方法中绑定服务器IP地址和端口号
  3. 使用Socket中的方法gOS,获取网络字节输出流对象
  4. 使用本地字节输入流FIS对象方法read,读取本地文件
  5. 使用网络字节输出流OS对象中的方法write,把读取的文件上传到服务器
  6. 使用Socket中方法gIS,获取网络字节输入流IS对象
  7. 使用网络字节输入流IS对象中方法read读取服务器回写的数据
  8. 释放资源FIS 和Socket

服务器端

文件上传案例的服务器端:读取客户端上传文件,保存到服务器硬盘,给客户端回写"上传成功

数据源:客户端上传的文件

目的地:服务器的硬盘e:\gc.txt

【实现步骤】

  1. 创建一个服务器SS对象,指定一个端口号
  2. 使用SS对象中accept方法,抓取客户端对象
  3. 使用Socket对象方法gIS方法获取网络字节输入流IS对象
  4. 判断存取目的地路径是否存在
  5. 创建一个本地字节输出流对象,构造方法中指定输出目的地
  6. 使用网络字节输入流IS对象方法read,读取客户端上传好的文件
  7. 使用本地字节输出流FOS对象方法write,写到硬盘上
  8. 使用Socket对象方法gOS获取到网络字节输出流OS对象
  9. 使用网络字节输出流对象OS对象方法write,给客户端回写“上传成功”
  10. 释放资源(FOS,socket,SS)

11.3.2 代码实现

【客户端】

package cn.itcast.day18.demo01;

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

/*
客户端实现步骤:
1. 创建一个本地字节输入流FIS对象,构造方法中绑定要读取的数据源
2. 创建一个客户端Socket对象,构造方法中绑定服务器IP地址和端口号
3. 使用Socket中的方法gOS,获取网络字节输出流对象
4. 使用本地字节输入流FIS对象方法read,读取本地文件
5. 使用网络字节输出流OS对象中的方法write,把读取的文件上传到服务器
6. 使用Socket中方法gIS,获取网络字节输入流IS对象
7. 使用网络字节输入流IS对象中方法read读取服务器回写的数据
8. 释放资源FIS 和Socket
 */
public class Demo01FileUploadClient {
    public static void main(String[] args) throws IOException {
        //1
        FileInputStream fis = new FileInputStream("D:\\GBK.txt");
        //2
        Socket socket = new Socket("127.0.0.1",8888);
        //3
        OutputStream oS = socket.getOutputStream();
        //4
        int len=0;
        byte[] bytes = new byte[1024];
        while ((len=fis.read(bytes))!=-1){
            //5
            oS.write(bytes,0,len);
        }
        //6
        InputStream iS = socket.getInputStream();
        //7
        while ((len=fis.read(bytes))!=-1){
            //5
            iS.read(bytes,0,len);
        }
        /*
        上传完文件,给服务器写一个结束标记
        shutdownOutput() 禁用此套接字的输出流
         */
        socket.shutdownOutput();
        //8
        fis.close();
        socket.close();



    }
}

【服务器端】

package cn.itcast.day18.demo01;

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

/*
    文件上传案例的服务器端:读取客户端上传文件,保存到服务器硬盘,给客户端回写"上传成功
【实现步骤】

1. 创建一个服务器SS对象,指定一个端口号
2. 使用SS对象中accept方法,抓取客户端对象
3. 使用Socket对象方法gIS方法获取网络字节输入流IS对象
4. 判断存取目的地路径是否存在
5. 创建一个本地字节输出流对象,构造方法中指定输出目的地
6. 使用网络字节输入流IS对象方法read,读取客户端上传好的文件
7. 使用本地字节输出流FOS对象方法write,写到硬盘上
8. 使用Socket对象方法gOS获取到网络字节输出流OS对象
9. 使用网络字节输出流对象OS对象方法write,给客户端回写“上传成功”
10. 释放资源(FOS,socket,SS)

 */
public class Demo02FileUploadServer {
    public static void main(String[] args) throws IOException {
        //1
        ServerSocket server = new ServerSocket(8888);
        //2
        Socket socket = server.accept();
        //3
        InputStream iS = socket.getInputStream();
        //4
        File file = new File("e:\\upload");
        if (!file.exists()){
            file.mkdirs();
        }
        //5
        FileOutputStream fOS = new FileOutputStream(file + "\\gc.txt");//
        //6
        int len=0;
        byte[] bytes =new byte[1024];
        while ((len=iS.read(bytes))!=-1){
            //7
            fOS.write(bytes,0,len);
        }
        //89
        OutputStream oS = socket.getOutputStream();
        oS.write("上传成功".getBytes());
        //10
        fOS.close();
        socket.close();
        server.close();


    }
}

11.3.3 代码优化

自定义一个文件命名规则:防止同名文件被覆盖
规则:域名+毫秒值+随机数

String fileName = "itcast"+System.currentTimeMillis()+ new Random().nextInt(1000000)+".txt";
FileOutputStream fOS = new FileOutputStream(file +"\\"+fileName);

让服务器一直处于监听状态

加入while死循环,不需要再关闭服务器close

为提高效率,死循环中捕捉到客户端后开启多线程

用try…catch来处理异常

11.4 B/S结构服务器

客户端:谷歌浏览器:http://127.0.0.1:8080/day18/web/index.html

服务器代码:

//创建一个服务器,指定端口号
ServerSocket server = new ServerSocket(8080);
//抓取客户端对象
Socket socket = server.accept();
//socket对象gIS获取网络字节输入流
InputStream iS = socket.getInputStream();
//使用网络字节输入流对象方法read读取客户端请求
byte[] bytes = new byte[1024];
int len =0;
while ((len=iS.read(bytes))!=-1){
    System.out.println(new String(bytes,0,len));
}

读取客户端的请求信息如下:

GET /day18/web/index.html HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8

接下来,服务器要给客户端回写一个信息,回写一个html页面

我们需要读取index.html文件,必须知道这个文件的地址。

而这个地址实际上就是我们读取客户端的请求信息中的第一行

GET /day18/web/index.html HTTP/1.1

我们可以使用BufferedReader中的方法:readLine来读取这第一行

new BufferedReader(new InputStreamReader(iS));//把网络字节输入流转换为字符缓冲输入流

我们只需要文件地址,可以使用String类中的方法split来切割字符串

切割标识为空格 split(" ")

那么array[1] 就是 /day18/web/index.html

再使用String类方法subString(1) ,获取html文件路径【去掉首个/字符】

day18/web/index.html

//写入HTTP协议响应头,固定写法

/* HTTP协议响应段代码 */

//写入空行,否则浏览器不解析

oS.write("\r\n".getBytes());

服务器将使用网络字节输出流把读取的文件,写到客户端(浏览器)显示