day24
一、网络编程
1.1 基础知识
1.1.1 计算机网络
计算机网络系统就是利用通信设备和线路将地理位置不同、功能独立的多个计算机系统互联起来,以功能完善的网络软件实现网络中资源共享和信息传递的系统。
注意:以上概念至少涉及三个方面的内容:
1. 至少两台计算机互联
2. 通信设备和线路介质(通信设备:像电话、信号塔、卫星等。 线路介质:像双绞线、光纤、电缆等)
3. 网络软件、通信协议
1.1.2 网络编程
网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。总结成一句话就是对信息的发送和接收。
1.1.3 为什么学习网络编程
。。。
难道你就只玩单机游戏吗?
难道你装QQ就是为了看腾讯的界面,不是为了和男女朋友聊天?
难道你下载一个微博,里面的数据不用更新?
总结:
1. 网络编程可以用来更新应用的数据
2. 网络编程可以用来发送和接收数据
1.1.4 网络编程中的几个重要概念
客户端(Client): 移动应用、电脑应用
服务器(Server): 为客户提供服务、提供数据、提供资源的机器
请求(Request): 客户端向服务端索要数据
响应(Response): 服务器对客户端的回应,一般指将数据返回给了客户端
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uOaiaKQu-1620640363457)(img/image-20210510002752784.png)]
1.2 网络编程三要素
IP地址
要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机。IP地址就是这个标识号,也就是设备的标识,计算机的身份证编号。
分类:ipv4和ipv6
ipv4:使用4个0-255的数字来表示一个ip地址。因为一共有32位,所以可以表示2^32个地址。
比如:192.168.1.8 11000000 10101000 00000001 00001000
ipv6:由八组数字组成,每组数字都是4个16进制数(每个数字16种状态,32个数字)
特殊的ip地址: 127.0.0.1 本机地址(代表你正在使用的这台计算机,也被称为localhost)
私有地址(不在外部流通,用在局域网中):
10.x.x.x
172.16.x.x-172.31.x.x
192.168.x.x
可以通过dos窗口查询当前ip
ipconfig
ping
那么我们如何获取和操作IP地址呢?
为了方便对IP地址的获取和操作,Java提供了类InetAddress供我们使用。
端口
网络通信本质上是两个应用程序的通信。因为每台计算机都有不止一个应用程序,那么在网络通信时如何区分这些应用程序呢? 这个时候就要用到端口号了。 如果说IP地址是计算机的唯一标识。那么端口号就是计算机中应用程序的唯一标识。
更精确点来说:端口号是正在运行程序标识,每一个进程都至少有一个端口。端口号组成范围:0-65535(也就是说在计算机中最多同时能够运行65536个进程)。注意:端口0-1023为系统使用或者保留端口。我们使用1024后面的就可以了,尽量写大点,避免冲突!!!
补充:
1. 如何查询应用的端口号?
打开dos窗口,输入命令netstat -ano
2. 常见端口号
MySQL 3306
Oracle 1521
Tomcat 8080
FTP(文件传输) 21
HTTP服务器 80
HTTPS服务器 443
SSH 22
总结:通过IP+端口,我们就可以确定要通信的对象了。
通信协议
通信协议是指双方实体完成通信或服务所必须遵循的规则和约定。协议定义了数据单元使用的格式,信息单元应该包含的信息与含义,连接方式,信息发送和接收的时序,从而确保网络中数据顺利地传送到确定的地方。
在计算机通信中,通信协议用于实现计算机与网络连接之间的标准,网络如果没有统一的通信协议,电脑之间的信息传递就无法识别。 通信协议是指通信各方事前约定的通信规则,可以简单地理解为各计算机之间进行相互会话所使用的共同语言。两台计算机在进行通信时,必须使用的通信协议。
目前主流的通信协议就是TCP/IP协议
OSI参考模型
要说TCP/IP绕不开OSI参考模型。
开放式系统互联通信参考模型(英语:Open System Interconnection Reference Model,缩写为 OSI),简称为OSI模型(OSI model),一种概念模型,由国际标准化组织提出,一个试图使各种计算机在世界范围内互连为网络的标准框架。
下图为7层模型,及其所负责的功能:
TCP/IP协议
TCP/IP协议是基于OSI七层参考模型优化而来。
概念层的每一层都有一些它们自己的协议。
我们要学习的UDP和TCP就是传输层的协议。
TCP和UDP
我们要学习使用的是传输层的TCP和UDP协议
UDP协议: 面向无连接
- 用户数据报协议(User Datagram Protocol)
- UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会立即发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据
- 由于使用UDP协议消耗系统资源小,通信效率高。比如qq聊天等用的就是UDP
- 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响,但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
TCP协议: 面向有连接, 分为客户端和服务器端
- 传输控制协议 (Transmission Control Protocol)
- TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端:由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
- 三次握手(TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠)
第一次握手,客户端向服务器端发出连接请求,等待服务器确认
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端发送确认信息,确认连接 - 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件
实际通信的过程
1.3 InetAddress类
位于java.net包下。
此类表示互联网协议 (IP) 地址。 IP 地址是 IP 使用的 32 位或 128 位无符号数字,它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的
1.3.1 如何获取InetAddress类的对象
static InetAddress getLocalHost() 返回本地主机
static InetAddress getByName(String host) 在给定主机名的情况下确定主机的 IP 地址
1.3.2 常用方法
byte[] getAddress() 返回此 InetAddress 对象的原始 IP 地址
String getHostName() 获取此 IP 地址的主机名
String toString() 将此 IP 地址转换为 String
1.4 套接字Socket
套接字是通信的基石,是支持TCP/IP协议的路通信的基本操作单元。
我们说的网络编程也被称为套接字编程、Socket编程
概念
所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口。
表示方式
套接字Socket=(IP地址:端口号),套接字的表示方法是IP地址后面写上端口号,中间用冒号或逗号隔开。
每一个传输层连接唯一地被通信两端的两个端点(即两个套接字)所确定。例如:如果IP地址是210.37.145.1,而端口号是23,那么得到套接字就是 210.37.145.1:23
1.5 UDP编程
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送、接收数据的对象。因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念。
1.5.1 涉及到的类
DatagramPacket
DatagramSocket
DatagramSocket
位于java.net包下,表示数据的发送和接收。
是基于UDP协议的Socket
数据发送: 发送端将数据发送到接收端
数据接收: 接收端获取数据
比如:就像快递车
构造方法:
DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口
DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口
常用方法:
void send(DatagramPacket p) 从此套接字发送数据报包
void receive(DatagramPacket p) 从此套接字接收数据报包
DatagramPacket
位于java.net包下,表示数据报包用来进行数据的封装和解析
数据封装: 发送端发送数据时,需要将数据打包(封装)
数据解析: 接收端在接收到数据后,将数据获取到(解析)
比如:就像快递包裹
构造方法:
DatagramPacket(byte[] buf, int length)
构造 DatagramPacket,用来接收长度为 length 的数据包
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
常用方法:
byte[] getData()
获取真正的数据
int getLength()
获取将要发送或接收到的数据的长度
InetAddress getAddress()
获取某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的
int getPort()
获取某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的
1.5.2 UDP发送和接收
发送
- 建立udp的socket服务
- 将要发送的数据封装成数据包
- 通过udp的socket服务,将数据包发送出
- 关闭资源
/*
使用UDP发送
*/
public class UDPSend {
public static void main(String[] args) throws IOException {
//1. 建立udp的socket服务(此处不需要指定端口)
DatagramSocket socket = new DatagramSocket();
//2. 将要发送的数据封装成数据包
//参数一:要发送的数据
byte[] buf = "Hello UDP!!!".getBytes();
//参数二:数据的长度
int length = buf.length;
//参数三:要发送到的计算机ip (127.0.0.1表明要发送到自己的电脑上)
InetAddress ip = InetAddress.getByName("127.0.0.1");
//参数四:端口号
int port = 10086;
DatagramPacket dp = new DatagramPacket(buf, length, ip, port);
//3. 通过udp的socket服务,将数据包发送出
socket.send(dp);
//4. 关闭资源
socket.close();
}
}
接收
- 建立udp的socket服务
- 先建立一个DatagramPacket对象,用来接收和解析数据
- 通过receive方法接收数据
- 通过数据包对象的功能来完成对接收到数据进行解析
- 可以对资源进行关闭
package com.ujiuye.demo02_UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/*
使用UDP接收
*/
public class UDPReceive {
public static void main(String[] args) throws IOException {
//1. 建立udp的socket服务(通过构造方法将10086端口绑定到DatagramSocket上)
//也就相当于本程序使用了10086端口
DatagramSocket socket = new DatagramSocket(10086);
//2. 先建立一个DatagramPacket对象,用来接收和解析数据
//参数一:用来装数据的数组
byte[] b = new byte[1024];
//参数二:数据的长度
int length = b.length;
DatagramPacket dp = new DatagramPacket(b, length);
//3. 通过receive方法接收数据
socket.receive(dp);
//4. 通过数据包对象的功能来完成对接收到数据进行解析
//获取真正的数据
byte[] data = dp.getData();
//获取数据的长度
int len = dp.getLength();
//获取发送机器的ip
InetAddress address = dp.getAddress();
System.out.println(address);
System.out.println(new String(data, 0, len));
//5. 可以对资源进行关闭
socket.close();
}
}
1.5.3 案例:聊天程序
需求:利用UDP协议的发送和接收,模拟聊天小程序。设计出一个可以一直聊天的工具,发送端从键盘录入,接收端进行数据的接收
思路:将发送端和接收端设计在死循环中
1.6 TCP编程
TCP编程,面向有连接(客户端向服务器端发送请求,服务器端必须存在可以通讯,连接才能成立)分为:
客户端 : Socket
服务器端 : ServerSocket
建立连接后,通过Socket中的IO流进行数据的传输。
1.6.1涉及到的类
Socket
ServerSocket
Socket
此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
注意:一个该类的对象就代表一个客户端程序
构造方法:
public Socket(InetAddress address,int port)
public Socket(String host,int port)
常用方法:
OutputStream getOutputStream() 返回此套接字的输出流
InputStream getInputStream() 返回此套接字的输入流
void close() 关闭此套接字, 会自动关闭相关的流
ServerSocket
此类实现服务器套接字。服务器套接字等待请求通过网络传入。
注意: 一个该类的对象就代表一个服务器端程序
构造方法:
ServerSocket(int port) 创建绑定到特定端口的服务器套接字
常用方法:
Socket accept() 侦听并接受到此套接字的连接
void close() 关闭此套接字,一般不关闭
总结
TCP如何建立连接: 在客户端创建Socket对象,指定服务器ip地址和端口号就会自动连接,如果连接成功,就表示三次握手成功,程序继续执行,如果连接失败,就会报异常。
TCP如何传输数据: 使用Socket对象获得输出流写出数据,使用Socket对象获得输入流读取数据。
1.6.2 TCP的发送和接收
发送(客户端)
- 建立客户端的Socket服务(需要声明服务端的ip和端口)
如果连接建立成功,就表明已经建立了数据传输的通道。就可以在该通道通过IO进行数据的读取和写入,该通道称为Socket流,Socket流中既有读取流,也有写入流 - 通过Socket对象调用方法可以获取这个流,然后通过流进行写数据
- 如果传输数据完毕,关闭资源
package com.ujiuye.demo04_TCP;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/*
TCP实现的客户端
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//1. 建立客户端的Socket服务(需要声明服务端的ip和端口)
// 如果连接建立成功,就表明已经建立了数据传输的通道。
// 就可以在该通道通过IO进行数据的读取和写入,该通道称为Socket流,Socket流中既有读取流,也有写入流
Socket socket = new Socket("127.0.0.1", 9527);
//2. 通过Socket对象调用方法可以获取这个流,然后通过流进行写数据
OutputStream out = socket.getOutputStream();
//从键盘接收数据
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
//写入到socket流中(也就是发送)
out.write(s.getBytes());
//3. 如果传输数据完毕,关闭资源
socket.close();
}
}
接收(服务端)
1. 建立服务器端的socket服(需要一个端口)
2. 服务端没有直接操作流的功能,而是通过accept方法获取客户端对象,再通过客户端对象获取到流并和客户端进行通信
3. 通过客户端的socket对象获取到流,并读取数据
4. 如果服务完成,需要关闭客户端,然后关闭服务器(但是一般会关闭客户端,不会关闭服务器。因为服务端是一直提供服务的)
package com.ujiuye.demo04_TCP;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
TCP实现的服务端
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1. 建立服务器端的socket服(需要一个端口)
ServerSocket ss = new ServerSocket(9527);
//2. 服务端没有直接操作流的功能,而是通过accept方法获取客户端对象,再通过客户端对象获取到流并和客户端进行通信
Socket socket = ss.accept();
//3. 通过客户端的socket对象获取到流,并读取数据
InputStream in = socket.getInputStream();
byte[] b = new byte[1024];
int len = 0;
while((len = in.read(b)) != -1){
System.out.println(new String(b, 0, len));
}
//4. 如果服务完成,需要关闭客户端,然后关闭服务器
//但是一般会关闭客户端,不会关闭服务器。因为服务端是一直提供服务的
}
}
1.7 案例
需求一
客户端向服务器发送字符串数据
参考上方代码
需求二
客户端向服务器发送字符串数据,服务器回写字符串数据给客户端(模拟聊天)
/*
服务端
*/
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(4396);
//获取Socket对象(因为Socket中才有输出和输入流)
Socket socket = ss.accept();
while(true){
//-----------接收数据-----------
InputStream in = socket.getInputStream();
byte[] b = new byte[1024];
int len = in.read(b);
System.out.println("接收到的数据为:" + new String(b, 0, len));
//-----------发送数据-----------
OutputStream out = socket.getOutputStream();
System.out.println("请输入要发送的数据:");
Scanner sc = new Scanner(System.in);
String s = sc.next();
byte[] data = s.getBytes();
out.write(data);
}
}
}
/*
客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 4396);
while(true){
//-----------发送数据-----------
//获取输出流,用来发送数据
OutputStream out = socket.getOutputStream();
//获取键盘录入对象,并进入数据录入
System.out.println("请输入要发送的数据:");
Scanner sc = new Scanner(System.in);
String s = sc.next();
byte[] data = s.getBytes();
out.write(data);
//-----------接收数据-----------
InputStream in = socket.getInputStream();
//读取数据
byte[] b = new byte[1024];
int len = in.read(b);
//打印数据
System.out.println("接收到的数据为:" + new String(b, 0, len));
}
}
}
发送数据
OutputStream out = socket.getOutputStream();
//获取键盘录入对象,并进入数据录入
System.out.println("请输入要发送的数据:");
Scanner sc = new Scanner(System.in);
String s = sc.next();
byte[] data = s.getBytes();
out.write(data);
//-----------接收数据-----------
InputStream in = socket.getInputStream();
//读取数据
byte[] b = new byte[1024];
int len = in.read(b);
//打印数据
System.out.println("接收到的数据为:" + new String(b, 0, len));
}
}
}