网络基础
软件结构
-
C/S
- 全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。
-
B/C
- 全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。
网络编程意义
- 在一定的协议下,实现两台计算机的通信的程序。
网络结构
-
应用层
- HTTP、FTP、TFTP、SMTP、SNMP、DNS
-
传输层
-
TCP
- 1.面向有连接, 数据安全, 速度慢
- 2.三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
- 3.保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。
-
UDP
- 1.面向无连接, 数据不安全, 速度快(数据包大小有限制: 64K)
- 2.它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。
- 3.日常应用中,例如视频会议、QQ聊天等。
-
-
网络层
- ICMP、IGMP、IP、ARP、RARP
-
数据链路层
-
物理层
网络编程三要素
-
协议
- 计算机网络通信必须遵守的规则
-
IP地址
-
指互联网协议地址(Internet Protocol Address),俗称IP(IP地址用来给一个网络中的计算机设备做唯一的编号)
-
分类有IPV4、IPV6
-
IPV4
- 是一个32位的二进制数,通常被分为4个字节,表示成
a.b.c.d
的形式,例如192.168.65.100
。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。
- 是一个32位的二进制数,通常被分为4个字节,表示成
-
IPV6
- 为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。
- 为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成
-
-
InetAddress : 表示IP地址, 主机名
-
public class Demo01 {
public static void main(String[] args) throws UnknownHostException {
// 获取本机
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
System.out.println("主机名: " + localHost.getHostName());
System.out.println("IP地址: " + localHost.getHostAddress());
// 获取指定的IP地址
InetAddress pyn = InetAddress.getByName("写IP地址");
System.out.println(pyn.getHostName());
System.out.println(pyn.getHostAddress());
}
}
-
端口号(0~65535)
- 唯一标识设备中的进程(应用程序)
UDP
发送端
- DatagramSocket()
- DatagramPacket(byte[] arr, int len, InetAddress ip, int port)
- send(DatagramPacket)
接收端
- DatagramSocket(int port)
- DatagramPacket(byte[] arr, int len)
- receive(DatagramPacket)
发送端给接收端发送一句话
/**
* 发送端
*/
public class SendTest {
public static void main(String[] args) throws Exception {
// 创建DatagramSocket
DatagramSocket socket = new DatagramSocket();// 建议不指定端口号, 但是如果指定必须**不能**和接收端是一样的
// 将字符串转换成字节数组, 就是要发送的数据
byte[] arr = "人无远虑必有近忧".getBytes();
// 获取发送给那个接收端的IP地址
InetAddress address = InetAddress.getLocalHost();
// 创建DatagramPacket
DatagramPacket packet = new DatagramPacket(arr, arr.length, address, 8888);
// 发送数据
socket.send(packet);
// 释放资源
socket.close();
}
}
/**
* 接收端
*/
public class ReceiveTest {
public static void main(String[] args) throws Exception{
// 创建DatagramSocket, 指定当前接收端的端口号
DatagramSocket socket = new DatagramSocket(8888);
// 创建字节数组
byte[] arr = new byte[1024];
// 创建DatagramPacket
DatagramPacket packet = new DatagramPacket(arr, arr.length);
// 接收数据
socket.receive(packet); // 接收端接收到的数据, 最终介绍到字节数组中
InetAddress inetAddress = packet.getAddress();
String name = inetAddress.getHostName();
String address = inetAddress.getHostAddress();
// 获取接收到字节的个数
int length = packet.getLength();
// 查看接收到的数据
// 将字节数组, 转换成字符串 -> 打印
System.out.println("收到了ip: " + address + ", 主机名: " + name + ", 发送过来的数据: " + new String(arr, 0, length));
// 释放资源
socket.close();
}
}
群聊
//不断接收数据
public class ReceiveAllTest {
public static void main(String[] args) throws Exception{
DatagramSocket socket = new DatagramSocket(8888);
// 创建用来接收数据的字节数组
byte[] arr = new byte[1024 * 8];
DatagramPacket packet = new DatagramPacket(arr, arr.length);
while (true) {
// 接收数据
socket.receive(packet);
// 获取发送人的ip地址和主机名
InetAddress ip = packet.getAddress();
String name = ip.getHostName();
String address = ip.getHostAddress();
// 数据存放在arr数组中, 所以打印arr数组中的内容
System.out.println("收到了ip: " + address + ", 主机名: " + name + ", 发送过来的数据: " + new String(arr));
}
// socket.close();
}
}
- 一个接收端, 对应多个发送端
TCP
客户端
-
Socket(String ip, int port)
- 指定连接服务器的IP地址和端口号
- getInputStream()
- getOutputStream()
服务器
-
ServerSocket(int port)
-
创建服务器, 指定服务器自己的端口号
-
Socket accept()
- 接收客户端的连接, 并获取Socket
-
客户端给服务器写出一句话
/**
* 客户端 : 给服务器发送一句话
*/
public class ClientTest {
public static void main(String[] args) throws IOException {
// 创建Socket对象, 并且指定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 8888);
// 获取和服务器连接的输出流
OutputStream os = socket.getOutputStream();
// 写出数据
os.write("汝妻子吾养也, 汝勿虑之!".getBytes());
// 释放资源
os.close();
socket.close();
}
}
/**
* 服务器 : 接收客户端发送过来的数据
*/
public class ServerTest {
public static void main(String[] args) throws IOException {
// 创建ServerSocket对象, 指定自己的端口号
ServerSocket ss = new ServerSocket(8888);
System.out.println("【服务器】正在等待客户端的连接...");
// 接收客户端的连接
Socket socket = ss.accept(); // 阻塞
// 了解: 通过socket可以获取到客户端的信息
String address = socket.getInetAddress().getHostAddress();
System.out.println("【服务器】" + address + "连接成功");
// 获取和客户端连接的输入流
InputStream is = socket.getInputStream();
// 创建字节数组
byte[] arr = new byte[1024 * 8];
// 读数据, 将读取到的数据, 存放到字节数组中
// 返回读取到有效的字节个数
int len = is.read(arr);
// 打印读取到的数据
System.out.println(new String(arr, 0, len));
// 释放资源
is.close();
socket.close();
ss.close();
}
}
客户端和服务器互相说一句话
public class ClientTest {
public static void main(String[] args) throws IOException {
// 创建Socket对象, 并且指定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 8888);
// 获取和服务器连接的输出流
OutputStream os = socket.getOutputStream();
// 写出数据
os.write("汝妻子吾养也, 汝勿虑之!".getBytes());
// ************客户端接收服务器的回写***************
// 获取和服务器连接的 输入流
InputStream is = socket.getInputStream();
// 创建字节数组
byte[] arr = new byte[1024 * 8];
// 读取数据
int len = is.read(arr);
// 打印
System.out.println(new String(arr, 0, len));
// *********************************************
// 释放资源
is.close();
os.close();
socket.close();
}
}
public class ServerTest {
public static void main(String[] args) throws IOException {
// 创建ServerSocket对象, 指定自己的端口号
ServerSocket ss = new ServerSocket(Constant.MY_PORT);
System.out.println("【服务器】正在等待客户端的连接...");
// 接收客户端的连接
Socket socket = ss.accept(); // 阻塞
// 了解: 通过socket可以获取到客户端的信息
String address = socket.getInetAddress().getHostAddress();
System.out.println("【服务器】" + address + "连接成功");
// 获取和客户端连接的输入流
InputStream is = socket.getInputStream();
// 创建字节数组
byte[] arr = new byte[1024 * 8];
// 读数据, 将读取到的数据, 存放到字节数组中
// 返回读取到有效的字节个数
int len = is.read(arr);
// 打印读取到的数据
System.out.println(new String(arr, 0, len));
// ************服务器回写给客户端一句话*****************
// 获取和客户端连接的输出流
OutputStream os = socket.getOutputStream();
// 写出一句话
os.write("我谢谢你!".getBytes());
// *************************************************
// 释放资源
os.close();
is.close();
socket.close();
ss.close();
}
}
综合练习
文件上传案例
- 普通文件上传
/**
* 文件上传案例的客户端
*/
public class UploadClient {
public static void main(String[] args) throws IOException {
// 创建和硬盘连接的输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("day14-code\\test.txt"));
// 创建Socket对象, 并指定服务器的IP地址和端口号
Socket socket = new Socket("192.168.39.80", 8888);
// 获取和服务器连接的输出流
OutputStream os = socket.getOutputStream();
// 高效的字节输出流
BufferedOutputStream bos = new BufferedOutputStream(os);
// 读写数据
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
// 释放资源
bos.close();
socket.close();
bis.close();
}
/**
* 文件上传案例的服务器
*/
public class UploadServer {
public static void main(String[] args) throws IOException {
// 创建ServerSocket对象, 并指定服务器自己的端口号
ServerSocket ss = new ServerSocket(8888);
System.out.println("【服务器】正在等待连接...");
// 接收客户端的连接
Socket socket = ss.accept();
System.out.println("【服务器】连接成功!~开始传输数据!");
// 获取和客户端连接的输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
// 获取和硬盘连接的输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("g:\\upload\\a.txt"));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
System.out.println("【服务器】文件上传成功!~");
// 释放资源
bos.close();
bis.close();
socket.close();
ss.close();
}
}
-
改进一
- 文件名
- while(true)
//使用UUID保证文件名字不重复
public class Demo02_UUID {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
UUID uuid = UUID.randomUUID();
String s = uuid.toString();
System.out.println(s);
}
}
}
/**
* 文件上传案例的客户端
*/
public class UploadClient {
public static void main(String[] args) throws IOException {
// 创建和硬盘连接的输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("day14-code\\test.txt"));
// 创建Socket对象, 并指定服务器的IP地址和端口号
Socket socket = new Socket("192.168.39.80", Constant.MY_PORT);
// 获取和服务器连接的输出流
OutputStream os = socket.getOutputStream();
// 高效的字节输出流
BufferedOutputStream bos = new BufferedOutputStream(os);
// 读写数据
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
// 释放资源
bos.close(); // 由于代码指定到close, 写出了结束标记, 所以程序才可以正常执行
socket.close();
bis.close();
}
}
/**
* 文件上传案例的服务器
*/
public class UploadServer {
public static void main(String[] args) throws IOException {
// 创建ServerSocket对象, 并指定服务器自己的端口号
ServerSocket ss = new ServerSocket(8888);
while (true) {
System.out.println("【服务器】正在等待连接...");
// 接收客户端的连接
Socket socket = ss.accept(); // 阻塞
// 在接收客户端连接之后, 开启新线程
// 新线程的任务就是去拷贝
new Thread(() -> {
try {
String address = socket.getInetAddress().getHostAddress();
System.out.println("【服务器】连接成功!~开始传输数据!");
// 获取和客户端连接的输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
// 字符串通过+的拼接, 以及StringBuilder的append方法
// 如果拼接引用数据类型, 会默认调用toString()方法
String path = "g:\\upload\\" + UUID.randomUUID() + ".jpg";
// 获取和硬盘连接的输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
System.out.println("【服务器】文件上传成功!~");
System.out.println(address + "上传了文件" + path);
// 释放资源
bos.close();
bis.close();
socket.close();
// ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
-
改进三
-
开启线程
-
回写
-
/**
* 文件上传案例的客户端
*/
public class UploadClient {
public static void main(String[] args) throws IOException {
// 创建和硬盘连接的输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("day14-code\\test.txt"));
// 创建Socket对象, 并指定服务器的IP地址和端口号
Socket socket = new Socket("192.168.39.80", Constant.MY_PORT);
// 获取和服务器连接的输出流
OutputStream os = socket.getOutputStream();
// 高效的字节输出流
BufferedOutputStream bos = new BufferedOutputStream(os);
// 读写数据
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
// 如果代码中加了回写, 一定要加上下面两条语句
// 刷缓冲区
bos.flush();
// 禁用此套接字(Socket)的输出流, 任何先前的数据将会被发送, 可以写出结束标记 -> shutdownOutput()
// 不会清空缓冲区
socket.shutdownOutput();
// ****************接收回写**********************
// 获取和服务器连接的输入流
InputStream is = socket.getInputStream();
byte[] arr = new byte[1024];
System.out.println("111");
int len = is.read(arr); // 读取, 阻塞
System.out.println("222");
// 打印
System.out.println(new String(arr, 0, len));
// *********************************************
// 释放资源
is.close();
bos.close(); // 由于代码指定到close, 写出了结束标记, 所以程序才可以正常执行
socket.close();
bis.close();
}
}
/**
* 文件上传案例的服务器
*/
public class UploadServer {
public static void main(String[] args) throws IOException {
// 创建ServerSocket对象, 并指定服务器自己的端口号
ServerSocket ss = new ServerSocket(Constant.MY_PORT);
while (true) {
System.out.println("【服务器】正在等待连接...");
// 接收客户端的连接
Socket socket = ss.accept(); // 阻塞
// 在接收客户端连接之后, 开启新线程
// 新线程的任务就是去拷贝
new Thread(() -> {
try {
String address = socket.getInetAddress().getHostAddress();
System.out.println("【服务器】连接成功!~开始传输数据!");
// 获取和客户端连接的输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
// 字符串通过+的拼接, 以及StringBuilder的append方法
// 如果拼接引用数据类型, 会默认调用toString()方法
String path = "g:\\upload\\" + UUID.randomUUID() + ".jpg";
// 获取和硬盘连接的输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
System.out.println("333");
int b;
while ((b = bis.read()) != -1) { // 阻塞
bos.write(b);
}
System.out.println("444");
// *****************回写******************
// 获取和客户端连接的输出流
OutputStream os = socket.getOutputStream();
os.write("上传成功".getBytes());
// **************************************
// 释放资源
os.close();
bos.close();
bis.close();
socket.close();
// ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
模拟B/S
BIO
NIO
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
while (true) {
Thread.sleep(1000);
// 客户端通道
SocketChannel channel = SocketChannel.open();
// 连接
channel.connect(new InetSocketAddress("127.0.0.1", 10000));
// 写出
channel.write(ByteBuffer.wrap("你好啊!~".getBytes()));
// 释放资源
channel.close();
}
}
}
public class Server {
public static void main(String[] args) throws IOException {
// 服务器通道, 只做接收客户端的连接
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 将通道设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 绑定端口号
serverSocketChannel.bind(new InetSocketAddress(10000));
// 创建多路复用器/ 选择器
Selector selector = Selector.open();
// 注册到一起, 指定接收新连接的操作
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 不断的轮询
while (true) {
// 得到操作集的操作个数
int count = selector.select();
// 判断, 个数>0
if (count > 0) {
// 获取操作集 key
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 涉及到要删除集合中的元素, 所以遍历方式使用 -> 迭代器
Iterator<SelectionKey> it = selectionKeys.iterator();
// 遍历
while (it.hasNext()) {
SelectionKey key = it.next();
// 移除对应的操作
it.remove();
// 接收新连接
if (key.isAcceptable()) {
// 获取服务器通道, 用来接收连接
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// 接收客户端的连接, 并获取客户端通道
SocketChannel channel = ssc.accept();
// 设置阻塞状态为非阻塞
channel.configureBlocking(false);
// 注册
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 读取数据
// 获取通道
SocketChannel channel = (SocketChannel) key.channel();
// 创建字节缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (channel.read(byteBuffer) > 0) {
// 写 -> 读
byteBuffer.flip();
System.out.println(new String(byteBuffer.array()));
// 读 -> 写
byteBuffer.clear();
}
channel.close();
}
}
}
}
}
}