目录
一、什么是网络编程
在网络通信协议下,不同的计算机上运行的程序,进行的数据传输。
- 应用场景:即时通信、网络对战、金融证券、国际贸易、邮件等等。不管是什么样的场景,都是计算机跟计算机之间通过网络进行的数据传输。
- Java中可以使用 java.net 包下的技术轻松开发出常见的网络应用程序。
1. 常见的软件架构
在用户本地需要下载并安装客户端程序,在远程有一个服务器端程序。 | ![]() |
只需要一个浏览器,用户通过不同的网址。客户访问不同的服务器。 | ![]() |
CS:客户端服务端模式需要开发客户端。
BS:浏览器服务端模式不需要开发客户端。
CS:适合定制专业化的办公类软件如:IDEA、网游。
BS:适合移动互联网应用,可以在任何地方随时访问的系统。
二、网络编程三要素
- IP:设备在网络中的地址,是唯一的标识。
- 端口号:应用程序在设备中的唯一的标识。
- 协议:数据在网络中传输的规则,常见的协议有UDP、TCP、http、https、ftp。
1. IP
全称:Internet Protocol,是互联网协议地址,也成IP地址。
IP是分配给上网设备的数字标签。
常见的IP分类为:
- IPv4,IPv6
1.1 IPv4
全称:Internet Protocol version 4,互联网通信协议第四版
采用32位地址长度,分为4组。
IPv4的地址分类形式:
- 公司地址(万维网使用)和私有地址(局域网使用)。
- 192.168. 开头的就是私有地址,范围即为192.168.0.0 --- 192.168.255.255,专门为组织结构内部使用,以此节省IP。
- 特殊IP地址:127.0.0.1,也可以是localhost:是回送地址也成本地回环地址,也成本机IP,永远只会寻找当前所在本机。
- 常见的CMD命令:
ipconfig 查看本机IP地址 ping 检查网络是否连通
1.2 IPv6
全称:Internet Protocol version 6,互联网通信协议第六版
由于互联网的蓬勃发展,IP地址的需求量愈来愈大,而IPv4的模式下IP的总数是有限的。
采用128位地址长度,分为8组。
2. 端口号
应用程序在设备中唯一的标识。
端口号:
- 由两个字节表示的整数,取值范围:0~65535。
- 其中0~1023之间的端口号用于一些知名的网络服务或者应用。
- 我们自己使用1024以上的端口号就可以。
注意:一个端口号只能被一个应用程序使用。
3. 协议
在计算机网络中,连接和通信的规则被称为网络通信协议。
- OS\参考模型:世界互联协议标准,全球通信规范,单模型过于理想化,未能在因特网上进行广泛推广。
- TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
OSI参考模型 TCP/IP参考模型 TCP/IP参考模型各层对应协议 面向哪些 应用层 应用层 HTTP、FTP、Telnet、DNS... 一把是应用程序需要关注的。如浏览器,邮箱。程序员一般在这一层开发 表示层 会话层 传输层 传输层 TCP、UDP、... 选择传输使用的TCP,UDP协议 网络层 网络层 lP、ICMP、ARP... 封装自己的IP,对方的IP等信息 数据链路层
物理+数据链路层硬件设备。
010100101010100101010...
转换成二进制利用物理设备传输 物理层
3.1 UDP协议
- 用户数据报协议(User Datagram Protocol)
- UDP是面向无连接通信协议。速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据。
3.2 TCP协议
- 传输控制协议TCP(Transmission Control Protocol)
- TCP协议是面向连接的通信协议。速度慢,没有大小限制,数据安全。
三、UDP通信程序
1. 发送数据
- 创建发送端的DatagramSocket对象
- 数据打包(DatagramPacket)
- 发送数据
- 释放资源
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");
int port = 10086;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
// 3.发送数据
ds.send(dp);
// 4.释放资源
ds.close();
}
}
2. 接收数据
- 创建接收端的DatagramSocket对象
- 接收打包好的数据
- 解析数据包
- 释放资源
public class ReceiveMessageDemo {
public static void main(String[] args) throws IOException {
// 1.创建DatagramSocket对象
// 细节:
// 在接收的时候,一定要绑定端口
// 而且绑定的端口一定要与发送的端口保持一致
DatagramSocket ds = new DatagramSocket();
// 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();
int port = dp.getPort();
System.out.println("接收到数据" + new String(data, 0, len));
System.out.println("该数据是从" + address
+ "这台电脑中的" + port + "这个端口发出的");
// 4.释放资源
ds.close();
}
}
3. UDP三种通信方式
- 单播:以前的代码就是单播。
- 组播:组播地址:224.0.0.0 ~ 239.255.255.255 (其中224.0.0.0 ~ 224.0.0.255 为预留的组播地址)
- 广播:广播地址:255.255.255.255
四、TCP通信程序
- TCP通信协议是一种可靠的网络协议,它在通信两端各建立一个Socket对象,通信之前要保障连接已经建立。
- 通过Socket产生IO流来进行网络通信。
代码书写步骤:
客户端 服务端
- 创建客户端的Socket对象(Socket)与指定服务端连接。
- 获取输出流,写数据。
- 释放资源
- 创建服务器端的Socket对象(ServerSocket)
- 监听客户端连接,返回Socket对象
- 获取输入流,读数据,并把数据显示在控制台上
- 释放资源
//Client.java
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
//TCP协议 发送数据
//1.创建客户端的Socket对象
//细节:在创建对象同时会连接服务端,如果连不上代码会报错
Socket sc = new Socket("127.0.0.1",10000);
//2.获取输出流,写数据
OutputStream os = sc.getOutputStream();
os.write("你好".getBytes());
//3.释放资源
os.close();
sc.close();
}
}
//Server.java
public class Server {
public static void main(String[] args) throws IOException {
// TCP协议 接收对象
//1.创建服务器端的ServerSocket对象
ServerSocket ss = new ServerSocket(10000);
//2.监听客户端连接,返回Socket对象
Socket sc = ss.accept();
//3.获取输入流,读数据,并把数据显示在控制台上
InputStream is = sc.getInputStream();
int b;
while((b = is.read()) != -1) {
System.out.println((char)b);
}
//4.释放资源
sc.close();
ss.close();
}
}
4.1 中文乱码问题
os.write("你好你好".getBytes()); //12字节 InputStream is = sc.getInputStream(); int b; while((b = is.read()) != -1) { System.out.println((char)b); } //一个一个字节的读
所以我们要将字节流换成字符流:
InputStream is = sc.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr);
4.2 三次握手和四次挥手
三次握手 | 四次挥手 |
确保连接建立 | 确保连接断开,且数据处理完毕 |
![]() | ![]() |
五、综合练习
1. 多收多发
客户端:多次发送数据
服务器:接收多次接收数据,并打印
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
//1.创建Socket对象,并连接服务端
Socket socket = new Socket("127.0.0.1",10000);
//2.写出数据
Scanner sc = new Scanner(System.in);
OutputStream os = socket.getOutputStream();
while(true) {
System.out.println("请输入您要输入的数据:");
String str = sc.nextLine();
if ("n".equals(str)) {
break;
}
os.write(str.getBytes());
}
//3.释放资源
socket.close();
}
}
public class Server {
public static void main(String[] args) throws IOException {
//1.创建ServerSocket对象指定端口
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端连接
Socket socket = ss.accept();
//3.读取数据
InputStreamReader isr= new InputStreamReader(socket.getInputStream());
int b;
while((b = isr.read()) !=-1) {
System.out.println((char)b);
}
//4.释放资源
socket.close();
ss.close();
}
}
2. 接收和反馈
客户端:发送一条数据,接收服务端反馈的消息并打印
服务器:接收数据并打印,再给客户端反馈消息
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
//1.创建Socket对象,并连接服务端
Socket socket = new Socket("127.0.0.1",10000);
//2.写出数据
String str = "见到你很高兴!";
OutputStream os = socket.getOutputStream();
os.write(str.getBytes());
//写一个结束标记
socket.shutdownOutput();
//3.接收服务端回写数据
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int b;
while((b = isr.read()) != -1) {
System.out.println((char)b);
}
//4.释放资源
socket.close();
}
}
public class Server {
public static void main(String[] args) throws IOException {
//1.创建ServerSocket对象指定端口
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端连接
System.out.println("等待客户端连接");
Socket socket = ss.accept();
System.out.println("连接成功");
//3.读取数据
InputStreamReader isr= new InputStreamReader(socket.getInputStream());
int b;
System.out.println("准备读取客户端发送过来的数据");
//细节:
//read方法会从连接通道中读取数据
//但是,需要一个结束标记,此处的循环才会停止
//否则,程序会一直停在read方法这,等待读取下面的数据
while((b = isr.read()) !=-1) {
System.out.println("正在读取数据");
System.out.println((char)b);
}
System.out.println("数据读取完毕,准备回写");
//4.回写数据
String str = "到底有多开心呢?";
OutputStream os = socket.getOutputStream();
os.write(str.getBytes());
System.out.println("回写成功");
//5.释放资源
socket.close();
ss.close();
}
}
3. 上传文件
客户端:将本地文件上传到服务器。接收服务器的反馈。
服务器:接收客户端上传的文件,上传完毕之后给出反馈。
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
// 1.创建Socket对象,并连接服务端
Socket socket = new Socket("127.0.0.1", 10000);
// 2.读取本地文件中的数据,并写到服务端中
BufferedInputStream bis = new BufferedInputStream
(new FileInputStream("java02//a.png"));
BufferedOutputStream bos = new
BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read()) != -1) {
bos.write(bytes, 0, len);
}
//结束标记
socket.shutdownOutput();
// 3.接收服务端回写数据
BufferedReader br = new BufferedReader
(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();
System.out.println(line);
// 4.释放资源
socket.close();
}
}
public class Server {
public static void main(String[] args) throws IOException {
//1.创建ServerSocket对象指定端口
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端连接
Socket socket = ss.accept();
//3.读取数据并保存到本地文件
BufferedInputStream bis = new BufferedInputStream
(socket.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream
(new FileOutputStream("java02\\b.png"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read()) != -1) {
bos.write(bytes, 0, len);
}
//4.回写数据
BufferedWriter bw = new BufferedWriter
(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
//5.释放资源
socket.close();
ss.close();
}
}
4. 上传文件(文件名重复问题)
解决上一题文件名重复问题。
解决方法:
//Server.java
BufferedInputStream bis =
new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos =
new BufferedOutputStream(new FileOutputStream("java02\\"+name+".png"));
public class UUIDTest {
public static void main(String[] args) {
String str = UUID.randomUUID().toString().replace("-", "");
System.out.println(str);
}
}
5. 上传文件(多线程版)
想要服务器不停止,能接收很多用户上传的图片,该怎么做呢?
提示:可以用循环或者多线程。
但是循环不合理,最优解法是(循环+多线程)改写。
public class Server {
public static void main(String[] args) throws IOException {
// 1.创建ServerSocket对象指定端口
ServerSocket ss = new ServerSocket(10000);
while (true) {
// 2.等待客户端连接
Socket socket = ss.accept();
//开启线程
new Thread(new MyRunnable(socket)).start();
}
}
}
public class MyRunnable implements Runnable {
Socket socket;
public MyRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 3.读取数据并保存到本地文件
BufferedInputStream bis = new
BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream
(new FileOutputStream("java02\\" + name + ".png"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read()) != -1) {
bos.write(bytes, 0, len);
}
// 4.回写数据
BufferedWriter bw = new BufferedWriter
(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5.释放资源
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
6. 上传文件(线程池优化)
频繁创建线程并销毁非常浪费系统资源,所以需要用线程池优化
public class Server {
public static void main(String[] args) throws IOException {
// 创建线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, // 核心线程池
16, // 线程池总大小
60, // 空闲时间
TimeUnit.SECONDS, // 空闲时间(单位)
new ArrayBlockingQueue<>(2), // 队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 堵塞队列
);
// 1.创建ServerSocket对象指定端口
ServerSocket ss = new ServerSocket(10000);
while (true) {
// 2.等待客户端连接
Socket socket = ss.accept();
// 开启线程
// new Thread(new MyRunnable(socket)).start();
pool.submit(new MyRunnable(socket));
}
}
}