外围概述:在互联网时代,咱们所利用的大部分软件都是基于网络为根底的,那么软件之间是如何通过网络通信的呢?为什么咱们通过浏览器输出网址就能够看到网页?本篇咱们将会学习网络编程,让咱们可能从编程角度更底层的更好的了解软件之间的通信流程。

第一章:网络编程根底

1.1-软件结构(理解)

C/S构造

C/S构造 :全称为Client/Server构造,是指客户端和服务器构造。常见程序有QQ、迅雷等软件。

B/S构造

B/S构造:全称为Browser/Server构造,是指浏览器和服务器构造。常见浏览器有谷歌、火狐等。

两种架构各有劣势,然而无论哪种架构,都离不开网络的反对。网络编程,就是在肯定的协定下,实现两台计算机的通信的程序。

1.2-网络通信协定(理解)

网络通信协定:通信协议是对计算机必须恪守的规定,只有恪守这些规定,计算机之间能力进行通信。这就 好比在路线中行驶的汽车肯定要恪守交通规则一样,协定中对数据的传输格局、传输速率、传输步骤等做了对立规定,通信单方必须同时恪守,最终实现数据交换。

TCP/IP协定: 传输控制协议/因特网互联协定( Transmission Control Protocol/Internet Protocol),是Internet最根本、最宽泛的协定。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的规范。它的外部蕴含一系列的用于解决数据通信的协定,并采纳了4层的分层模型,每一层都呼叫它的下一层所提供的协定来实现本人的需要。

1.3-协定分类(理解)

通信的协定还是比较复杂的, java.net 包中蕴含的类和接口,它们提供低层次的通信细节。咱们能够间接应用这些类和接口,来专一于网络程序开发,而不必思考通信的细节。

TCP

TCP:传输控制协议 (Transmission Control Protocol)。TCP协定是面向连贯的通信协议,即传输数据之前,在发送端和接收端建设逻辑连贯,而后再传输数据,它提供了两台计算机之间牢靠无差错的数据传输。

三次握手:TCP协定中,在发送数据的筹备阶段,客户端与服务器之间的三次交互,以保障连贯的牢靠。

  1. 第一次握手,客户端向服务器端收回连贯申请,期待服务器确认。
  2. 第二次握手,服务器端向客户端回送一个响应,告诉客户端收到了连贯申请。
  3. 第三次握手,客户端再次向服务器端发送确认信息,确认连贯。整个交互过程如下图所示。

实现三次握手,连贯建设后,客户端和服务器就能够开始进行数据传输了。因为这种面向连贯的个性,TCP协定能够保障传输数据的平安,所以利用非常宽泛,例如下载文件、浏览网页等。

UDP

用户数据报协定(User Datagram Protocol)。UDP协定是一个面向无连贯的协定。传输数据时,不须要建设连贯,不论对方端服务是否启动,间接将数据、数据源和目的地都封装在数据包中,间接发送。每个数据包的大小限度在64k以内。它是不牢靠协定,因为无连贯,所以传输速度快,然而容易失落数据。日常利用中,例如视频会议、QQ聊天等。

1.4-网络编程三要素(理解)

协定

协定:计算机网络通信必须恪守的规定,曾经介绍过了,不再赘述。

IP地址

什么是IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做惟一的编号。如果咱们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。

IP地址的分类:

  • IPv4:是一个32位的二进制数,通常被分为4个字节,示意成 a.b.c.d 的模式,例如 192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多能够示意42亿个。
  • IPv6:因为互联网的蓬勃发展,IP地址的需求量愈来愈大,然而网络地址资源无限,使得IP的调配越发缓和。有材料显示,寰球IPv4地址在2011年2月调配结束。 为了扩充地址空间,拟通过IPv6从新定义地址空间,采纳128位地址长度,每16个字节一组,分成8组十六进 制数,示意成 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789 ,号称能够为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

常用命令

查看本机IP地址,在控制台输出: ipconfig

查看网络是否连通,在控制台输出:

ping 空格 IP地址ping 220.181.57.216【非凡的IP地址】本机IP地址: 127.0.0.1 、 localhost 。
端口号

网络的通信,实质上是两个过程(应用程序)的通信。每台计算机都有很多的过程,那么在网络通信时,如何辨别这些过程呢?

如果说IP地址能够惟一标识网络中的设施,那么端口号就能够惟一标识设施中的过程(应用程序)了。

端口号:用两个字节示意的整数,它的取值范畴是0-65535。其中,0~1023之间的端口号用于一些出名的网络服务和利用,一般的应用程序须要应用1024以上的端口号。如果端口号被另外一个服务或利用所占用,会导致以后程序启动失败。

