网络编程
1.什么是网络编程
在网络通信协议下,不同计算机上运行的程序,进行的数据传输。
如果想把一个计算的结果,或者是电脑上的文件通过网络传递给你的朋友,就需要用到网络编程。
在实际生活中,网络通信无处不在
- **应用场景:**即时通信、网游对战、金融证券、国际贸易、邮件、等等
不管是什么场景,都是 计算机 跟 计算机 之间通过网络进行数据传输。
- Java中可以使用
java.net
包下的技术轻松开发出常见的网络应用程序。
目前市面上常见的软件架构:
- B/S
- C/S
不管是 B/S 架构,还是 C/S 架构,客户端/浏览器负责的仅仅是把数据展示出来,展示给用户去看。在项目中,真正核心的逻辑都是在服务器当中的。
BS架构的优缺点
- 不需要开发客户端,只需要页面 + 服务端
- 用户不需要下载,打开浏览器就能使用
- B/S 的特点主要突出一个:方便
- 如果应用过大,用户体验受到影响
CS架构的优缺点
- 画面可以做的非常精美,用户体验好(因为所有资源都被用户下载到了本地)
- 需要开发客户端,也需要开发服务端(针对公司而言,CS架构的开发、安装、部署、维护非常麻烦)
- 用户需要下载和更新的时候太麻烦
二者都有各自的优缺,具体用哪种,需要结合当前项目的特点来断定。一般而言,是类似于 LOL、王者荣耀这种游戏,它对于画面、音乐都有非常大的要求,就用 CS 架构;如果类似与新闻这种对画面没有太大要求的就可以使用 BS 架构。
2.网络编程三要素
两台计算机只要需要知道哪些东西才能进行数据传输呢?特别是,我想要给一堆电脑中的其中一台发送数据,需要知道哪些参数才可以呢?
- 确定对方电脑在互联网上的地址,这个地址是唯一的——IP
- 确定接收数据的软件,从微信->微信,而不是微信->qq,一个端口号只能被一个软件绑定——端口号
- 确定网络传输的规则,数据在传输的时候,我们不能随便书写它的格式,想怎么写就怎么写——协议
需要知道 IP、端口、协议,才能进行数据的传输。手机、IPAD、笔记本,都是可以上网的,只要上网,都需要有一个 IP,所以此处写的是设备。
IP
全程:Internet Protocol,是互联网协议地址,也称 IP 地址。是分配给上网设备的数字标签。
通俗理解
上网设备在网络中的地址,是唯一的
常见的IP分类为
IPV4、IPV6
IPV4
全称:Internet Protocol version 4,互联网通信协议第四版。
采用 32 位地址长度,分为 4 组
真实的 IP 是 32 bit 的二进制数
因为不好记也不好用,后来采用点分十进制表示法将 8 个 bit 作为一组转化为 10进制,总共分为4组,它们是无符号数,每一组的取值范围是 0 ~ 255。
IPV4 的缺点:在 IPV4 中总共只有不到 43 亿个 IP,IPV4 的数量是有限的,是不够使用的,2019年11月26日全部分配完毕。为了解决 IP 不够用的问题,出现了 IPV6。
IPV6
全称:Internet Protocol version 6,互联网通信协议第六版。
由于互联网的蓬勃发展,IP地址的需求量愈来愈大,而 IPV4 的模式下 IP 的总数是有限的。
IPV6 采用 128 位地址长度,分为 8 组,每一组是 16 个 bit。IPV6 可以给地球上每一粒沙子都定义一个 IP。
目前 IPV6 还未普及,在不久的将来,IPV6 会超越 IPV4,成为市场的主流。
IPV4已经分配完了,IPV6还未普及,目前如何解决IP不够用的问题?
IPV4的地址分类形式
- 公网地址(万维网使用)和私有地址(局域网使用)。
- 192.168. 开头的就是私有地址,范围为 192.168.0.0 ~ 192,168.255.255,专门为组织机构内部使用,以此节省 IP
在网吧中所有电脑共享同一个公网 IP,再由路由器给每一台电脑分配局域网 IP,这样就可以实现节约 IP 的效果。
特殊IP地址
127.0.0.1,也可以是 localhost: 是回送地址也称本地回环地址,也称本地本机 IP,永远只会寻找当前所在本机。
疑问: 假设 192.168.1.100 是我电脑的 IP,那么这个 IP 跟 127.0.0.1 是一样的吗?
它们是不一样的。
假设我们现在有一堆电脑,它们的 IP 都是由路由器分配的。
每一个路由器给设备分配的 IP 可能是不一样的,所以会有这么一个情况:当我们换了一个地方上网,我们上网设备的局域网 IP 有可能不一样。
但是,如果我们往 127.0.0.1 发送数据,那么它是不经过路由器的。我们的数据经过网卡的时候,网卡发现,我们要往 127.0.0.1 发送数据,此时,它就把这个数据给我们自己发过来了,不管在哪里上网,永远都是这样的。这就是两者的区别。
建议: 在练习的时候,如果我们是自己给自己发送数据,就写 127.0.0.1 就可以了。
常见 CMD 命令
- ipconfig: 查看本机 IP 地址
- ping: 检查网络是否连通,不仅可以检查局域网是否畅通,还可以检查外网是否畅通。在 ping 的后边,既可以跟随 IP,也可以跟随 网址。
网址的底层逻辑其实也是 ip
InetAddress 的使用
在Java中用来表示 IP 的类:InetAddress
在底层它会进行判断,我们用的是 6 版本的还是 4 版本的,如果是 4 版本的,它其实创建的是它的子类 Inet4Address 的对象并返回;如果是 6 版本的则会创建 Inet6Address 并返回。
由于该类没有对外暴露构造方法,所以我们需要通过它的构造方法 getByName 来获取它的对象。这个方法的底层其实就是判断是 IPV4 和 IPV6,判断完之后会创建对应的子类对象并返回。
public class MyInetAddressDemo1 {
public static void main(String[] args) throws Exception {
/**
* static InetAddress getByName(String host) 确定主机名称的 ip 地址。主机名称可以是及其名称,也可以是 ip 地址
* String getHostName() 获取 IP 地址的主机名
* String getHostAddress() 返回文本显示中的 IP 地址字符串
*/
// 1.获取 InetAddress 的对象
// IP 的对象 ——> 一台电脑的对象
InetAddress address = InetAddress.getByName("192.168.0.105");
System.out.println("ipv4 address:" + address); // ipv4 address:/192.168.0.105
// 主机名其实就是我们给自己电脑起的名字,如果没起也会有默认的 Win + E 打开我的电脑,右键 + 属性可以查看
InetAddress address2 = InetAddress.getByName("DESKTOP-V32JVCD");
System.out.println("hostname:" + address2); // hostname:DESKTOP-V32JVCD/192.168.0.105
// getHostName 方法的小细节:如果电脑因为网络原因或者局域网中压根就没有这台电脑,此时获取不到主机名,此时以 IP 的形式进行展示
String hostName = address.getHostName();
System.out.println(hostName); // 192.168.0.105
System.out.println(address2.getHostName()); // DESKTOP-V32JVCD
String hostAddress = address.getHostAddress();
System.out.println(hostAddress); // 192.168.0.105
System.out.println(address2.getHostAddress()); // 192.168.0.105
/**
* 这只是一个前置的代码,一旦我们获取到 IP 之后,我们就可以给某一台电脑发送消息了
*/
}
}
端口号
应用程序在设备中唯一的标识
端口号:由两个字节表示的整数,取值范围:0~65535
其中 0 ~ 1023 之间的端口号用于一些知名的网络服务或者应用
我们自己使用 1024 以上的端口号就可以了。
注意:一个端口号只能被一个应用程序使用。
端口其实就是电脑往外发送数据的出口,或者说是电脑接收外部数据的入口。
软件一运行就要绑定一个端口,如果没有绑定端口,那么它就是单击的,是无法往外发送数据/接收数据的。
协议
在计算机网络中,连接和通信的规则被称为网络通信协议。协议就是数据传输的规则
在传输数据的时候,国际标准组织定义了一个 OSI 的网络参考模型,把传输数据分成了7层
- OSI 参考模型:世界互联协议标准,全球通信规范,单模型过于理想化,未能在因特网上进行广泛推广。
- TCP/IP 参考模型(或 TCP/IP 协议):事实上的国际标准。
代码运行在应用层,如果我们想给对方电脑发送数据,则会一层一层的往下,到达物理层,然后转化为二进制数据,再发送给对方电脑。对方电脑接收到数据之后会进行解析,再一层一层的传输给最上方的应用层,我们的程序就可以接收到数据了。
UDP 协议
-
用户数据报协议(User Datagram Protocol)
-
UDP 是 面向无连接 通信协议。
速度快,有大小限制一次最多发送 64 k,数据不安全,易丢失数据
面向无连接:UDP 协议传输数据时,不会管两台设备之间的网络是否畅通,直接发,你能收到就收到,收不到就拉倒。
UDP 适用于丢失一点数据不会产生任何影响的场景,比如:网络会议、语言通话、在线视频
TCP 协议
-
传输控制协议 TCP(Transmission Control Protocol)
-
TCP 协议是 面向连接 的通信协议。
速度慢,没有大小限制,数据安全。
TCP 发送数据之前会先检查两台设置之间的网络畅通。简单来说,就是确保连接成功才会发送数据。
TCP 适用于对数据有非常大的需求,一点都不能丢的情况。比如:下载软件、文字聊天、发送邮件
3.UDP通信程序
UDP 通信程序(发送数据)
UDP 协议发送数据
public class SendMessageDemo {
public static void main(String[] args) throws Exception {
// 发送数据
// 1.创建 DatagramSocket对象(快递公司)
// 细节:
// 绑定端口:以后我们就是通过这个端口往外发送数据
// 空参:所有可用的端口中随机一个进行使用
// 有参:指定端口号进行绑定
// DatagramSocket socket = new DatagramSocket(8000);
DatagramSocket socket = new DatagramSocket();
// 2.打包数据
String str = "你好威猛啊!!!";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10086;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
// 3.发送数据
socket.send(dp);
// 4.释放资源
socket.close();
}
}
UDP 通信程序(接收数据)
注意:一定得先运行接收端再运行发送端
public class ReceiveMessageDemo {
public static void main(String[] args) throws Exception {
// 接收数据
// 注意:一定得先运行接收端再运行发送端
// 1.创建DatagramSocket对象(快递公司)
// 细节:
// 在接收的时候,一定要绑定端口
// 而且绑定的端口,一定要跟发送的端口保持一直
DatagramSocket socket = new DatagramSocket(10086);
// 2.接收数据包,既然此处是用来接收数据的,所以只需要传一个数组即可,IP 和 端口就不用了
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 该方法是阻塞的
// 程序执行到这一步的时候,会在这里死等
// 等发送端发送消息
socket.receive(packet);
// 3.解析数据包
byte[] data = packet.getData();
int length = packet.getLength();
InetAddress address = packet.getAddress();
int port = packet.getPort();
System.out.println("接收到的数据" + new String(data, 0, length));
System.out.println("该数据是从" + address + "这台电脑中的" + port + "端口发送过来的");
//4.资源释放
socket.close();
}
}
从哪个端口发出无所谓,只要发送的 ip + port 指向接收端即可。
练习:聊天室
public class SendMessageChat {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(7070);
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入您要说的话");
String next = scanner.nextLine();
byte[] bytes = next.getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("127.0.0.1"), 7071);
socket.send(packet);
if (next.equals("886")) {
break;
}
}
System.out.println("退出发送端");
socket.close();
}
}
public class ReceiveMessageChat {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(7071);
// 每次接收到数据都会对 bytes 进行覆盖
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {
socket.receive(packet);
System.out.println(packet.getAddress().getHostAddress() + ":" + packet.getPort() + "的设备发来数据:" + new String(packet.getData(), 0, packet.getLength()));
}
}
}
允许某个类可以允许多个
UDP 的三种通信方式
-
单播:一对一,左边的发送端只给右边的一台设备发送消息
以前的代码就是单播
-
组播:可以给一组设备发送消息
组播地址:224.0.0.0 ~ 239.255.255.255
其中 244.0.0.0 ~ 244.0.0.255 为预留的组播地址(我们自己想用只能用这个范围之内的)
之前的 ip 只能表示一台电脑,而这里随便一个组播地址就可以表示多台电脑
组播发送端代码
public class SendMessageDemo { public static void main(String[] args) throws Exception { /** * 组播发送代码 */ // 通过 MulticastSocket 对象 MulticastSocket ms = new MulticastSocket(); // 创建 DatagramPacket 对象 String str = "你好,你好!"; byte[] bytes = str.getBytes(); // 发送时指定 IP 地址一定要指定组播地址 InetAddress address = InetAddress.getByName("224.0.0.1"); int port = 10000; DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, address, port); // 调用 MulticastSocket 发送数据方法发送数据 ms.send(datagramPacket); // 释放资源 ms.close(); } }
组播接收端代码
public class ReceiveMessageDemo1 { public static void main(String[] args) throws Exception { /** * 组播接收端代码 */ // 1.创建 MulticastSocket 对象 MulticastSocket ms = new MulticastSocket(10000); // 2.将当前本机,添加到 224.0.0.1 的这一组当中 InetAddress address = InetAddress.getByName("224.0.0.1"); ms.joinGroup(address); // 3.创建 DatagramPacket 数据包对象 byte[] bytes = new byte[1024]; DatagramPacket dp = new DatagramPacket(bytes, bytes.length); // 4.接收数据 ms.receive(dp); // 5.解析数据 byte[] data = dp.getData(); int len = dp.getLength(); String ip = dp.getAddress().getHostAddress(); String name = dp.getAddress().getHostName(); System.out.println("ip 为:" + ip + ",主机名为:" + name + "的人,发送了数据:" + new String(data, 0, len)); // 6. 释放资源 ms.close(); } }
-
广播:可以给局域网中所有设备发送消息
广播地址:255.255.255
广播代码几乎和单播一模一样,只需要修改 ip 地址为 255.255.255.255 即可
广播发送端
public class SendMessageDemo {
public static void main(String[] args) throws Exception {
// 发送数据
// 1.创建 DatagramSocket对象(快递公司)
// 细节:
// 绑定端口:以后我们就是通过这个端口往外发送数据
// 空参:所有可用的端口中随机一个进行使用
// 有参:指定端口号进行绑定
// DatagramSocket socket = new DatagramSocket(8000);
DatagramSocket socket = new DatagramSocket();
// 2.打包数据
String str = "你好威猛啊!!!";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("255.255.255.255");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
// 3.发送数据
socket.send(dp);
// 4.释放资源
socket.close();
}
}
广播接收端:
public class ReceiveMessageDemo {
public static void main(String[] args) throws Exception {
// 接收数据
// 注意:一定得先运行接收端再运行发送端
// 1.创建DatagramSocket对象(快递公司)
// 细节:
// 在接收的时候,一定要绑定端口
// 而且绑定的端口,一定要跟发送的端口保持一直
DatagramSocket socket = new DatagramSocket(10000);
// 2.接收数据包,既然此处是用来接收数据的,所以只需要传一个数组即可,IP 和 端口就不用了
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
socket.receive(packet);
// 3.解析数据包
byte[] data = packet.getData();
int length = packet.getLength();
InetAddress address = packet.getAddress();
int port = packet.getPort();
System.out.println("接收到的数据" + new String(data, 0, length));
System.out.println("该数据是从" + address + "这台电脑中的" + port + "端口发送过来的");
//4.资源释放
socket.close();
}
}
4.TCP通信程序
TCP 通信协议是一种可靠的网络协议,它在通信的两端各建立一个 Socket 对象
通信之前要保证连接已经建立
通过 Socket 产生 IO 流来进行网络通信
TCP 发送数据
public class Client {
public static void main(String[] args) throws Exception {
// TCP 协议,发送数据
// 1.创建 Socket 对象
// 细节:在创建对象的同时会连接服务端,如果连接不上,代码会报错
Socket socket = new Socket("127.0.0.1", 10000);
// 2.可以从连接通道中获取输入流
OutputStream os = socket.getOutputStream();
// 写出数据
os.write("你好你好".getBytes()); // 字节流输出时只能输出字节数据
// 3.释放资源
os.close();
socket.close();
}
}
TCP 读取数据
public class Server {
public static void main(String[] args) throws Exception {
// TCP 协议,接收数据
// 1.创建对象 ServerSocket
// 注意:创建对象时一定要绑定一个端口,而且此端口一定要跟客户端连接的端口保持一致
// 如果不一致的话,客户端还是连接不上
ServerSocket serverSocket = new ServerSocket(10000);
// 2.监听客户端的连接
Socket socket = serverSocket.accept();
// 3.从连接通道中获取输入流读取数据
/*InputStream is = socket.getInputStream();
// 结局中文乱码问题:写的时候是把一个中文字符分为3个字节来输出,读的时候需要一个字符一个字符的读
InputStreamReader isr = new InputStreamReader(is);*/
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int buff;
while ((buff = br.read()) != -1) {
System.out.print((char) buff);
}
// 4.释放资源
br.close();
// 断开于客户端之间的连接
socket.close();
// 关闭服务器
serverSocket.close();
}
}
TCP 通信程序(三次握手)
ICMP协议
TCP 通信程序(四次挥手)
ICMP协议
四次回收必须等待服务端把连接通道里的数据处理完毕了连接才能断开
5.综合练习
TCP通信练习1 — 多发多收
客户端:多次发送数据
public class Client {
public static void main(String[] args) throws Exception {
// 客户端:多次发送数据
// 服务器:接收多次接收数据,并打印
// 1.创建 Socket 对象并连接服务端
Socket socket = new Socket("127.0.0.1", 10000);
// 2.写出数据
OutputStream outputStream = socket.getOutputStream();
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入您要发送的信息");
String msg = sc.nextLine();
if (msg.equals("886")) {
break;
}
outputStream.write(msg.getBytes());
}
System.out.println("客户端下线!");
// 3.释放资源
sc.close();
socket.close();
}
}
服务端:接收多次接收数据,并打印
public class Server {
public static void main(String[] args) throws Exception {
// 客户端:多次发送数据
// 服务器:接收多次接收数据,并打印
// 1. 创建对象绑定 10000 端口
ServerSocket serverSocket = new ServerSocket(10000);
// 2.等待客户端连接
Socket socket = serverSocket.accept();
// 3.读取数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int buff;
while ((buff = br.read()) != -1) {
System.out.print((char) buff);
}
// 4.释放资源
socket.close();
serverSocket.close();
}
}
TCP通信练习2 — 接收和反馈
客户端:发送一条数据,接收服务端反馈的消息并打印
public class Client {
public static void main(String[] args) throws Exception {
// 客户端:发送一条数据,接收服务端反馈的消息并打印
// 服务端:接收数据并打印,再给客户端反馈消息
// 1.创建 Socket 对象连接服务器
Socket socket = new Socket("127.0.0.1", 10000);
// 2.写出数据
String str = "见到你很高兴!";
OutputStream os = socket.getOutputStream();
os.write(str.getBytes());
// 写出一个结束标记
socket.shutdownOutput(); // 结束输出流
// 3.接收服务端回写的数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int b;
while ((b = br.read()) != -1) {
System.out.print((char) b);
}
// 4.释放资源
socket.close();
}
}
服务端:接收数据并打印,再给客户端反馈消息
public class Server {
public static void main(String[] args) throws Exception {
// 客户端:发送一条数据,接收服务端反馈的消息并打印
// 服务端:接收数据并打印,再给客户端反馈消息
// 1.创建对象并绑定 10000 端口
ServerSocket ss = new ServerSocket(10000);
// 2.等待客户端连接
Socket socket = ss.accept();
// 3.socket中获取输入流读取数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int b;
/**
* 细节:
* read 方法会从连接通道中读取数据
* 但是,需要有一个结束标记,此处的循环才会停止
* 否则,程序就会一直停在 read 方法这里,等待读取下面的数据
*/
while ((b = br.read()) != -1) {
System.out.print((char) b);
}
// 4.回写数据
String str = "到底有多开心?";
OutputStream os = socket.getOutputStream();
os.write(str.getBytes());
// 5.释放资源
socket.close();
ss.close();
}
}
TCP通信练习3 — 上传文件
客户端:将本地文件上传到服务器。接收服务器的反馈。
public class Client {
public static void main(String[] args) throws Exception {
// 客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
// 1.创建 Socket 对象连接服务器
Socket socket = new Socket("127.0.0.1", 10000);
// 2.读取本地文件中的数据,并写到服务器中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("./clientdir/a.png"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
// 输出文件结束符
socket.shutdownOutput();
// 回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println(br.readLine());
// 3.释放资源
bis.close();
socket.close();
}
}
服务器:接收客户端上传的文件,上传完毕之后给出反馈。
public class Server {
public static void main(String[] args) throws Exception {
// 客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
// 1.创建对象并绑定 10000 端口
ServerSocket ss = new ServerSocket(10000);
// 2.等待客户端连接
Socket socket = ss.accept();
// 3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("./serverdir/b.png"));
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
// 回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功!");
bw.newLine();
bw.flush();
// 4.释放资源
bos.close();
socket.close();
ss.close();
}
}
TCP通信练习4 — 上传文件(文件名重复问题)
解决上一题文件名重复问题
客户端不变,服务端输出文件时文件名使用 UUID 生成
public class Server {
public static void main(String[] args) throws Exception {
// 客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
// 1.创建对象并绑定 10000 端口
ServerSocket ss = new ServerSocket(8000);
// 2.等待客户端连接
Socket socket = ss.accept();
// 3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "") + ".png";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("./serverdir/" + name));
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
// 回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功!");
bw.newLine();
bw.flush();
// 4.释放资源
bos.close();
socket.close();
ss.close();
}
}
TCP通信练习5 — 上传文件(多线程版)
想要服务器不停止,能接收很多用户上传的图片。
该怎么做呢?
提示:可以用循环或者多线程。
但是循环不合理,最优解法是多线程改写。
public class Server {
public static void main(String[] args) throws Exception {
// 客户端:将本地文件上传到服务器。接收服务器的反馈。
// 服务器:接收客户端上传的文件,上传完毕之后给出反馈。
// 1.创建对象并绑定 10000 端口
ServerSocket ss = new ServerSocket(8000);
while (true) {
// 2.等待客户端连接
Socket socket = ss.accept();
// 开启一条线程
// 一个用户就对应服务端的一条线程
new Thread(new MyRunnable(socket)).start();
}
}
static class MyRunnable implements Runnable {
private Socket socket;
public MyRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "") + ".png";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("serverdir/" + name));
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
// 回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功!");
bw.newLine();
bw.flush();
// 4.释放资源
bos.close();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (null != socket) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
TCP通信练习6 — 上传文件(线程池版)
频繁创建并销毁线程非常浪费系统资源,所以需要用线程池优化。
public class Server {
public static void main(String[] args) throws Exception {
// 客户端:将本地文件上传到服务器。接收服务器的反馈。
// 服务器:接收客户端上传的文件,上传完毕之后给出反馈。
// 创建线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // 核心线程数
16, // 线程池总大小
60, // 空闲时间
TimeUnit.SECONDS, // 空闲时间单位
new ArrayBlockingQueue<>(2), // 阻塞队列
Executors.defaultThreadFactory(), // 线程工厂:让线程池如何创建线程对象
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// 1.创建对象并绑定 10000 端口
ServerSocket ss = new ServerSocket(8000);
while (true) {
// 2.等待客户端连接
Socket socket = ss.accept();
// 开启一条线程
// 一个用户就对应服务端的一条线程
pool.submit(new MyRunnable(socket));
}
}
static class MyRunnable implements Runnable {
private Socket socket;
public MyRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "") + ".png";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("serverdir/" + name));
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
// 回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功!");
bw.newLine();
bw.flush();
// 4.释放资源
bos.close();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (null != socket) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
TCP通信练习7 — BS(接收浏览器的消息并打印)
客户端:不需要写
服务端:接收数据并打印
public class Server {
public static void main(String[] args) throws Exception {
// 服务端:BS架构->接收浏览器的消息并打印
// 1.创建对象并绑定 10000 端口
ServerSocket ss = new ServerSocket(8000);
// 2.等待客户端连接
Socket socket = ss.accept();
// 3.socket中获取输入流读取数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int b;
while ((b = br.read()) != -1) {
System.out.print((char) b);
}
// 4.释放资源
socket.close();
ss.close();
}
}
浏览器直接请求 127.0.0.1:8000 即可看到控制台输出:
GET / HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
sec-ch-ua: "Chromium";v="116", "Not)A;Brand";v="24", "Microsoft Edge";v="116"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.76
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6