Java进阶06 网络编程

1 网络编程概述

  • 计算机网络:

    • 把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规 模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
  • 网络编程的目的:

    • 直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
  • 网络编程中有两个主要的问题

    • 如何准确地定位网络上一台或多台主机;//用IP地址
    • 定位主机上的特定的应用找到主机后如何可靠高效地进行数据传输 //通讯协议
  • Java网络编程

    • Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。
    • Java提供的网络类库,可以实现网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。

2 网络通信要素-3要素

2.1 IP地址

2.1.1 什么是IP地址

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

2.1.2 IP地址的分类

按照协议可分为IPv4和IPv6

IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,例如192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。

  • A类IP地址

    • 一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”,
    • 地址范围1.0.0.1-126.255.255.254
    • 二进制表示为:00000001 00000000 00000000 00000001 - 01111110 11111111 11111111 11111110
    • 可用的A类网络有126个,每个网络能容纳1677214个主机
  • B类IP地址

    • 一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,
    • 地址范围128.1.0.1-191.255.255.254
    • 二进制表示为:10000000 00000001 00000000 00000001 - 10111111 11111111 11111111 11111110
    • 可用的B类网络有16384个,每个网络能容纳65534主机
  • C类IP地址

    • 一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”
    • 范围192.0.1.1-223.255.255.254
    • 二进制表示为: 11000000 00000000 00000001 00000001-11011111 11111111 11111110 11111110
    • C类网络可达2097152个,每个网络能容纳254个主机
  • D类地址

    • 用于多点广播
    • D类IP地址第一个字节以“1110”开始,它是一个专门保留的地址。
    • 它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中
    • 多点广播地址用来一次寻址一组计算机 s 地址范围224.0.0.1-239.255.255.254
  • E类IP地址

    • 以“1111”开始,为将来使用保留
    • E类地址保留,仅作实验和开发用
  • 私有IP

    • 在这么多网络IP中,国际规定有一部分IP地址是用于我们的局域网使用,也就是属于私网IP,不在公网中使用的,它们的范围是:
10.0.0.0~10.255.255.255172.16.0.0~172.31.255.255192.168.0.0~192.168.255.255

注意

  • IP地址127.0.0.1~127.255.255.255用于回路测试,如:127.0.0.1可以代表本机IP地址(localhost),用http://127.0.0.1就可以测试本机中配置的Web服务器。

常用命令

  • 查看本机IP地址,在控制台输入:
ipconfig //一个网卡一个IP地址
  • 检查网络是否连通,在控制台输入:
ping 空格 IP地址
ping 220.181.57.216

IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

2.1.3 InetAdress

  • InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress实例

    • public static InetAddress getLocalHost()
    • public static InetAddress getByName(String host)
  • InetAddress提供了如下几个常用的方法

    • public String getHostAddress():返回 IP 地址字符串(以文本表现形式)。
    • public String getHostName():获取此 IP 地址的主机名
    • public boolean isReachable(int timeout):测试是否可以达到该地址

2.2 端口号

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

