day15-网络编程
一:网络编程介绍
1.1 网络编程概述
-
计算机网络
是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
-
网络编程
在网络通信协议下,不同计算机上运行的程序,可以进行数据传输
1.2 网络编程三要素
-
IP地址
要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识
-
端口
网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识
-
协议
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议
1.2.1 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组十六进制数,这样就解决了网络地址资源数量不够的问题
- 特殊IP地址:
- 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
1.2.2 端口
- 端口概述:设备上应用程序的唯一标识
- 端口号:用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
- MySQL:3306
- TomCat:8080
1.2.3 协议
协议概述:计算机网络中,连接和通信的规则被称为网络通信协议
协议分类:DUP,TCP协议
UDP协议特点:面向无连接的协议,速度快,有大小限制,一次最多64KB,数据不安全,已丢失数据
UDP客户端单播代码实现步骤
- 创建一个站点new DatagramSocket
- 打包数据 创建一个DatagramPackage
- 由站点发送数据 send方法
- 释放资源
/** * UDP协议客户端 * * @author FaceHY * @version 1.0 * @since 1.5 */ public class Client { private static Scanner sc = new Scanner(System.in);
public static void main(String[] args) throws IOException { start(); }
private static void start() throws IOException { // 1. 创建DatagramSocket对象 DatagramSocket ds = new DatagramSocket(); // 2. 创建DatagramPackage对象,参数1:字节数组;参数2:字节数组长度;参数3:IP;参数4:端口号 System.out.println("请输入:"); String s = sc.nextLine(); byte[] bytes = s.getBytes(); InetAddress address = InetAddress.getByName("127.0.0.1"); int port = 32145; DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port); // 3. 发送 ds.send(dp); // 4. 释放资源 ds.close(); } } ~~~ ~~~
UDP服务器端单播代码实现步骤
- 创建Socket对象
- 打包数据
- 接受数据receive ,将接收的数据存入箱子里面
- 从箱子中获取数据
- 释放资源
/** * UDP协议服务器端 * * @author FaceHY * @version 1.0 * @since 1.5 */ public class Server { public static void main(String[] args) throws IOException { start(); }
private static void start() throws IOException {
// 1. 创建DatagramSocket对象,关联端口号
DatagramSocket ds = new DatagramSocket(32145);
// 2. 创建DatagramPacket对象,将接收的数据存入
DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
// 3. 接受数据
ds.receive(dp);
// 4. 装换为字符串并打印
int length = dp.getData().length;
System.out.println(new String(dp.getData(), 0, length));
// 5. 关流
ds.close();
}
}
TCP协议特点:与UDP想反,是面向连接的协议,速度相对较慢,没有大小限制,数据相对安全
TCP客户端代码实现步骤
- 创建socket对象,向服务器发送请求
- 通过socket对象获取字节流对象
- 调用字节流对象中的write方法开始写数据
- 释放资源
- 注意:如果写的文件中是含有中文的纯文本文件,则需要将字节流转换为字符流
- socket对象中并不能直接get到字符流,自能获取字节流,就需要通过转换流转换一下
- 先将字节流转换为转换流对象
- 再将转换流对象转换为字符流对象
- 最后为了效率,将字符流对象转换为字符缓冲流对象
/** * TCP协议客户端 * * @author FaceHY * @version 1.0 * @since 1.5 */ public class Client { public static void main(String[] args) throws IOException { start(); }
private static void start() throws IOException {
// 1. 创建socket对象,关联IP和端口号
Socket socket = new Socket(“127.0.0.1”, 23564);
// 2. 创建字符输出流将socket获取的字节流转换为字符流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
// 3. 读写操作
bw.write(“你好啊”);
bw.newLine();
bw.flush();
// 4. 关流
bw.close();
socket.close();
}
}
TCP服务器端代码实现步骤
- 创建ServerSocket对象
- 调用ServerSocket的accept方法 (响应客户端的请求)
- 通过accept方法的返回值socket对象获取字节流对象
- 开始读写操作
- 释放资源
- 注意点:accept和read如果没有读到结束标记,会进入死循环
1.3 InetAddress类的使用
InetAddress:此类表示Internet协议(IP)地址
相关方法
方法名 | 说明 |
---|---|
static InetAddress getByName(String host) | 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回文本显示中的IP地址字符串 |
1.4 服务器多线程优化
package networkpractice.test3;
import java.io.*;
import java.net.Socket;
import java.util.UUID;
public class MyThread implements Runnable{
BufferedInputStream bis = null;
BufferedWriter bw = null;
private Socket socket;
public MyThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 1. 创建字节缓冲输出流,并关联文件
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\" + UUID.randomUUID().toString() + ".txt"));
// 2. 通过socket获取字节输入流
bis = new BufferedInputStream(socket.getInputStream());
// 3. 开始读写操作,将客户端发送的文件读取到服务器本地
int b;
while ((b = bis.read()) != -1){
bos.write(b);
}
bos.close();
// 4. 给出反馈
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功!");
bw.flush();
bw.newLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if ( bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
}
客户端
package networkpractice.test3;
import java.io.*;
import java.net.Socket;
/*
客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
// 1. 创建socket对象,IP地址为本机IP地址并设置端口
Socket socket = new Socket("127.0.0.1",11111);
// 2. 手动创建字节输入缓冲流并关联文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\stu.txt"));
// 3. 通过socket对象获取字节输出流,并转换为字符缓冲流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
// 4. 读写操作
int b;
while ((b = bis.read()) != -1){
bos.write(b);
}
bos.flush();
socket.shutdownOutput();
bis.close();
// 5. 接收服务器的反馈并读取
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
// 6. 释放资源
br.close();
socket.close();
}
}
服务器端
package networkpractice.test3;import java.io.*;import java.net.ServerSocket;import java.net.Socket;import java.util.UUID;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.Executors;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;/* 服务器端 */public class Server { public static void main(String[] args) throws IOException { // 1. 创建serverSocket对象 ServerSocket server = new ServerSocket(11111); // 2. 创建自定义线程池 ThreadPoolExecutor pool = new ThreadPoolExecutor( 2, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() ); while (true) { // 3. 响应客户端的请求 Socket socket = server.accept(); // 4. 创建线程任务对象 MyThread mt = new MyThread(socket); // 5. 将线程任务提交给线程池 pool.submit(mt); } }}