【JAVA入门】Day49 - 网络编程
文章目录
网络编程就是在网络通信协议下,不同计算机上运行的程序,进行的数据传输。
Java 中可以使用 java.net 包下的技术轻松开发出常见的网络应用程序。要开发网络应用程序,我们需要先了解计算机常见的软件架构。
计算机软件常见的软件架构是 C/S 和 B/S 架构。
- C/S:Client/Server 客户端/服务器。这种架构需要用户在本地下载并安装客户端应用程序,在远程有一个服务端程序,它们共同工作。比如QQ、Wechat。
- B/S:Browser/Server 浏览器/服务器。这种架构只需要一个浏览器,用户通过不同的网址就能访问不同的服务器。比如JD.com、Taobao.com。
B/S 架构的优点是我们不需要开发客户端,只需要页面 + 服务端即可,用户不需要下载,打开浏览器就能用。但是如果应用过大,用户体验就会受到影响。
C/S 架构的优点是软件的资源大部分存储在用户本地,画面可以做得很精美,用户体验好。但是 C/S 架构既要开发客户端,也要开发服务端。 服务端更新后,用户也要随之更新客户端。
一、网络编程三要素
在网络编程过程中,我们需要知道三件事情,这三件事情是非常重要的。
- 其一是确定对方电脑在互联网上的地址,这个地址一般被我们称作IP。
- 其二是确定对方电脑上接收数据的软件,这个软件一般是绑定一个端口号使用。
- 其三是确定网络传输的规则,我们用户之间通过网络传递数据不能随便传输,而应该遵循一定的协议。
因此网络编程的三要素是:IP、端口号、协议。三者缺一不可。
- IP:设备在网络中的地址,是唯一的标识。
- 端口号:应用程序在设备中唯一的标识。
- 协议:数据在网络中传输的规则,常见的协议有 UDP、TCP、http、https、FTP。
1.1 IP
IP 的全称是 Internet Protocol,是互联网协议地址,也称 IP 地址,它是分配给上网设备的数字标签。通俗地说,它是上网设备在网络中的地址,是唯一的。
常见的 IP 地址有两种,一种是 IPv4 ,另一种是 IPv6。
1.1.1 IPv4
IPv4 全称是 Internet Protocol version 4。它是互联网通信协议的第四版。
采用32位地址长度,分为四组(一组8bit)。32位,也就是32bit,一共4字节。
由于它这 32bit 十分不好记,所以我们8位分为一组,一共四组,每一组转成十进制,中间用 “.” 表示,这就是点分十进制表示法。要注意每一组之间的数字是有取值范围的:0 ~ 255。因此,IPv4 能表示的 IP 地址值其实是有上限的,大概是43亿个。
为了解决 IPv4 数量不够用的问题,IPv6 出现了。
1.1.2 IPv6
IPv6 全称 Internet Protocol version 6,互联网通信协议第六版。它是为了解决 IPv4 不够用的问题而诞生的。它的全长达到了 128bit ,且被分成了 8 组(一组16bit)。它的分组方式也和 IPv4 不同,它采用的是“冒分十六进制”法。就是将这八组的每一组用冒号分隔开,然后转成十六进制表示。一般我们会把每一组前面多余的0省略。如果计算出的十六进制表示形式中间有多个连续0,我们还会采用“0位压缩表示法”简略书写。
1.1.3 IPv4 的地址分类形式
IPv4 的数量既然告急,那在 IPv6 诞生之前我们是如何解决这个问题的呢?这就要谈到 IPv4 的地址分类形式。
IPv4 实际上分为公网地址(万维网使用)和私有地址(局域网使用)。
在 IPv4 中,192.168 开头的就是局域网 IP,它是私有地址,范围是:192.168.0.0 ~ 192.168.255.255。专门为组织机构内部使用,以此节省 IP。一般在使用时,往往是多台设备共享同一个公网 IP,是由路由器给每一台设备分配一个局域网 IP。
在众多 IP 中有一个非常特殊的地址,它就是 127.0.0.1,也可以写作 localhost: ,它是回送地址,也称作本地回环地址,也称本机IP,它永远只会寻找当前所在的本机。在本机向本机传收数据时,我们一般都是用这个 IP。
1.1.4 InetAddress 类
在 Java 当中,用来表示 IP 的类是 InetAddress。它有两个子类:Inet4Address,Inet6Address。我们在创建 InetAddress 对象时,实际上是根据你当前系统使用的 IP 协议,生成相对应的子类对象。InetAddress 没有对外提供构造方法,我们生成对象需要使用这个方法:
static InetAddress getByName(String host) 在给定主机名的情况下确定主机的IP地址
代码实现如下所示。
package InternetCode;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class MyInetAddressDemo1 {
/*
InetAddress类
*/
public static void main(String[] args) throws UnknownHostException {
//1.获取InetAddress对象(IP对象)
InetAddress address = InetAddress.getByName("172.17.81.27");
System.out.println(address);
//2.返回主机名
String name = address.getHostName();
System.out.println(name); //172.17.81.27
//3.返回IP
String ip = address.getHostAddress();
System.out.println(ip);
}
}
1.2 端口号
端口号是应用程序在设备中的唯一标识。
端口号是由两个字节表示的整数,它的取值范围是:0 ~ 65535。
其中 0 ~ 1023 之间的端口号用于一些知名的网络服务或者应用。如果我们自己要使用端口,就必须使用 1024 及以上的端口号。但是要注意的是,一个端口号只能被一个应用程序使用。
端口号就好比两台电脑中软件交换信息的出入口。
1.3 协议
计算机网络中,连接和通信的规则被称为网络通信协议。
目前国际上的通信标准协议是 TCP/IP 协议。它的前身是 OSI 参考模型,这是一个七层的模型,由于太过复杂没有被广泛推广,而 TCP/IP 协议是它的简化。
TCP/IP 协议分为四层:应用层、传输层、网络层、物理链路层。它们每一层都分别有自己的协议。
在这些协议中,传输层的两个协议 —— UDP、TCP 尤为重要,需要我们知道。
1.3.1 UDP协议和TCP协议的对比
UDP 协议:
- 用户数据报协议(User Datagram Protocol)。
- UDP 是面向无连接通信协议。它的特点是速度快,但是有大小限制(一次最多发送64KB),数据不安全,容易丢失数据。
- 所谓面向无连接,就是无论两台设备的网路是否连接成功,UDP 协议都会将数据发送出去,不管目标设备是否能收到。
TCP 协议:
- 传输控制协议(Transmission Control Protocol)。
- TCP 协议是面向连接的通信协议。它的特点是速度慢,没有大小限制,数据安全。
- 面向连接就是确保两台设备的网络畅通,确保连接成功后,才会发送数据。
1.3.2 UDP 通信程序的编写
1.3.2.1 发送数据
UDP 通信程序的编写可以分为发送数据和接收数据两部分。如果把 UDP 发送数据比作发快递,那么可以按照以下步骤施行。
代码实现:
package UDPProgram;
import javax.xml.crypto.Data;
import java.io.IOException;
import java.net.*;
public class SendMessageDemo {
public static void main(String[] args) throws IOException {
//发送数据
//1.创建DatagramSocket对象(快递公司)
//细节:
//绑定端口:通过这个端口往外发送数据
//空参:所有可用端口中随机使用一个
//有参:指定端口号进行绑定
DatagramSocket ds = new DatagramSocket();
//2.打包数据
String str = "你好丫!!!!";
byte[] bytes = str.getBytes(); //发送数据时要转为字节数组
InetAddress address = InetAddress.getByName("127.0.0.1"); //要发送给哪台电脑(IP)
int port = 10086; //发送给哪个端口
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
//3.发送数据
ds.send(dp);
//4.释放资源
ds.close();
}
}
1.3.2.2 接收数据
如果把 UDP 数据的接收比作收快递,那么可以按照以下步骤执行:
代码实现如下:
package UDPProgram;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class ReceiveMessageDemo {
public static void main(String[] args) throws IOException {
//接收数据
//1.创建DatagramSocket对象(快递公司)
//细节:
//在接收的时候,一定要绑定端口
//绑定的端口一定要和发送端口一致
DatagramSocket ds = new DatagramSocket(10086);
//2.接收数据包
//创建数组用来存储接收的数据
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//该方法是阻塞的
//程序在执行到这一步的时候,会在这里死等
ds.receive(dp);
//3.解析数据包
byte[] data = dp.getData(); //获取数据,获取到的就是传递过来的整个字节数组
int len = dp.getLength(); //获取了多少个字节?
InetAddress address = dp.getAddress(); //发送者的IP地址
int port = dp.getPort(); //发送者是从哪个端口发送来的数据
System.out.println("接收到数据" + new String(data,0,len));
System.out.println("该数据是从" + address + "这台电脑中的" + port + "这个端口发出的。");
}
}
需要注意的是,在测试数据发送和接收时,要先运行接收程序,再运行发送程序。接收程序运行后,代码执行到 ds.receive() 这一行时,程序会在这里死等,直到接收到数据后,才会执行下面的代码。
1.3.2.3 收发数据的合并 —— 简易聊天室
使用网络编程和 UDP 协议,可以实现简单的聊天室系统:
UDP 发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束。
UDP 接收数据:接收端不知发送端何时停止发送,采用死循环接收。
发送端的代码编写如下:
package UDPProgram;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class SendMessageDemo1 {
public static void main(String[] args) throws IOException {
/*
发送数据,数据来自键盘录入,直到输入886,发送数据结束
*/
//1.创建对象DatagramSocket对象
DatagramSocket ds = new DatagramSocket();
//2.打包数据
Scanner sc = new Scanner(System.in);
System.out.println("------------------输入要说的话---------------");
while (true) {
String str = sc.nextLine();
if(str.equals("886")){
break;
}
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.发送数据
ds.send(dp);
}
//4.释放资源
ds.close();
}
}
思路:将接收键盘输入和打包发送放入死循环执行,当键盘接收到"886"时,跳出循环。
接收端代码实现如下:
package UDPProgram;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiveMessageDemo1 {
public static void main(String[] args) throws IOException {
/*
接收端
*/
//1.创建DatagramSocket对象,端口要和发送端一致
DatagramSocket ds = new DatagramSocket(10086);
//2.接收数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
while (true) {
ds.receive(dp);
//3.解析数据包
byte[] data = dp.getData(); //数据内容
int len = dp.getLength(); //数据长度
String ip = dp.getAddress().getHostAddress(); //发送IP
String name = dp.getAddress().getHostName(); //发送主机名
//4.打印数据
System.out.println("ip为:" + ip + ",主机名为:" + name + "的人,发送给了数据:" + new String(data,0,len));
}
}
}
思路:将接收语句和数据包解析语句写入循环,无限执行即可。
1.3.2 UDP 的三种通信方式
UDP 的通信方式一般有三种如下三图所示:
之前我们写的聊天室收发数据就属于一对一的单播。
组播是一次往一组电脑中发送数据,组播地址有取值范围:224.0.0.0 - 239.255.255.255,其中 224.0.0.0 - 224.0.0.255 为预留的组播地址。
广播是往局域网里所有的电脑都发送数据,广播的地址很简单只有一个:255.255.255.255。
由此可见,UDP 发送数据的方式可以根据你发送的地址来区分,组播的代码可以这样写:
发送端要创建的是 MulticastSocket 对象,也就是组播对象。且发送到的 IP 地址应该在 224.0.0.0 ~ 224.0.0.255 之间,这一区间的 IP 是预留好的组播 IP 地址。
package UDPProgram;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class SendGroupMessageDemo {
public static void main(String[] args) throws IOException {
/*
组播发送代码
*/
//1.创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket();
//2.创建组播IP地址
//在创建IP地址时,我们要创建组播地址,也就是224.0.0.0 ~ 224.0.0.255之间的地址
InetAddress address = InetAddress.getByName("224.0.0.1");
//3.创建DatagramPacket对象
String s = "你好呀!!!";
byte[] bytes = s.getBytes();
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
//4.发送数据
ms.send(dp);
//5.释放资源
ms.close();
}
}
接收端也要创建 MulticastSocket 对象,然后利用 ms.joinGroup() 方法把本机的 IP 加入到组播 IP 这一组当中,此时再接收数据才能收到。
package UDPProgram;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class ReceiveGroupMessageDemo {
public static void main(String[] args) throws IOException {
/*
组播接收端
*/
//1.创建ms对象,注意写上端口
MulticastSocket ms = new MulticastSocket(10000);
//2.将当前本机的IP,添加到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();
}
}
如果不在这一组的机器,是接收不到这一组传来的消息的。
而广播的代码就更简单了,只需要把单播代码里要发送的 IP 地址改成 255.255.255.255,就能实现广播。
1.3.3 TCP 通信程序的编写
TCP 通信协议是一种可靠的网络协议,它在通信的两端分别各自建立一个套接字对象(Socket)。通信之前,一定要保证连接已建立。两台设备之间通过套接字产生IO流来进行网络通信。针对客户端而讲,它生成的是输出流,对服务器而言,它生成的是输入流。
TCP 通信的流程一般如下:
客户端:
① 创建客户端的 Socket 对象与指定的服务端连接。
Socket(String host, int port)
② 获取输出流,写数据。
OutputStream getOutputStream()
③ 释放资源。
void close()
服务端:
① 创建服务端的 Socket 对象(ServerSocket)。
ServerSocket(int port)
② 监听客户端,返回一个 Socket 对象。
Socket accept()
③ 获取输入流,读数据,把数据显示在控制台。
InputStream getInputStream()
④ 释放资源。
void close()
以下是按照这个步骤写的客户端与服务端的代码实现:
package TCPProgram;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
//TCP协议,发送数据
//1.创建Socket对象
//细节:创建对象同时会尝试连接服务端,如果连不上,代码会报错
Socket socket = new Socket("127.0.0.1", 10000);
//2.可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
//写出数据
os.write("aaa".getBytes());
//3.释放资源
socket.close();
}
}
package TCPProgram;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
//TCP协议,接收数据
//1.创建对象ServerSocket,注意端口保持一致
ServerSocket ss = new ServerSocket(10000);
//2.监听客户端的连接,死等
//如果有客户端来连,返回一个Socket对象
Socket socket = ss.accept();
//3.从连接通道中获取输入流
InputStream is = socket.getInputStream();
//读入数据
int b;
while((b = is.read()) != -1) {
System.out.println((char) b);
}
//4.释放资源
socket.close(); //断开跟客户端连接
ss.close(); //关闭服务器
}
}
细节:代码中的服务端是按照字节流来读取的,一个字节一个字节读取,一定读不了中文,在 UTF-8 编码中,中文汉字用 3 个字节存储,因此每次读取一个字节只是读了三分之一个汉字,势必会产生乱码。为了解决乱码问题,我们要把服务端的字节输入流改写成字符输入流。
字节输入流 ——> 字符输入流,我们可以使用转换流实现。
package TCPProgram;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
//TCP协议,接收数据
//1.创建对象ServerSocket,注意端口保持一致
ServerSocket ss = new ServerSocket(10000);
//2.监听客户端的连接,死等
//如果有客户端来连,返回一个Socket对象
Socket socket = ss.accept();
//3.从连接通道中获取输入流
InputStream is = socket.getInputStream();
//通过转换流把字节流转换为字符流
InputStreamReader isr = new InputStreamReader(is);
//用字符流读入数据
int b;
while((b = isr.read()) != -1) {
System.out.print((char) b);
}
//4.释放资源
socket.close(); //断开跟客户端连接
ss.close(); //关闭服务器
}
}
细节:通过字符流读取大量数据时,如果想要提高读取效率,我们可以再生成一个缓冲流,把字符流包装起来,这样读取效率会大大提高。
package TCPProgram;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
//TCP协议,接收数据
//1.创建对象ServerSocket,注意端口保持一致
ServerSocket ss = new ServerSocket(10000);
//2.监听客户端的连接,死等
//如果有客户端来连,返回一个Socket对象
Socket socket = ss.accept();
//3.从连接通道中获取输入流
InputStream is = socket.getInputStream();
//通过转换流把字节流转换为字符流
InputStreamReader isr = new InputStreamReader(is);
//通过缓冲流把字符流包装成缓冲流
BufferedReader br = new BufferedReader(isr);
//用字符流读入数据
int b;
while((b = br.read()) != -1) {
System.out.print((char) b);
}
//4.释放资源
socket.close(); //断开跟客户端连接
ss.close(); //关闭服务器
}
}
1.3.4 TCP通信——三次握手和四次挥手
三次握手是为了保证连接的建立。它的三次体现在客户端与服务端实际上互通了三次消息。客户端发出连接请求到服务端,服务端返回响应到客户端,客户端发送确认信息到服务端。
四次挥手是为了确保连接断开前,传输数据已经处理完毕。客户端向服务端发送断连请求,服务端返回响应到客户端,然后等到服务端处理完所有数据后,服务端再向客户端发出确认取消信息,客户端再返回确认消息到服务端。