如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。

  • 端口号标识正在计算机上运行的进程(程序),不同的进程有不同的端口号
  • 用两个字节表示的整数,它的取值范围是0~65535。
  • 端口分类:

    • 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
    • 注册端口:1024~49151。分配给用户进程或应用程序。(著名端口 如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
    • 动态/私有端口:49152~65535。

查看端口

  • netstat -an查看端口状态

  • lsof -i [tcp/udp]:2425

2.3 协议

什么是协议

通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。

现在的生活中,不同的计算机只需要能够联网(有线无线都可以)那么就可以相互进行传递数据那么不同种类之间的计算机到底是怎么进行数据传递的呢?就像说不同语言的人沟通一样,只要有一种大家都认可都遵守的协议即可,那么这个计算机都遵守的网络通信协议叫做TCP/IP协议

TCP/IP协议簇

传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。

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

  • IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。

  • TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层。

上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。
链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
运输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。

TCP与UDP

通信的协议还是比较复杂的,Java提供了其中两个重要的协议,即传输控制协议TCP(Transmission Control Protocol)和用户数据报协议UDP(User Datagram Protocol)的实现,它们提供低层次的通信细节。我们可以直接使用这些协议的实现(类和接口,位于java.net 包中),来专注于网络程序开发,而不用考虑通信的细节。

  • UDP:用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。(eg:QQ群聊、朋友圈)

    由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

    但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。

特点:数据被限制在64kb以内,超出这个范围就不能发送了。

数据报(Datagram):网络传输的基本单位

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

    在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。

    • 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。

      • 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
      • 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
      • 第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。

​ 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。

3 TCP网络编程

3.1 概述

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

两端通信时步骤:

  1. 服务端程序,需要事先启动,等待客户端的连接。
  2. 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。

在Java中,提供了两个类用于实现TCP通信程序:

  1. 客户端java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
  2. 服务端java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。

3.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() : 禁用此套接字的输出流。
    • 任何先前写出的数据将被发送,随后终止输出流。

3.3 ServerSocket

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

构造方法

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

构造举例,代码如下:

ServerSocket server = new ServerSocket(6666);

成员方法

  • public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。

3.4 简单的TCP网络程序

TCP通信分析图解

  1. 【服务端】启动,创建ServerSocket对象,等待连接。
  2. 【客户端】启动,创建Socket对象,请求连接。
  3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
  4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
  5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。

到此,客户端向服务端发送数据成功。

自此,服务端向客户端回写数据。

  1. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
  2. 【客户端】Scoket对象,获取InputStream,解析回写数据。
  3. 【客户端】释放资源,断开连接。

客户端向服务器发送数据

服务端实现:

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.获取流对象 . 输出流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. 通过 socket 获取输出流OutputStream out = server.getOutputStream();// 6. 回写数据out.write("我很好,谢谢你".getBytes());// 7.关闭资源.out.close();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();}
}

4 UDP网络编程

4.1 概述

DatagramSocketDatagramPacket实现了基于 UDP 协议网络程序。

UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。

DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。

UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。如同发快递包裹一样。

4.2 DatagramSocket

DatagramSocket 类:此类表示用来发送接收数据报包的套接字。

构造方法

  • public DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。

  • public DatagramSocket(int port,InetAddress laddr):创建数据报套接字,将其绑定到指定的本地地址。

成员方法

  • public void close()关闭此数据报套接字。

  • public void send(DatagramPacket p)从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。

  • public void receive(DatagramPacket p)从此套接字接收数据报包。当此方法返回时,DatagramPacket的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。

  • public InetAddress getLocalAddress()获取套接字绑定的本地地址。

  • public int getLocalPort()返回此套接字绑定的本地主机上的端口号。

  • public InetAddress getInetAddress()返回此套接字连接的地址。如果套接字未连接,则返回 null。

  • public int getPort()返回此套接字的端口。如果套接字未连接,则返回-1。

4.3 DatagramPacket

DatagramPacket 类:此类表示数据报包。数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。

构造方法

  • public DatagramPacket(byte[] buf,int length)构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。

  • public DatagramPacket(byte[] buf,int length,InetAddress address,int port)构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length参数必须小于等于 buf.length。

成员方法

  • public InetAddress getAddress()返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。

  • public int getPort()返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。

  • public byte[] getData()返回数据缓冲区。接收到的或将要发送的数据从缓冲区中的偏移量 offset 处开始,持续 length 长度。

  • public int getLength()返回将要发送或接收到的数据的长度。

4.4 简单的UDP网络程序

UDP通信分析图解

  1. DatagramSocket与DatagramPacket

  2. 建立发送端,接收端

  3. 建立数据包

  4. 调用Socket的发送、接收方法

  5. 关闭Socket

发送端与接收端是两个独立的运行程序

建立UDP接收端

  1. 建立udp socket服务,因为是要接收数据,必须要明确一个端口号。

  2. 创建数据包,用于存储接收到的数据。方便用数据包对象的方法解析这些数据.

  3. 使用socket服务的receive方法将接收的数据存储到数据包中。

  4. 通过数据包的方法解析数据包中的数据。

  5. 关闭资源