利用 协定 + IP地址 + 端口号 三元组合,就能够标识网络中的过程了,那么过程间的通信就能够利用这个标识与其它过程进行交互。

第二章:TCP通信程序

2.1-TCP通信程序介绍(理解)

TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格辨别为客户端(Client)服务端(Server)

两端通信时步骤:
  1. 服务端程序,须要当时启动,期待客户端的连贯。
  2. 客户端被动连贯服务器端,连贯胜利能力通信。服务端不能够被动连贯客户端。
在Java中,提供了两个类用于实现TCP通信程序:
  • 客户端:java.net.Socket 类示意。创立Socket对象,向服务端收回连贯申请,服务端响应申请,两者建设连贯开始通信。
  • 服务端:java.net.ServerSocket 类示意。创立ServerSocket对象,相当于开启一个服务,并期待客户端的连贯。

2.2-Socket类(重要)

Socket 类:该类实现客户端套接字,套接字指的是两台设施之间通信的端点。

构造方法

public Socket(String host, int port) :创立套接字对象并将其连贯到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。

回送地址(127.x.x.x) 是本机回送地址(Loopback Address),次要用于网络软件测试以及本地机过程间通信,无论什么程序,一旦应用回送地址发送数据,立刻返回,不进行任何网络传输。

Socket client = new Socket("127.0.0.1", 6666);
成员办法
  • public InputStream getInputStream(): 返回此套接字的输出流。

    • 如果此Scoket具备相关联的通道,则生成的InputStream 的所有操作也关联该通道。
    • 敞开生成的InputStream也将敞开相干的Socket。
  • public OutputStream getOutputStream() : 返回此套接字的输入流。

    • 如果此Scoket具备相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
    • 敞开生成的OutputStream也将敞开相干的Socket。
  • public void close() :敞开此套接字。

    • 一旦一个socket被敞开,它不可再应用。
    • 敞开此socket也将敞开相干的InputStream和OutputStream 。
  • public void shutdownOutput() : 禁用此套接字的输入流。

    • 任何先前写出的数据将被发送,随后终止输入流

2.3-ServerSocket类(重要)

ServerSocket 类:这个类实现了服务器套接字,该对象期待通过网络的申请。

构造方法

构造方法:public ServerSocket(int port):应用该构造方法在创立ServerSocket对象时,就能够将其绑定到一个指 定的端口号上,参数port就是端口号。

ServerSocket server = new ServerSocket(6666);
成员办法

成员办法:public Socket accept():侦听并承受连贯,返回一个新的Socket对象,用于和客户端实现通信。该办法 会始终阻塞直到建设连贯。

2.4-简略的TCP网络程序(重要)

TCP通信剖析
  1. 【服务端】启动,创立ServerSocket对象,期待连贯。
  2. 【客户端】启动,创立Socket对象,申请连贯。
  3. 【服务端】接管连贯,调用accept办法,并返回一个Socket对象。
  4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
  5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
  6. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
  7. 【客户端】Scoket对象,获取InputStream,解析回写数据。
  8. 【客户端】开释资源,断开连接

客户端向服务端发送数据

客户端代码

