Java高级_Day17(网络编程,端口,枚举,注解)
网络编程
网络编程的基本概述:
计算机网络:
- 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
网络编程的三要素:
IP地址:
- 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识
端口:
- 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识
协议:
- 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议
IP地址:
IP地址:是网络中设备的唯一标识
IP地址的分类:
- IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
- IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题
常用命令:
- ipconfig:查看本机IP地址
- ping IP地址:检查网络是否连通
- ping IP地址
- ping IP地址 -t 表示持续发送ping指令
- ping www.baidu.com(域名)
特殊IP地址:
127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
localhost :表示本机地址
InetAddress
此类表示Internet协议(IP)地址。
IP地址是由IP使用的32位或128位无符号数字,构建UDP和TCP协议的低级协议
返回值类型 | 方法 |
---|---|
static InetAddress | getByName(String host) 确定主机名称的IP地址。 |
String | getHostAddress() 返回文本显示中的IP地址字符串。 |
String | getHostName() 获取此IP地址的主机名。 |
static InetAddress | getLocalHost() 返回本地主机的地址。 |
byte[] | getAddress() 返回此 InetAddress对象的原始IP地址。 |
static InetAddress[] | getAllByName(String host) 给定主机的名称,根据系统上配置的名称服务返回其IP地址数组。 |
public static void main(String[] args) throws UnknownHostException {
//获取InetAddress对象
InetAddress address = InetAddress.getByName("192.168.1.4");
//获取主机名称
String name = address.getHostName();
System.out.println("主机名称:" + name);
//从主句名称获取InetAddress对象
InetAddress address1 = InetAddress.getByName(name);
//获取主机的IP地址
String ip = address1.getHostAddress();
System.out.println("IP:" +ip);
//获取本地主机地址
InetAddress address2 = InetAddress.getLocalHost();
//获取IP地址的字节表示
byte[] ip1 = address2.getAddress();
System.out.println("IP地址的字节表示:");
for (byte b : ip1){
System.out.print(b + "\t");
}
System.out.println();
}
端口
计算机上的程序的唯一标识
端口号:
用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
协议:
计算机网络中,连接和通信的规则被称为网络通信协议
UDP协议:
- 用户数据报协议(User Datagram Protocol)
- UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
- 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
- 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
TCP协议: - 传输控制协议 (Transmission Control Protocol)
- TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
- 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认
- 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
- 第三次握手,客户端再次向服务器端发送确认信息,确认连接
- 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等
序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。
确认号ack:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。
确认ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效
同步SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。
UDP通信的使用
Socket 网络套接字,该类实现客户端套接字(也称为“套接字”)。套接字是两台机器之间通讯的端点。
DatagramSocket :此类表示用于发送和接收数据报数据包的套接字。
DatagramSocket()
构造数据报套接字并将其绑定到本地主机上的任何可用端口。
DatagramSocket(int port)
构造数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int port, InetAddress laddr)
创建一个数据报套接字,绑定到指定的本地地址。
返回值类型 | 方法 |
---|---|
void | receive(DatagramPacket p) 从此套接字接收数据报包。 |
void | send(DatagramPacket p) 从此套接字发送数据报包。 |
void | close() 关闭此数据报套接字。 |
DatagramPacket : 该类表示数据报包。
DatagramPacket(byte[] buf, int length)
构造一个 DatagramPacket用于接收长度的数据包 length 。
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
DatagramPacket(byte[] buf, int offset, int length)
构造一个 DatagramPacket用于接收长度的分组 length ,指定偏移到缓冲器中。
返回值类型 | 方法 |
---|---|
byte[] | getData() 返回数据缓冲区。 |
int | getLength() 返回要发送的数据的长度或接收到的数据的长度。 |
实现发送数据:
- 创建Socket对象 DatagramSocket
- 打包数据 DatagramPacket
- 调用DatagramSocket的 send方法发送数据(DatagramPacket)
- 关闭套接字 结束数据的发送
public class SendData {
public static void main(String[] args) throws IOException {
System.out.println("发送端开始工作,开始发送数据......");
//建立发送端 套接字对象 可以绑定到本地的任意端口
DatagramSocket sendSocket = new DatagramSocket();
//数据打包
//构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
byte[] data = "发送的数据".getBytes(StandardCharsets.UTF_8);
DatagramPacket dp = new DatagramPacket(data,data.length, InetAddress.getByName("192.168.1.4"),10086);
//发送数据
sendSocket.send(dp);
//关闭发送端
sendSocket.close();
System.out.println("发送端发送完成...");
}
}
接收端:
- 创建DatagramSocket对象 DatagramSocket(int port) 构造数据报套接字并将其绑定到本地主机上的指定端口。
- 调用DatagramSocket的recive方法 接收数据报包(DatagramPacket )
- 解析数据报包
- 关闭接收端
public class ReceiveData {
public static void main(String[] args) throws IOException {
System.out.println("接收端开始工作.....");
//建立接收端
DatagramSocket receiveSocket = new DatagramSocket(10086);
//接收数据
while (true){
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
receiveSocket.receive(dp);
// 从接受到的数据报包中来获取数据 以字节数组的形式出现
byte[] bytes = dp.getData();
//将字节数组转换为字符串
String receiveData = new String(bytes);
//输出数据
System.out.println(receiveData);
//接收端一般不需要关闭
}
}
}
注意事项:
- 数据发送时 数据的目标端口必须一致
- 在指定发送数据的目的地的IP的时候,可以使用ip地址,也可以使用主机名
实现:发送端可以多次向接收端发送数据,数据是可以由手动输入的。包括发送的终止也是可以由自己控制的
public class SendInputData {
public static void main(String[] args) throws IOException {
//创建Socket对象
DatagramSocket sendSocket = new DatagramSocket();
//封装键盘输入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while((line = br.readLine()) != null){
//判断结束条件
if("bye".equals(line)){
break;
}
//将输入的字符串转换为字节数组
byte[] buf = line.getBytes(StandardCharsets.UTF_8);
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"),10086);
sendSocket.send(dp);
System.out.println("发送端发送给了数据:" + line);
}
//关闭发送
sendSocket.close();
}
}
接收端不变
TCP协议发送数据和接收数据的基本的使用:
TCP的特点:
- 面向链接: 三次握手,四次挥手
- 传输可靠
- 区分客户端和服务端
TCP协议的基础客户端:Socket
Socket(InetAddress address, int port)
创建流套接字并将其连接到指定IP地址的指定端口号。
Socket(String host, int port)
创建流套接字并将其连接到指定主机上的指定端口号。
返回值类型 | 方法 |
---|---|
void | close() 关闭此套接字。 |
InputStream | getInputStream() 返回此套接字的输入流。 |
OutputStream | getOutputStream() 返回此套接字的输出流。 |
客户端:
public class Client {
public static void main(String[] args) throws IOException {
//创建客户端的Socket
Socket client = new Socket("192.168.1.4", 10010);
// 准备数据 通过输出流
OutputStream os = client.getOutputStream();
os.write("服务端 ,你好 ,我来了...".getBytes(StandardCharsets.UTF_8));
//释放资源
os.close();
client.close();
}
}
服务端:
ServerSocket现了服务器套接字。 服务器套接字等待通过网络进入的请求
ServerSocket()
创建未绑定的服务器套接字。
ServerSocket(int port)
创建绑定到指定端口的服务器套接字。
返回值类型 | 方法 |
---|---|
Socket | accept() 侦听要连接到此套接字并接受它。 |
public class Server {
public static void main(String[] args) throws IOException {
//创建服务端的Socket
ServerSocket server = new ServerSocket(10010);
System.out.println("服务端启动在了10010端口上......");
// 接收到客户端的Socket
Socket socket = server.accept();
// 解析数据
InputStream is = socket.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
String data = new String(buf,0,len);
System.out.println("接受到了客户端的数据:" +data);
socket.close();
server.close();
}
}
TCP练习1:
客户端发送的数据是由客户通过键盘输入,直到用户输入exit 发送数据结束,当服务端再接受到客户端的数据的时候 ,服务端也可以通过键盘输入给出响应。直到接受到客户端发送的 exit,则服务端停止接收数据
客户端:
public class Client {
public static void main(String[] args) throws IOException {
//创建服务端
Socket client = new Socket("127.0.0.1",10086);
//封装键盘输入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//向服务端写出数据的流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
//得到服务端的返回
BufferedReader severbr = new BufferedReader(new InputStreamReader(client.getInputStream()));
String line;
while((line = br.readLine()) != null){
//将数据写出到服务器
bw.write(line);
bw.newLine();
bw.flush();
//判断停止条件
if("exit".equals(line)){
break;
}
//输出服务器的返回1
String str = severbr.readLine();
System.out.println("服务端的响应数据:" + str);
}
//释放资源
client.close();
}
}
服务端:
public class Sever {
public static void main(String[] args) throws IOException {
//创建服务端
ServerSocket serverSocket = new ServerSocket(10086);
//接收客户端
Socket server = serverSocket.accept();
//创建客户端输入流
BufferedReader br = new BufferedReader(new InputStreamReader(server.getInputStream()));
//创建响应服务端的输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(server.getOutputStream()));
//封装键盘输入流
BufferedReader severbr = new BufferedReader(new InputStreamReader(System.in));
String line;
while((line = br.readLine()) != null){
System.out.println(line);
if("exit".equals(line)){
break;
}
System.out.print("对客户端的回应:");
String str = severbr.readLine();
bw.write(str);
bw.newLine();
bw.flush();
}
//释放资源
serverSocket.close();
}
}
TCP练习2:
实现客户端和服务端的文件传输
客户端:
public class Client {
public static void main(String[] args) throws IOException {
//创建服务端
Socket client = new Socket("127.0.0.1",10086);
//创建读取文件输入流
InputStream is = new FileInputStream("Java_17\\dir\\1.txt");
//创建输出流
OutputStream os = client.getOutputStream();
byte[] bytes = new byte[1024];
int len;
while((len = is.read(bytes)) != -1){
os.write(bytes,0,len);
}
//释放资源
is.close();
client.close();
}
}
服务端:
public class Server {
public static void main(String[] args) throws IOException {
//创建服务端
ServerSocket serverSocket = new ServerSocket(10086);
//获取客户端
Socket socket = serverSocket.accept();
//创建输入流
InputStream is = socket.getInputStream();
//创建输出流
OutputStream os = new FileOutputStream("Java_17\\dir\\2.txt");
byte[] bytes = new byte[1024];
int len;
while((len = is.read(bytes)) != -1){
os.write(bytes, 0,len);
}
//释放资源
serverSocket.close();
}
}
TCP练习3:
需求:一个服务端对多个客户端提供服务,此时的服务端将面临的 高并发问题,解决高并发,则必然需要使用多线程,因此此时的服务端必须是多线程的。
客户端:
public class Client {
public static void main(String[] args) throws IOException {
Socket client = new Socket("127.0.0.1", 10086);
//封装键盘输入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 使用输出流向服务端写出数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
BufferedReader serverResp = new BufferedReader(new InputStreamReader(client.getInputStream()));
String line;
while((line = br.readLine())!= null){
//将数据写出到服务端
bw.write(line);
bw.newLine();
bw.flush();
if("exit".equals(line)){
break;
}
String str = serverResp.readLine();
System.out.println("服务端的响应数据:" +str);
}
//释放资源
// is.close();关闭socket 则会自动关闭流
// os.close();
client.close();
}
}
服务端的线程类:
public class ServerThread implements Runnable{
private Socket socket;
public ServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try{
//获得输入流 来读取客户端发送过来的数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream());
String line;
Scanner sc = new Scanner(System.in);
while( (line = br.readLine())!= null){
System.out.println(line);
if("exit".equals(line)){
break;
}
System.out.print("请做出回应:");
String resp = sc.nextLine();
pw.println(Thread.currentThread().getName() +"---" +resp);
pw.flush();
}
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
服务端:
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(10086);
while(true){// 此时的服务端将面临多个客户端,这时需要不断的去和客户端建立链接
Socket socket = serverSocket.accept();
new Thread(new ServerThread(socket)).start();
}
}
}
枚举
当需要定义一组常量来表示具有相同类型的又穷对象时,此时就可以使用枚举。
使用类来模拟枚举的定义:
public class Season {
private final String NAME;//季节名称
private final String DESC;//季节描述
private Season(String name,String desc){
this.NAME = name;
this.DESC = desc;
}
public static final Season SPRING = new Season("春天","万物复苏");
public static final Season SUMMER = new Season("夏天","烈日炎炎");
public static final Season AUTUMN = new Season("秋天","果实累累");
public static final Season WINTER = new Season("冬天","白雪皑皑");
}
枚举定义:
public enum SeasonEnum {
// 在类的最前边定义所有的对象,多个对象之间使用逗号分隔,当对象定义结束之后,最后一个对象使用分号。
SPRING("春天","万物复苏"),
SUMMER("夏天","烈日炎炎"),
AUTUMN("秋天","果实累累"),
WINTER("冬天","白雪皑皑");
private final String NAME ;
private final String DESC ;
private SeasonEnum(String name,String desc){
this.NAME = name;
this.DESC = desc;
}
public String getNAME() {
return NAME;
}
public String getDESC() {
return DESC;
}
}
public class SeasonEnumTest {
public static void main(String[] args) {
System.out.println(SeasonEnum.SPRING.getNAME()+"----"+SeasonEnum.SPRING.getDESC());
System.out.println(SeasonEnum.SUMMER);
}
}
枚举实现接口:
public interface ColorInfo {
void show();
String getColor();
}
public enum Season implements ColorInfo {
SPRING("春天","绿意盎然"),
SUMMER("夏天","红日似火"),
AUTUMN("秋天","果实累累"),
WINTER("冬天","白雪皑皑");
private final String name;
private final String desc;
private Season(String name,String desc){
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public void show() {
System.out.println("季节名称:" +name +"季节描述:" +desc);
}
@Override
public String getColor() {
return "green";
}
}
枚举:一种特殊的 类
定义格式:
public enum 类名{
可列举的多个对象,之间使用逗号分隔,结尾用分号。
定义对象所有的属性,属性尽量定义为final;
private 带参构造
属性的getter方法
}
注解
从jdk1.5新增的一个对元数据的解释说明的标记(Annotation)
Annotation可以修饰类、包、构造器、方法、成员变量、局部变量等。注解就可以理解为一个修饰符
文档的说明:
生成文档注释的时候 ,我们希望可以在生成的文档中形成更好的说明,往往我们会使用一些文档的注解:
@author
标明开发该类模块的作者,多个作者之间使用,分割
@version
标明该类模块的版本
@see
参考转向,也就是相关主题
@since
从哪个版本开始增加的
@param
对方法中某参数的说明,如果没有参数就不能写
@return
对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception
对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写 其中
@param @return
和 @exception
这三个标记都是只用于方法的。
@param的格式要求:
@param
形参名 形参类型 形参说明
@return 的格式要求:
@return
返回值类型 返回值说明
@exception
的格式要求:
@exception
异常类型 异常说明
进行代码的格式校验:
@Override
校验方法是否满足重写的要求
@Deprecated
表示该方法已过时 不建议使用
@SuppressWarnings
抑制编译器警告