public static void main(String[] args) throws IOException {System.out.println("接收端启动......");//1,建立udp socket服务。DatagramSocket ds = new DatagramSocket(10000);//2,创建数据包。byte[] buf = new byte[1024];DatagramPacket dp = new DatagramPacket(buf,buf.length);//3,使用接收方法将数据存储到数据包中。ds.receive(dp);//阻塞式的。//4,通过数据包对象的方法,解析其中的数据,比如,地址,端口,数据内容。String ip = dp.getAddress().getHostAddress();int port = dp.getPort();String text = new String(dp.getData(),0,dp.getLength());System.out.println(ip+":"+port+":"+text);//5,关闭资源。ds.close();}

创建UDP传输的发送端

  1. 建立udp的socket服务。
  2. 将要发送的数据封装到数据包中。
  3. 通过udp的socket服务将数据包发送出去。
  4. 关闭socket服务。
public static void main(String[] args) throws IOException {System.out.println("发送端启动......");//1,udpsocket服务。使用DatagramSocket对象。DatagramSocket ds = new DatagramSocket(8888);//2,将要发送的数据封装到数据包中。String str = "udp传输演示:哥们来了!";//使用DatagramPacket将数据封装到的该对象包中。byte[] buf = str.getBytes();DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.100"),10000);//3,通过udp的socket服务将数据包发送出去。使用send方法。ds.send(dp);//4,关闭资源。ds.close();}

加入循环

 public static void main(String[] args) throws IOException {System.out.println("发送端启动......");/** 创建UDP传输的发送端。* 思路:* 1,建立udp的socket服务。* 2,将要发送的数据封装到数据包中。 * 3,通过udp的socket服务将数据包发送出去。* 4,关闭socket服务。*///1,udpsocket服务。使用DatagramSocket对象。DatagramSocket ds = new DatagramSocket(8888);//       String str = "udp传输演示:哥们来了!";BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));String line = null;while((line=bufr.readLine())!=null){byte[] buf = line.getBytes();DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.100"),10000);ds.send(dp);if("886".equals(line))break;}//4,关闭资源。ds.close();}
public static void main(String[] args) throws IOException {System.out.println("接收端启动......");/** 建立UDP接收端的思路。* 1,建立udp socket服务,因为是要接收数据,必须要明确一个端口号。* 2,创建数据包,用于存储接收到的数据。方便用数据包对象的方法解析这些数据.* 3,使用socket服务的receive方法将接收的数据存储到数据包中。* 4,通过数据包的方法解析数据包中的数据。* 5,关闭资源 *///1,建立udp socket服务。DatagramSocket ds = new DatagramSocket(10000);while(true){//2,创建数据包。byte[] buf = new byte[1024];DatagramPacket dp = new DatagramPacket(buf,buf.length);//3,使用接收方法将数据存储到数据包中。ds.receive(dp);//阻塞式的。//4,通过数据包对象的方法,解析其中的数据,比如,地址,端口,数据内容。String ip = dp.getAddress().getHostAddress();int port = dp.getPort();String text = new String(dp.getData(),0,dp.getLength());System.out.println(ip+":"+port+":"+text);}//5,关闭资源。
//      ds.close();}

5 文件上传–自学

文件上传分析图解

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

基本实现

服务端实现:

public class FileUpload_Server {public static void main(String[] args) throws IOException {System.out.println("服务器 启动.....  ");// 1. 创建服务端ServerSocketServerSocket serverSocket = new ServerSocket(6666);// 2. 建立连接 Socket accept = serverSocket.accept();// 3. 创建流对象// 3.1 获取输入流,读取文件数据BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());// 3.2 创建输出流,保存到本地 .BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.jpg"));// 4. 读写数据byte[] b = new byte[1024 * 8];int len;while ((len = bis.read(b)) != -1) {bos.write(b, 0, len);}//5. 关闭 资源bos.close();bis.close();accept.close();System.out.println("文件上传已保存");}
}

客户端实现:

public class FileUPload_Client {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);bos.flush();}System.out.println("文件发送完毕");// 3.释放资源bos.close(); socket.close();bis.close(); System.out.println("文件上传完毕 ");}
}

文件上传优化分析

  1. 文件名称写死的问题

    服务端,保存文件的名称如果写死,那么最终导致服务器硬盘,只会保留一个文件,建议使用系统时间优化,保证文件名称唯一,代码如下:

FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") // 文件名称
BufferedOutputStream bos = new BufferedOutputStream(fis);
  1. 循环接收的问题

    服务端,指保存一个文件就关闭了,之后的用户无法再上传,这是不符合实际的,使用循环改进,可以不断的接收不同用户的文件,代码如下:

// 每次接收新的连接,创建一个Socket
while(true){Socket accept = serverSocket.accept();......
}
  1. 效率问题

    服务端,在接收大文件时,可能耗费几秒钟的时间,此时不能接收其他用户上传,所以,使用多线程技术优化,代码如下:

while(true){Socket accept = serverSocket.accept();// accept 交给子线程处理.new Thread(new Runnable(){......InputStream bis = accept.getInputStream();......}).start();
}

优化实现

public class FileUpload_Server {public static void main(String[] args) throws IOException {System.out.println("服务器 启动.....  ");// 1. 创建服务端ServerSocketServerSocket serverSocket = new ServerSocket(6666);// 2. 循环接收,建立连接while (true) {Socket accept = serverSocket.accept();/* 3. socket对象交给子线程处理,进行读写操作Runnable接口中,只有一个run方法,使用lambda表达式简化格式*/new Thread(new Runnable() {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. 关闭 资源bos.close();bis.close();accept.close();System.out.println("文件上传已保存");} catch (IOException e) {e.printStackTrace();}}).start();}}
}

信息回写分析图解

前四步与基本文件上传一致.

  1. 【服务端】获取输出流,回写数据。
  2. 【客户端】获取输入流,解析回写数据。

回写实现

public class FileUpload_Server {public static void main(String[] args) throws IOException {System.out.println("服务器 启动.....  ");// 1. 创建服务端ServerSocketServerSocket serverSocket = new ServerSocket(6666);// 2. 循环接收,建立连接while (true) {Socket accept = serverSocket.accept();/*3. socket对象交给子线程处理,进行读写操作Runnable接口中,只有一个run方法,使用lambda表达式简化格式*/new Thread(new Runnable {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();}}
}

客户端实现:

public class FileUpload_Client {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();}
}

6 B/S服务器

模拟网站服务器,使用浏览器访问自己编写的服务端程序,查看网页效果。

案例分析

  1. 准备页面数据,web文件夹。

    复制到我们工程中,比如复制到day01中

  1. 我们模拟服务器端,ServerSocket类监听端口,使用浏览器访问
public static void main(String[] args) throws IOException {ServerSocket server = new ServerSocket(8000);Socket socket = server.accept();InputStream in = socket.getInputStream();byte[] bytes = new byte[1024];int len = in.read(bytes);System.out.println(new String(bytes,0,len));socket.close();server.close();
}

  1. 服务器程序中字节输入流可以读取到浏览器发来的请求信息
GET /web/index.html HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Accept-Encoding: gzip, deflate, br
Accept-Language: zh

GET/web/index.html HTTP/1.1是浏览器的请求消息。/web/index.html为浏览器想要请求的服务器端的资源,使用字符串切割方式获取到请求的资源。

//转换流,读取浏览器请求第一行
BufferedReader readWb = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = readWb.readLine();
//取出请求资源的路径
String[] strArr = requst.split(" ");
//去掉web前面的/
String path = strArr[1].substring(1);
System.out.println(path);

案例实现

服务端实现:

public class SerDemo {public static void main(String[] args) throws IOException {System.out.println("服务端  启动 , 等待连接 .... ");// 创建ServerSocket 对象ServerSocket server = new ServerSocket(8888);Socket socket = server.accept();// 转换流读取浏览器的请求消息BufferedReader readWb = newBufferedReader(new InputStreamReader(socket.getInputStream()));String requst = readWb.readLine();// 取出请求资源的路径String[] strArr = requst.split(" ");// 去掉web前面的/String path = strArr[1].substring(1);// 读取客户端请求的资源文件FileInputStream fis = new FileInputStream(path);byte[] bytes= new byte[1024];int len = 0 ;// 字节输出流,将文件写会客户端OutputStream out = socket.getOutputStream();// 写入HTTP协议响应头,固定写法out.write("HTTP/1.1 200 OK\r\n".getBytes());out.write("Content-Type:text/html\r\n".getBytes());// 必须要写入空行,否则浏览器不解析out.write("\r\n".getBytes());while((len = fis.read(bytes))!=-1){out.write(bytes,0,len);}fis.close();out.close();readWb.close();   socket.close();server.close();}
}

访问效果

发现浏览器中出现很多的叉子,说明浏览器没有读取到图片信息导致。

浏览器工作原理是遇到图片会开启一个线程进行单独的访问,因此在服务器端加入线程技术。

public class ServerDemo {public static void main(String[] args) throws IOException {ServerSocket server = new ServerSocket(8888);while(true){Socket socket = server.accept();new Thread(new Web(socket)).start();}}static class Web implements Runnable{private Socket socket;public Web(Socket socket){this.socket=socket;}public void run() {try{//转换流,读取浏览器请求第一行BufferedReader readWb = newBufferedReader(new InputStreamReader(socket.getInputStream()));String requst = readWb.readLine();//取出请求资源的路径String[] strArr = requst.split(" ");System.out.println(Arrays.toString(strArr));String path = strArr[1].substring(1);System.out.println(path);FileInputStream fis = new FileInputStream(path);System.out.println(fis);byte[] bytes= new byte[1024];int len = 0 ;//向浏览器 回写数据OutputStream out = socket.getOutputStream();out.write("HTTP/1.1 200 OK\r\n".getBytes());out.write("Content-Type:text/html\r\n".getBytes());out.write("\r\n".getBytes());while((len = fis.read(bytes))!=-1){out.write(bytes,0,len);}fis.close();out.close();readWb.close();socket.close();}catch(Exception ex){}}}}

访问效果

自己实际实现

package webServer;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;/*** WebServer*/
public class WebServer {public static void main(String[] args) {ServerSocket ss=null;Socket socket=null;try {ss=new ServerSocket(10086);while (true){socket=ss.accept();//转换流,读取浏览器请求第一行BufferedReader readWb = new BufferedReader(new InputStreamReader(socket.getInputStream()));String requst = readWb.readLine();//取出请求资源的路径String[] strArr = requst.split(" ");//去掉web前面的/String path = strArr[1].substring(1);System.out.println(path);FileInputStream fis=new FileInputStream("./web/"+path);byte[] bytes = new byte[1024];int len=0;// 字节输出流,将文件写会客户端OutputStream out = socket.getOutputStream();// 写入HTTP协议响应头,固定写法out.write("HTTP/1.1 200 OK\r\n".getBytes());out.write("Content-Type:text/html\r\n".getBytes());// 必须要写入空行,否则浏览器不解析out.write("\r\n".getBytes());while((len = fis.read(bytes))!=-1){out.write(bytes,0,len);}out.write("OK".getBytes());}} catch (Exception e) {e.printStackTrace();}finally {if (socket!=null){try {socket.close();} catch (IOException e) {e.printStackTrace();}}if (ss!=null){try {ss.close();} catch (IOException e) {e.printStackTrace();}}}}
}

注意:

TCP服务端
网络编程需要注意的问题:
1.端口被占用 /–>更改端口
2 .(accept()–>等待其他客户端连接/read()–>等待结束标志位/receive)–> )等是阻塞方法,当程序停在某步不再执行时,需要排查这几个方法是否处于阻塞状态。

​ 3.如果想要让服务器结束本次读取的内容,客户端就而发向服务端发送个明确的结束标志。

​ 第一种方式是关闭socket;
​ 弟二种方式是调用shutdownOutput。

Java基础15-java进阶(6)【网络编程】相关推荐

  1. Java基础复习笔记系列 九 网络编程

    Java基础复习笔记系列之 网络编程 学习资料参考: 1.http://www.icoolxue.com/ 2. 1.网络编程的基础概念. TCP/IP协议:Socket编程:IP地址. 中国和美国之 ...

  2. Java进阶之网络编程

    网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编 ...

  3. java进阶之---------网络编程

    java进阶之网络编程,学这个之前我们在这里简单讨论一下什么是计算机网络: 计算机网络是将不同的地理位置但是具有独立功能的计算机及其外部设备,通过通信线路连接起来,实现资源共享 这里我们就将实现这个功 ...

  4. Java语言进阶:网络编程入门

    Java语言进阶:网络编程 网络编程入门 C/S C/S结构 :全称为Client/Server结构,是指客户端和服务器结构.常见程序有QQ.迅雷等软件. 特点: 客户端和服务器是分开的,需要下载客户 ...

  5. 20165230 《Java程序设计》实验五《网络编程与安全》实验报告

    20165230 <Java程序设计>实验五<网络编程与安全>实验报告 一.实验报告封面 课程:Java程序设计 班级:1652班 姓名:田坤烨 学号:20165230 成绩: ...

  6. java的网络编程设计报告_20165230 《Java程序设计》实验五《网络编程与安全》实验报告...

    20165230 <Java程序设计>实验五<网络编程与安全>实验报告 一.实验报告封面 课程:Java程序设计 班级:1652班 姓名:田坤烨 学号:20165230 成绩: ...

  7. 浅谈JAVA中如何利用socket进行网络编程(二)

    转自:http://developer.51cto.com/art/201106/268386.htm Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,利用它可以 ...

  8. Java基础-面向接口(interface)编程

    Java基础-面向接口(interface)编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.接口的概念 接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的&q ...

  9. 黑马程序员:Java基础总结----Java语言编程规范

       黑马程序员:Java基础总结        Java语言编程规范:参考自SUN公司文档  ASP.Net+Android+IO开发..Net培训.期待与您交流!  I.   排版规范 A.  规 ...

  10. 2018-2019-2 20175227张雪莹《Java程序设计》实验五 《网络编程与安全》

    2018-2019-2 20175227张雪莹<Java程序设计> 实验五 <网络编程与安全> 实验报告封面 课程:Java程序设计 班级:1752班 姓名:张雪莹 学号:20 ...

最新文章

  1. MySQL升级教程(CentOS)
  2. uploadify 附件上传
  3. npm git 遇到的问题解决笔记
  4. python manager与basemanager_使用Python多处理管理器(BaseManager/SyncManager)与远程计算机共享队列时出现管道中断...
  5. C——通过调用函数分配内存
  6. 每日一题 2020.05.26
  7. 怎么查询房贷批下来没?
  8. python实现diff json 并且打印出log日志
  9. 中图杯获奖作品计算机组,地理奥赛网-首页
  10. linux nfs 测试 读写,部署NFS与测试NFS
  11. IEEE1588 Precision Time Protocol(PTP)
  12. JAVA基础_数组(一维数组)
  13. aect17定义_【多选题】AECT关于教育技术的17定义认为,教育技术的目的是()? A. 提高绩效的 B. 促进知识理解 C. 调整改善学习及表现 D. 促进学习...
  14. 星空银河html,[内蒙好星空]5个夜晚一人逛银河[有星云星系]
  15. js中几种对数值取整数和小数部分的方法
  16. 5.1.2 网络编程进阶---开启子进程的两种方式,进程之间内存空间是相互隔离的...
  17. 《浪潮之巅》作者吴军:把握技术革命的浪尖
  18. https://github.com/gnustep/
  19. python ip反查域名
  20. 来自NULL的程序人生大作业

热门文章

  1. 分享50个AI绘画prompt的关键词,让你的AI绘画更贴近想法
  2. ubuntu下自动安装arm-linux-gcc和arm-linux-g++
  3. Java-JPBC-SM9(GBT 38635)实现与测试
  4. swjtu2382(Paint Box)
  5. 质押智能合约详解|手把手教你玩转质押合约|Defi质押
  6. VMware与艾莫讯国产300编程电缆兼容性问题
  7. Consider defining a bean of type ‘com.service.UserService‘ in your configuration解决方案
  8. 如何制作mac下的icns图标
  9. 传智播客PHP笔记05-thinkphp框架-视图渲染、display,fetch,模板替换,模板变量的赋值与实现,系统变量,模板函数,模板运算符,foreach,if,比较标签,volist标签
  10. 3000字计算机领域技术发展,计算机应用技术专业毕业论文3000字