public class ClientTCP {    public static void main(String[] args) throws Exception {        System.out.println("客户端 发送数据");        // 1.创立 Socket ( ip , port ) , 确定连贯到哪里.        Socket client = new Socket("localhost", 6666);        // 2.获取流对象 . 输入流        OutputStream os = client.getOutputStream();        // 3.写出数据.        os.write("你好么? tcp ,我来了".getBytes());        // 4. 敞开资源 .        os.close();        client.close();    }}

服务端代码

public class ServerTCP {    public static void main(String[] args) throws IOException {        System.out.println("服务端启动 , 期待连贯 .... ");        // 1.创立 ServerSocket对象,绑定端口,开始期待连贯        ServerSocket ss = new ServerSocket(6666);        // 2.接管连贯 accept 办法, 返回 socket 对象.        Socket server = ss.accept();        // 3.通过socket 获取输出流        InputStream is = server.getInputStream();        // 4.一次性读取数据          // 4.1 创立字节数组        byte[] b = new byte[1024];          // 4.2 据读取到字节数组中.        int len = is.read(b);        // 4.3 解析数组,打印字符串信息        String msg = new String(b, 0, len);        System.out.println(msg);        //5.敞开资源.        is.close();        server.close();    }}
服务端向客户端回写数据

服务端代码

public class ClientTCP {    public static void main(String[] args) throws Exception {        System.out.println("客户端 发送数据");        // 1.创立 Socket ( ip , port ) , 确定连贯到哪里.        Socket client = new Socket("localhost", 6666);        // 2.通过Scoket,获取输入流对象         OutputStream os = client.getOutputStream();        // 3.写出数据.        os.write("你好么? tcp ,我来了".getBytes());          // ==============解析回写=========================          // 4. 通过Scoket,获取 输出流对象          InputStream in = client.getInputStream();          // 5. 读取数据数据          byte[] b = new byte[100];          int len = in.read(b);          System.out.println(new String(b, 0, len));        // 6. 敞开资源 .          in.close();        os.close();        client.close();    }}

客户端代码

public class ServerTCP {    public static void main(String[] args) throws IOException {        System.out.println("服务端启动 , 期待连贯 .... ");        // 1.创立 ServerSocket对象,绑定端口,开始期待连贯        ServerSocket ss = new ServerSocket(6666);        // 2.接管连贯 accept 办法, 返回 socket 对象.        Socket server = ss.accept();        // 3.通过socket 获取输出流        InputStream is = server.getInputStream();        // 4.一次性读取数据          // 4.1 创立字节数组        byte[] b = new byte[1024];          // 4.2 据读取到字节数组中.        int len = is.read(b);        // 4.3 解析数组,打印字符串信息        String msg = new String(b, 0, len);        System.out.println(msg);          // =================回写数据=======================          // 5. 通过 socket 获取输入流           OutputStream out = server.getOutputStream();          // 6. 回写数据           out.write("我很好,谢谢你".getBytes());          // 7.敞开资源.          out.close();        is.close();        server.close();    }}

第三章:文件上传案例

3.1-文件上传剖析

