一. 软件结构
- C/S结构:全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、微信、支付宝等软件
- B/S结构: 全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有IE、谷歌、火狐等.
以上两种架构,各有利弊,但都离不开网络,网络编程,就是基于一定的协议,实现两台计算机之间的通信的程序
二. 网络编程三大要素
2.1 IP地址和域名
2.1.1 IP地址
IP地址: 指互联网协议地址(lnternet Protocol Address) ,俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把"个人电脑"比作“一台电话”的话,那么“P地址”就相当于”电话号码”。
IP地址分类方式一:
-
IPv4:
🤮网际协议版本4(英语:Internet Protocol version 4,IPv4),又称互联网通信协议第四版,是网际协议开发过程中的第四个修订版本,也是此协议第一个被广泛部署的版本。IPv4是互联网的核心,也是使用最广泛的网际协议版本,其后继版本为IPv6,直到2011年,IANA IPv4位址完全用尽时,IPv6仍处在部署的初期。
🤮IPv4使用32位(4字节)地址,因此地址空间中只有4,294,967,296(2)个地址。不过,一些地址是为特殊用途所保留的,如专用网络(约1800万个地址)和多播地址(约2.7亿个地址),这减少了可在互联网上路由的地址数量。随着地址不断被分配给最终用户,IPv4地址枯竭问题也在随之产生。基于分类网络、无类别域间路由和网络地址转换的地址结构重构显著地减少了地址枯竭的速度。但在2011年2月3日,在最后5个地址块被分配给5个区域互联网注册管理机构之后,IANA的主要地址池已经用尽。
-
IPv6:
🤮由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得P的分配越发紧张.为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。IPv4和IPv6地址格式不相同,因此在很长一段时间里,互联网中出现IPv4和IPv6长期共存的局面。
IP地址分类方式二:
公网地址( 万维网使用)和 私有地址( 局域网使用)192.168.开头的就是私有址址,范围即为192.168.0.0-192.168.255.255,专门为组织机构内部使用
常用命令:
🏀查看本机IP地址,在控制台输入:
ipconfig /all
🏀检查网络是否连通,在控制台输入:
ping 空格 IP地址
ping 220.181.57.216
特殊的IP地址:
- 本地回环地址(HostAddress):
127.0.0.1
- 主机名(HostName):
localhost
2.1.2 域名
域名(英语:Domain Name),又称网域,是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识(有时也指地理位置)。
由于IP地址具有不方便记忆并且不能显示地址组织的名称和性质等缺点,人们设计出了域名,并通过网域名称系统(DNS,Domain Name System)来将域名和IP地址相互映射,使人更方便地访问互联网,而不用去记住能够被机器直接读取的IP地址数串
2.2 端口号
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以**唯一标识设备中的进程(应用程序)**了。端口号: 用两个宁节表示的整数,它的取值范围是0~65535
-
公认端口: 0~1023。被预先定义的服务通信占用,如: HTTP (80) ,FTP (21),Tenet (23)。
-
注册端口: 1024~49151。分配给用户进程或应用程序。如: Tomcat (8080) ,MySQL (3306)Oracle (1521) 。
-
动态/私有端口: 49152~65535
💡小tips:
如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
2.3 网络协议
-
网络通信协议: 通过计算机网络可以使多台计算机实现通信连接,位于同一个网络中的计算机在进行连接和通信需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必同时遵守才能完成数据交换
-
TCP/IP协议: 传输控制协议/因特网互联协议( Transmission Control Protocol/internet Protocol),是lnternet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
TCP/IP通信模型图如下所示:
💡小tips:
我们常说的Tcp协议与ip协议是指位于TCP/IP模型中的传输层协议与网络层协议
三. TCP与UDP协议
3.1 UDP协议
UDP: 用户数据报协议(User Datagram Protocol),它是非面向连的,不可靠的无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。
- 大小限制的: 数据被限制在64kb以内,超出这个范围就不能发送了.
- 数据报(Datagram): 网络传输的基本单位
3.2 TCP协议
TCP: 传输控制协议(Transmission ontrol Protocol)。它是面向连接的,可靠的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。是一种面向连接的、可靠的、基于字节流的传输层的通信协议,可以连续传输大量的数据。类似于打电话的效果。
这是因为它为当一台计算机需要与另一台远程计算机连接时,TCP协议会采用“三次握手"方式让它们建立一个连接,用于发送和接收数据的虚拟链路。数据传输完毕TCP协议会采用“四次挥手"方式断开连接。
TP协议负责收集这些数据信息包,并将其按活当的次序放好传送,在接收端收到后再将其正确的还原。TCP协议保证了数据包在传送中准确无误。TCP协议使用重发机制,当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体确认信息,如果没有收到另一个通信实体确认信息,则会再次重复刚才发送的消息。
四. 网络编程API
4.1 InetAddress类
InetAddress类主要表示IP地址,两个子类: Inet4Address.Inet6Address
Internet上的主机有两种方式表示地址:
域名(hostName): www.baidu.com
IP 地(hostAddress): 202.108.35.210
llnetAddress 类没有提供公共的构造器,而是提供了 如下几个静态方法来获取InetAddress 实例:
public static lnetAddress getLocalHost()
public static lnetAddress getByName(String host)
public static lnetAddress getByAddress(bytel] addr)
InetAddress 提供了如下几个常用的方法:
public String getHostAddress()
: 返回IP地址字符串 (以文本表现形式)
public String getHostName)
: 获取此IP地址的主机名
代码演示如下:
@Test
public void test01() throws UnknownHostException {
//获取本地主机名和IP地址
InetAddress localHost = Inet4Address.getLocalHost();
System.out.println(localHost);
}
代码演示如下:
/根据给定的host获取主机名和IP地址
InetAddress byName = Inet4Address.getByName("www.baidu.com");
System.out.println(byName);
//根据给的byte数组【IP地址】获取主机名和IP地址
byte[] addres={(byte) 192,(byte) 168,31,105};
InetAddress byAddress = Inet4Address.getByAddress(addres);
System.out.println(byAddress);
如何查看本地IP地址信息?
看如下操作:
4.2 socket分类
通信的两端都要有Socket(也可以叫“套接字”),是两台机器间通信的端点。**网络通信其实就是Socket间的通信。**Socket可以分为:
-
流套接字(stream socket):使用TCP提供可依赖的字节流服务
-
ServerSocket:此类实现TCP服务器套接字。服务器套接字等待请求通过网络传入。
🔔ServerSocket:建立连接
-
Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
🔔Socket:传输数据
-
-
数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务
-
DatagramSocket:此类表示用来发送和接收UDP数据报包的套接字。
🔔DatagramSocket:既建立连接,又传输数据
-
4.3 socket相关API
Socket类的常用构造方法:
public Socket(InetAddress address,int port)
:创建一个流套接字并将其连接到指定 IP 地址的指定端口号。public Socket(String host,int port)
:创建一个流套接字并将其连接到指定主机上的指定端口号。
Socket类的常用方法:
public InputStream getInputStream()
:返回此套接字的输入流,可以用于接收消息public OutputStream getOutputStream()
:返回此套接字的输出流,可以用于发送消息public InetAddress getInetAddress()
:此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。public InetAddress getLocalAddress()
:获取套接字绑定的本地地址。public int getPort()
:此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。public int getLocalPort()
:返回此套接字绑定到的本地端口。如果尚未绑定套接字,则返回 -1。public void close()
:关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream。public void shutdownInput()
:如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。public void shutdownOutput()
:禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。
🔔注意:
先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等于调用Socket的close()方法。在通信结束后,仍然要调用Scoket的close()方法,因为只有该方法才会释放Socket占用的资源,比如占用的本地端口号等。
4.4 DateGramSocket类
DatagramSocket 类的常用方法:
public DatagramSocket(int port)
:创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。public DatagramSocket(int port,InetAddress laddr)
:创建数据报套接字,将其绑定到指定的本地地址。本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。public void close()
:关闭此数据报套接字。public void send(DatagramPacket p)
:从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。public void receive(DatagramPacket p)
:从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。
4.5 DateGramPacket类
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 int getLength()
:返回将要发送或接收到的数据的长度。
五. TCP网络编程
5.1 网络通信
Java语言的基于套接字TCP编程分为服务端编程和客户端编程,其通信模型如图所示:
基于TCP的Socket通图:
5.2 网络编程案例
5.2.1 基于TCP协议的网络编程案例
案例一:服务器端发送消息给客户端,客户端接收消息
🔔服务器端需求:
- 开启一个TCP协议的服务,在8888端口号监听客户端的连接。
- 接收一个客户端的连接
- 给这个客户端发一句话: 欢迎登录
- 关闭服务器
🔔客户端需求:
- 与服务器建立连接,或者说和服务器发起连接请求
- 直接接收服务器发过来的“欢迎登录”
- 关闭客户端连接
代码演示如下:
public class Server1 {
public static void main(String[] args) throws IOException {
//开启一个TCP协议的服务,在8888端口号监听客户端的连接
ServerSocket server=new ServerSocket(8888);
//正式接收客户端的连接
Socket socket=server.accept();
/*
这是一个阻塞式方法,
如果此时没有客户端来请求,则这句代码一直等待。这句代码执行一次,就表示有一个客户端请求连接了,并且 连接成功;
连接成功后,会给它分配一个Socket对象,这个socket对象用来和这个客户端进行数据的传输通信。
*/
//如果连接成功,则会打印如下这句话
System.out.println(socket.getInetAddress()+"连接成功");
//给该客户端发送一句话,欢迎登录
OutputStream out=socket.getOutputStream();//套接字的输出流,发送消息
String info="欢迎登录";
out.write(info.getBytes());//将字符串转换为字节数据
//如果后面不和这个客户端通信,服务器就要关闭
socket.close();
//如果服务器后面不接受其他客户端连接,那么服务关闭,一般不关闭。
server.close();
}
}
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket=new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
InputStream input = socket.getInputStream(); //流套接字的输入流,用来接收并读取消息
byte[] data=new byte[1024];//用来装接收来自的服务器的字节数据
int len;
while ((len=input.read(data))!=-1){//从输入流中读取一定数量的字节,并将其存储在缓冲区数组 data 中,返回的是实际读取的字节数
System.out.println(new String(data,0,len));// 通过使用平台的默认字符集解码指定的 byte 数组data,构造一个新的 String。
//即将每次读取的实际字节数解码为string输出到控制台
}
//后续不不需要通信,就关闭连接
socket.close();
}
}
案例二:客户端与服务器端多次通信
🔔服务器端需求:
(1)开启一个TCP协议的服务,在8888端口号监听客户端的连接(2)接收一个客户端的连接
(3)接收客户端发过来的单词或词语,然后我把它“反转”例如: 客户端发过来“heLLo",反转"oLLeh"
(4)把反转后的单词或词语,返回给客户端
(5)直到客户端发过来"bye"为止🔔客户端需求:
(1) 与服务器建立连接,或者说和服务器发起连接请求(2)从键盘输入单词,并且按行给服务器发送每一个单词
(3) 直到输入bye结束
(4)结束后要断开连接
代码演示如下:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket server=new ServerSocket(8888);
Socket socket=server.accept();
System.out.println(socket.getInetAddress()+"连接成功");
InputStream input = socket.getInputStream();//服务器端接收客户端发来的信息【字节输入流】
InputStreamReader isr=new InputStreamReader(input); //将字节输入流通过转换流转换为字符输入流
BufferedReader br=new BufferedReader(isr);//缓冲流包裹字符输入流,我时希望可以使用缓冲流独有的方法readLine()
OutputStream output = socket.getOutputStream();//服务器发送给客户端的消息
/* OutputStreamWriter osw=new OutputStreamWriter(output);//将字节输出流转换为字符输出流
BufferedWriter bw=new BufferedWriter(osw);//缓冲流包裹字符输出流,可以使用它独有的方法;*/
PrintStream pr=new PrintStream(output);
String s ;
while (!(("bye".equals(s=br.readLine())))){
System.out.println("客户端发来的单词或词语:"+s);
if (s.equals(null)){
throw new NullPointerException("客户端发来的字符串为空");
}
StringBuilder sb=new StringBuilder(s);
sb.reverse();
System.out.println("服务器已反转单词:"+sb.toString());
pr.println(sb.toString());//将反转后的字符串换发给客户端
}
socket.close();
server.close();
}
}
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket=new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
OutputStream output = socket.getOutputStream();//字节输出流;output输出,从内存写出到文件,可以发送消息
/* OutputStreamWriter osw=new OutputStreamWriter(output);//字节流转换为字符输出流
BufferedWriter bw=new BufferedWriter(osw);//缓冲流包裹字符流*/
PrintStream pr=new PrintStream(output);
Scanner input=new Scanner(System.in);//System.in:默认键盘输入
InputStream in = socket.getInputStream();//字节输入流;接收消息
InputStreamReader isr=new InputStreamReader(in);//字节输入流转换为字符输入流
BufferedReader br=new BufferedReader(isr);//缓冲流包裹字符输入流
while (true){
System.out.print("请输入单词或词语:");
String word=input.next();
pr.println(word);
if (word.equals("bye")){
break;
}
System.out.println("服务器返回的结果:"+br.readLine());
}
socket.close();
}
}
案例三:上述需求不变,服务器端要与多个客户端多次通信
🔔服务器端需求:
(1)开启一个TCP协议的服务,在8888端口号监听客户端的连接(2)接收一个客户端的连接
(3)接收客户端发过来的单词或词语,然后我把它“反转”例如: 客户端发过来“heLLo",反转"oLLeh"
(4)把反转后的单词或词语,返回给客户端
(5)直到客户端发过来"bye"为止🔔客户端需求:
(1) 与服务器建立连接,或者说和服务器发起连接请求(2)从键盘输入单词,并且按行给服务器发送每一个单词
(3) 直到输入bye结束
(4)结束后要断开连接
🤮服务器端要同时与多个客户端通信
核心实现思路:运用多线程实现同时与多个客户端通信
代码演示如下:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
//接收多个客户端连接
while (true) {
Socket socket = server.accept();
System.out.println(socket.getInetAddress()+"连接成功");
new ClientHandle(socket).start();
//服务器端一直运行
// server.close();
}
}
}
class ClientHandle extends Thread{
private Socket socket;
public ClientHandle(Socket socket){
this.socket=socket;
}
@Override
public void run() {
//这里编译器报红了,此异常为受检异常,要么抛出去;要么try...catch。但是这里不能跑出去,run()方法是重写方法,对于
//受检异常来说,如果被重写方法没有抛出此受检异常,那么重写的方法不能跑出去,只能自己try..catch
try (
InputStream input = socket.getInputStream();//服务器端接收客户端发来的信息【字节输入流】
InputStreamReader isr = new InputStreamReader(input); //将字节输入流通过转换流转换为字符输入流
BufferedReader br = new BufferedReader(isr);//缓冲流包裹字符输入流,我时希望可以使用缓冲流独有的方法readLine()
OutputStream output = socket.getOutputStream();//服务器发送给客户端的消息
PrintStream pr = new PrintStream(output);
) {
String s;
//这里编译器报红了,此异常为受检异常,要么抛出去;要么try...catch
while (!(("bye".equals(s = br.readLine())))) {
System.out.println("客户端发来的单词或词语:" + s);
if (s.equals(null)) {
throw new NullPointerException("客户端发来的字符串为空");
}
StringBuilder sb = new StringBuilder(s);
sb.reverse();
System.out.println("服务器已反转单词:" + sb.toString());
pr.println(sb.toString());//将反转后的字符串换发给客户端
}
}catch (Exception e){
e.printStackTrace();
}finally {
//这里编译器报红了,此异常为受检异常,要么抛出去;要么try...catch
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket=new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
OutputStream output = socket.getOutputStream();//字节输出流;output输出,从内存写出到文件,可以发送消息
PrintStream pr=new PrintStream(output);
Scanner input=new Scanner(System.in);//System.in:默认键盘输入
InputStream in = socket.getInputStream();//字节输入流;接收消息
InputStreamReader isr=new InputStreamReader(in);//字节输入流转换为字符输入流
BufferedReader br=new BufferedReader(isr);//缓冲流包裹字符输入流
while (true){
System.out.print("请输入单词或词语:");
String word=input.next();
pr.println(word);
if (word.equals("bye")){
break;
}
System.out.println("服务器返回的结果:"+br.readLine());
}
socket.close();
}
}
案例四:多个客户端上传文件
🔔需求:
每一个客户端启动后都可以给服务器上传一个文件,服务器接收到文件后保存到一个upLoad目录中,可以同时接收多个客户端的文件
代码演示如下:
第一版代码:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
//需求:
//每一个客户端启动后都可以给服务器上传一个文件,服务器接收到文件后保存到一个upLoad目录中,可以同时接收多个客户端的文件
public class Server2 {
public static void main(String[] args) throws Exception{
ServerSocket server=new ServerSocket(8888);
while (true){
Socket socket = server.accept();//建立与客户端的连接
System.out.println(socket.getLocalAddress()+"连接成功");
new ClientsHandle(socket).start();//实现服务器端同时与多个客户端通信
}
}
}
class ClientsHandle extends Thread {
private Socket socket;
public ClientsHandle(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (
InputStream in = socket.getInputStream();//字节输入流,接收文件
BufferedInputStream bis = new BufferedInputStream(in);//缓冲流
ObjectInputStream ois = new ObjectInputStream(bis);//接收的数据中包含文件名和文件
OutputStream out = socket.getOutputStream();//字节输出流,发送消息
PrintStream pr=new PrintStream(out);
) {
String fileName = ois.readUTF();
// String ext= fiLeName.substring(fiLeName.lastIndexOf("."));//拿到文件后缀名
String newFileName=System.currentTimeMillis()+"&"+socket.getInetAddress().getHostAddress()+"&"+fileName;
FileOutputStream fos=new FileOutputStream(new File("E:\\JavaDemo\\upLoad",newFileName));
BufferedOutputStream bos=new BufferedOutputStream(fos);
byte[] data=new byte[1024];//1kb
int len;
while ((len=ois.read(data))!=-1){
bos.write(data,0,len);
}
bos.flush();
pr.println(newFileName+"已上传完毕");//告诉客户端文件已上传完毕
bos.close();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class Client2 {
public static void main(String[] args) throws Exception {
Socket socket=new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
try(
OutputStream out = socket.getOutputStream();//向服务器发送文件
ObjectOutputStream oos=new ObjectOutputStream(out);
InputStream in = socket.getInputStream();//字节输入流,接收服务器返回的消息
InputStreamReader isr=new InputStreamReader(in);//将字节输入流转换为字符输入流
BufferedReader br=new BufferedReader(isr);
){
// File file=new File("D:\\BaiduNetdiskDownload\\尚硅谷2022Java\\01-Java基础【完结】\\尚硅谷_JavaEE_01_JavaSE课程资料\\09_api","JDK_API_1.6_zh_中文.CHM");
Scanner input=new Scanner(System.in);
System.out.print("请输入要上传的文件路径:");
String filePath = input.nextLine();
File file=new File(filePath);
FileInputStream fis=new FileInputStream(file);//文件输入流,从本地文件读取文件内容
BufferedInputStream bis=new BufferedInputStream(fis);//给文件输入流套上字节缓冲流,加快读取数据速度
oos.writeUTF(file.getName());//发送文件名
byte[] data=new byte[1024];//1kb
int len;
while ((len=bis.read(data))!=-1){
oos.write(data,0,len);
}
System.out.println("客户端已经发送完毕");
oos.flush();
//D:\Test\Image\dog.png
//不加下面的代码,出现了这样的问题:客户端输入本地文件路径后,一直卡住不动,服务器端也是卡住不动,
//目的文件夹中的图片虽然有边框,但是无法显示,数据没有完全写入进去
/*
视频中给出的原因:客户端发完文件以后,没有关闭输出通道。服务器端在读取客户端送过来的文件数据中一直没有读到“-1”这个标记
然后就没有返回“newFileName+"已上传完毕" ” 这句话给客户端
我个人的理解:文件的字节数据就像水一样,IO流就像水管,服务器端和客户端就像两个抽水机,客户端把字节数据抓取,放在流中,服务器端一直
在往流中拿数据,若客户端把字节数据都放完了,它那一端的流入口没有关闭,服务器端就一直在流里抓数据,若一直到达流的末尾,它会一直阻塞
若客户端关闭了它那边的流通道,服务器端便读到流的末尾,因为此时的流就像开盖的啤酒瓶,服务器端在瓶口,客户端在瓶底,见底了
api文档这样解释:public abstract int read()
throws IOException从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。
如果因为已经到达流末尾而没有可用的字节,则返回值 -1。
在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。
*/
socket.shutdownOutput();//关闭输出通道,对方才能读取到-1标识
System.out.println(br.readLine());
bis.close();
fis.close();
/*
* 我在客户端与服务器端中对流的构建均采用了try()...catch,此try()...catch会自动关闭消耗资源的流对象
* 之前测试了几次代码,出现了socket closed的异常:Java.net.SocketEception
* 原因:tcp协议在进行三次握手,四次挥手的过程中,程序的底层中socket与所有的IO流都关闭了,因此出现了异常,而tcp挥手的时间点与
* 程序底层try()...catch自动关闭各种流的系统资源的时间点不好把控。
* 解决方案:使用普通的try...catch
*
* */
}catch (Exception e){
e.printStackTrace();
}finally {
socket.close();
}
}
}![1678981728071](C:\Users\king\AppData\Roaming\Typora\typora-user-images\1678981728071.png)
出现了一些问题,如下红框所示:
第二版代码修改演示如下:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
//需求:
//每一个客户端启动后都可以给服务器上传一个文件,服务器接收到文件后保存到一个upLoad目录中,可以同时接收多个客户端的文件
public class Server2 {
public static void main(String[] args) throws Exception{
ServerSocket server=new ServerSocket(8888);
while (true){
Socket socket = server.accept();//建立与客户端的连接
System.out.println(socket.getLocalAddress()+"连接成功");
new ClientsHandle(socket).start();//实现服务器端同时与多个客户端通信
}
}
}
class ClientsHandle extends Thread {
private Socket socket;
public ClientsHandle(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
ObjectInputStream ois=null;
PrintStream pr=null;
BufferedOutputStream bos=null;
try {
/* InputStream in = socket.getInputStream();//字节输入流,接收文件
BufferedInputStream bis = new BufferedInputStream(in);//缓冲流*/
ois = new ObjectInputStream(new BufferedInputStream(socket.getInputStream()));//接收的数据中包含文件名和文件
// OutputStream out = socket.getOutputStream();//字节输出流,发送消息
pr=new PrintStream(socket.getOutputStream());
String fileName = ois.readUTF();
// String ext= fiLeName.substring(fiLeName.lastIndexOf("."));//拿到文件后缀名
String newFileName=System.currentTimeMillis()+"&"+socket.getInetAddress().getHostAddress()+"&"+fileName;
bos=new BufferedOutputStream(new FileOutputStream(new File("E:\\JavaDemo\\upLoad",newFileName)));
byte[] data=new byte[1024];//1kb
int len;
while ((len=ois.read(data))!=-1){
bos.write(data,0,len);
}
bos.flush();
pr.println(newFileName+"已上传完毕");//告诉客户端文件已上传完毕
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
bos.close();
ois.close();
pr.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class Client2 {
public static void main(String[] args) throws Exception {
Socket socket=new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
ObjectOutputStream oos=null;
BufferedReader br=null;
BufferedInputStream bis=null;
try{
// OutputStream out = socket.getOutputStream();//向服务器发送文件
oos=new ObjectOutputStream(socket.getOutputStream());
/* InputStream in = socket.getInputStream();//字节输入流,接收服务器返回的消息
InputStreamReader isr=new InputStreamReader(in);//将字节输入流转换为字符输入流*/
br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
// File file=new File("D:\\BaiduNetdiskDownload\\尚硅谷2022Java\\01-Java基础【完结】\\尚硅谷_JavaEE_01_JavaSE课程资料\\09_api","JDK_API_1.6_zh_中文.CHM");
Scanner input=new Scanner(System.in);
System.out.print("请输入要上传的文件路径:");
String filePath = input.nextLine();
File file=new File(filePath);
/* FileInputStream fis=new FileInputStream(file);//文件输入流,从本地文件读取文件内容*/
bis=new BufferedInputStream(new FileInputStream(file));//给文件输入流套上字节缓冲流,加快读取数据速度
oos.writeUTF(file.getName());//发送文件名
byte[] data=new byte[1024];//1kb
int len;
while ((len=bis.read(data))!=-1){
oos.write(data,0,len);
}
System.out.println("客户端已经发送完毕");
oos.flush();
//D:\Test\Image\dog.png
//不加下面的代码,出现了这样的问题:客户端输入本地文件路径后,一直卡住不动,服务器端也是卡住不动,
//目的文件夹中的图片虽然有边框,但是无法显示,数据没有完全写入进去
/*
视频中给出的原因:客户端发完文件以后,没有关闭输出通道。服务器端在读取客户端送过来的文件数据中一直没有读到“-1”这个标记
然后就没有返回“newFileName+"已上传完毕" ” 这句话给客户端
我个人的理解:文件的字节数据就像水一样,IO流就像水管,服务器端和客户端就像两个抽水机,客户端把字节数据抓取,放在流中,服务器端一直
在往流中拿数据,若客户端把字节数据都放完了,它那一端的流入口没有关闭,服务器端就一直在流里抓数据,若一直到达流的末尾,它会一直阻塞
若客户端关闭了它那边的流通道,服务器端便读到流的末尾,因为此时的流就像开盖的啤酒瓶,服务器端在瓶口,客户端在瓶底,见底了
api文档这样解释:public abstract int read()
throws IOException从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。
如果因为已经到达流末尾而没有可用的字节,则返回值 -1。
在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。
*/
socket.shutdownOutput();//关闭输出通道,对方才能读取到-1标识
System.out.println(br.readLine());
/*
* 我在客户端与服务器端中对流的构建均采用了try()...catch,此try()...catch会自动关闭消耗资源的流对象
* 之前测试了几次代码,出现了socket closed的异常:Java.net.SocketEception
* 原因:tcp协议在进行三次握手,四次挥手的过程中,程序的底层中socket与所有的IO流都关闭了,因此出现了异常,而tcp挥手的时间点与
* 程序底层try()...catch自动关闭各种流的系统资源的时间点不好把控。
* 解决方案:使用普通的try...catch
*
* */
}catch (Exception e){
e.printStackTrace();
}finally {
try {
bis.close();
oos.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
案例程序通信原理图解如下:
六. UDP网络编程
6.1 网络编程案例
🔔发送端需求:
(1) 启动一个socket,用于发送数据报
(2) 准备发送的数据
(3)把数包装到数据报对象中
(4) 通过socket把数据报发出去
🔔接收端需求:
(1) 先建立一个socket,用于监听是否有数据发过来
(2) 准备一个数据报,用于接收数据
(3) 接收数据
代码演示如下:
import java.io.IOException;
import java.net.*;
public class Send {
public static void main(String[] args) throws IOException {
//没有指定端口IP和端口号,ip地址默认本机,端口随机分配,现在不关心发送方的IP和端口号
DatagramSocket socket=new DatagramSocket();
String str="Hello World,UDP网络编程";
byte[] data=str.getBytes();
DatagramPacket dp=new DatagramPacket(data,data.length, InetAddress.getLocalHost(),8888);
socket.send(dp);
System.out.println("发送完毕");
//如果后面不用,可以关闭
socket.close();
}
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Receive {
public static void main(String[] args) throws Exception {
DatagramSocket socket=new DatagramSocket(8888);
byte[] data=new byte[1024];//1024个字节
DatagramPacket dp=new DatagramPacket(data,data.length);
socket.receive(dp);
System.out.println("接收完毕");
System.out.println(new String(data,0,dp.getLength()));//dp.gerLength()得到的是实际读取的字节数
socket.close();
}
}