  1. 【客户端】输出流,从硬盘读取文件数据到程序中。
  2. 【客户端】输入流,写出文件数据到服务端。
  3. 【服务端】输出流,读取文件数据到服务端程序。
  4. 【服务端】输入流,写出文件数据到服务器硬盘中。
  5. 【服务端】获取输入流,回写数据。
  6. 【客户端】获取输出流,解析回写数据。

3.2-客户端程序实现

public static void main(String[] args) throws IOException {    // 1.创立流对象    // 1.1 创立输出流,读取本地文件    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));    // 1.2 创立输入流,写到服务端    Socket socket = new Socket("localhost", 6666);    BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());    //2.写出数据.    byte[] b  = new byte[1024 * 8 ];    int len ;    while (( len  = bis.read(b))!=-1) {        bos.write(b, 0, len);    }    // 敞开输入流,告诉服务端,写出数据结束    socket.shutdownOutput();    System.out.println("文件发送结束");    // 3. =====解析回写============    InputStream in = socket.getInputStream();    byte[] back = new byte[20];    in.read(back);    System.out.println(new String(back));    in.close();        // 4.开释资源    socket.close();    bis.close();}

3.3-文件上传单线程服务器实现

public static void main(String[] args) throws IOException {        System.out.println("服务器 启动.....  ");        // 1. 创立服务端ServerSocket        ServerSocket serverSocket = new ServerSocket(6666);        // 2. 循环接管,建设连贯        Socket accept = serverSocket.accept();            /*             *3. socket对象进行读写操作               */        try {                //3.1 获取输出流对象                BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());                //3.2 创立输入流对象, 保留到本地 .                FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");                BufferedOutputStream bos = new BufferedOutputStream(fis);                // 3.3 读写数据                byte[] b = new byte[1024 * 8];                int len;                while ((len = bis.read(b)) != -1) {                    bos.write(b, 0, len);                }                // 4.=======信息回写===========================                System.out.println("back ........");                OutputStream out = accept.getOutputStream();                out.write("上传胜利".getBytes());                out.close();                //================================                //5. 敞开 资源                bos.close();                bis.close();                accept.close();                System.out.println("文件上传已保留");        } catch (IOException e) {            e.printStackTrace();        }    }}

3.4-文件上传多线程服务器实现

文件上传的案例中,服务器只能为客户端服务器一次,之后服务器端程序就会完结。而咱们必须做到让服务器程序不能完结,时时刻刻都要为客户端服务。而且同时能够为多个客户端提供服务器,做到一个客户端就要开启一个信新的线程。

public static void main(String[] args) throws IOException{    System.out.println("服务器 启动.....  ");    // 1. 创立服务端ServerSocket    ServerSocket serverSocket = new ServerSocket(6666);    // 2. 循环接管,建设连贯    while (true) {        Socket accept = serverSocket.accept();        /*        3. socket对象交给子线程解决,进行读写操作        Runnable接口中,只有一个run办法,应用lambda表达式简化格局        */        new Thread(() -> {            try{            //3.1 获取输出流对象            BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());            //3.2 创立输入流对象, 保留到本地 .            FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");            BufferedOutputStream bos = new BufferedOutputStream(fis);            // 3.3 读写数据            byte[] b = new byte[1024 * 8];            int len;            while ((len = bis.read(b)) != -1) {            bos.write(b, 0, len);            }            // 4.=======信息回写===========================            System.out.println("back ........");            OutputStream out = accept.getOutputStream();            out.write("上传胜利".getBytes());            out.close();            //================================            //5. 敞开 资源            bos.close();            bis.close();            accept.close();            System.out.println("文件上传已保留");            } catch (IOException e) {            e.printStackTrace();            }        }).start();    }}

3.5-文件上传服务器实现优化

频繁的创立线程会减少系统资源的开销,能够利用线程池进行再次优化。

public static void main(String[] args)  throws IOException{    System.out.println("服务器 启动.....  ");    ServerSocket serverSocket = new ServerSocket(6666);    //创立10个线程的线程池    ExecutorService executorService = Executors.newFixedThreadPool(10);        while (true) {        Socket accept = serverSocket.accept();        //提交线程执行的工作        executorService.submit(()->{            try{            BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());            FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");            BufferedOutputStream bos = new BufferedOutputStream(fis);            byte[] b = new byte[1024 * 8];            int len;            while ((len = bis.read(b)) != -1) {            bos.write(b, 0, len);            }            System.out.println("back ........");            OutputStream out = accept.getOutputStream();            out.write("上传胜利".getBytes());            out.close();            bos.close();            bis.close();            accept.close();            System.out.println("文件上传已保留");            } catch (IOException e) {                e.printStackTrace();            }        });    }}

第四章:模仿B/S服务器

模仿网站服务器,应用浏览器拜访本人编写的服务端程序,查看网页成果。

4.1-案例剖析

我的项目中有一个web我的项目,咱们写服务端代码,而后通过浏览器输出地址127.0.0.1:8888/web/index.html

  • 筹备页面数据,web文件夹。

    • 蕴含网页html文件
    • 蕴含图片
    • 蕴含css样式表
  • 咱们模仿服务器端,ServerSocket类监听端口,应用浏览器拜访,查看网页成果

4.2-HTTP协定

案例中需应用浏览器查看成果,浏览器和服务器之间是遵循HTTP协定的,咱们先对HTTP协定进行简略的介绍,在前期JavaWeb相干篇幅中再详解解释。

  • HTTP协定,称为超文本传输协定。
  • 规定了客户端浏览器和服务器之间的协定。
  • HTTP协定是TCP网络通信模型中应用层的协定。
  • 客户端浏览器被动向服务器发动申请,服务器收到后进行响应。
  • 客户端申请

    • 客户端在申请的信息的第一行中,携带了客户端想要申请的资源门路。
  • 服务器端响应

    • 响应中必须告知客户端响应的后果。
    • 200状态码示意响应胜利。
    • Content-Type:text/html 告知浏览器响应的内容是文本/网页内容。

4.3-代码实现

代码
public class WebSever {  public static void main(String[] args) throws IOException {    // 创立ServerSocket对象   ServerSocket server = new ServerSocket(8888);   while (true){     new Thread(new Runnable() {       @Override       public void run() {        try{          // 获取socket对象          Socket socket = server.accept();          // 读取接管的内容          BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));          String line = reader.readLine(); // GET /web/index.html HTTP/1.1          String path = line.split(" ")[1].substring(1);          // 创立本地字节输出流          FileInputStream fis = new FileInputStream("day09_Socket\\" +path);          // 创立网络输入流          OutputStream os = socket.getOutputStream();          // 写入HTTP协定响应头,固定写法          os.write("HTTP/1.1 200 OK\r\n".getBytes());          os.write("Content‐Type:text/html\r\n".getBytes());          // 必须要写入空行,否则浏览器不解析          os.write("\r\n".getBytes());          int len = 0;          byte[]bts = new byte[1024];          while((len=fis.read(bts))!=-1){            os.write(bts,0,len);          }          // 敞开资源          os.close();          fis.close();          reader.close();          socket.close();        } catch (IOException e){          e.printStackTrace();        }       }     }).start();   }  }}
成果

浏览器输出地址127.0.0.1:8888/web/index.html拜访