本文由 程序喵正在路上 原创,CSDN首发!
系列专栏:Java从入门到大牛
首发时间:2023年11月23日
欢迎关注点赞收藏留言
什么是网络编程?
可以让设备中的程序与网络上的其他设备中的程序进行数据交互,也就是实现网络通信
Java的 java.net.* 包下提供了网络编程的解决方案
基本的通信架构有两种形式:CS架构(Client客户端/Server服务端)、BS架构(Brower浏览器/Server服务端)
无论是CS架构,还是BS架构的软件都必须依赖网络编程
IPv6
IP域名
由于IP地址比较难记住,所以有了IP域名,例如 www.baidu.com 等等
公网IP,内网IP
特殊IP地址
IP常用命令
InetAddress
应用
import java.net.InetAddress;
/**
* 目标:掌握InetAddress类的使用
*/
public class InetAddressTest {
public static void main(String[] args) throws Exception {
// 1、获取本机IP地址对象的
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
// 2、获取指定IP或者域名的IP地址对象。
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
// ping www.baidu.com
System.out.println(ip2.isReachable(6000));
}
}
端口
分类
注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则会出错
通信协议
开放式网络互联标准:OSI网络参考模型
传输层的两个通信协议
UDP协议
TCP协议
TCP协议:三次握手建立可靠连接
TCP协议:四次挥手断开连接
Java提供了一个 java.net.DatagramSocket 类来实现UDP通信
DatagramSocket: 用于创建客户端、服务端
实战:使用UDP通信实现发送消息、接收消息
Client.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 目标:完成UDP通信快速入门:实现1发1收。
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1、创建客户端对象
DatagramSocket socket = new DatagramSocket();
// 2、创建数据包对象封装要发出去的数据
byte[] bytes = "我是快乐的客户端,我爱你abc".getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length
, InetAddress.getLocalHost(), 6666);
// 3、开始正式发送这个数据包的数据出去了
socket.send(packet);
System.out.println("客户端数据发送完毕~~~");
socket.close(); // 释放资源!
}
}
Server.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* 目标:完成UDP通信快速入门-服务端开发
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
// 1、创建一个服务端对象,注册端口
DatagramSocket socket = new DatagramSocket(6666);
// 2、创建一个数据包对象,用于接收数据的
byte[] buffer = new byte[1024 * 64]; // 64KB.
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 3、开始正式使用数据包来接收客户端发来的数据
socket.receive(packet);
// 4、从字节数组中,把接收到的数据直接打印出来
// 接收多少就倒出多少
// 获取本次数据包接收了多少数据
int len = packet.getLength();
String rs = new String(buffer, 0 , len);
System.out.println(rs);
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
socket.close(); // 释放资源
}
}
先启动服务端,再启动客户端
客户端可以反复发送数据
接收端可以反复接收数据
Client.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
/**
* 目标:完成UDP通信快速入门:实现客户端反复的发
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1、创建客户端对象
DatagramSocket socket = new DatagramSocket();
// 2、创建数据包对象封装要发出去的数据
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 一旦发现用户输入的exit命令,就退出客户端
if("exit".equals(msg)){
System.out.println("欢迎下次光临!退出成功!");
socket.close(); // 释放资源
break; // 跳出死循环
}
byte[] bytes = msg.getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length
, InetAddress.getLocalHost(), 6666);
// 3、开始正式发送这个数据包的数据出去了
socket.send(packet);
}
}
}
Server.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* 目标:完成UDP通信快速入门-服务端反复的收
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("----服务端启动----");
// 1、创建一个服务端对象
DatagramSocket socket = new DatagramSocket(6666);
// 2、创建一个数据包对象,用于接收数据的
byte[] buffer = new byte[1024 * 64]; // 64KB.
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
// 3、开始正式使用数据包来接收客户端发来的数据
socket.receive(packet);
// 4、从字节数组中,把接收到的数据直接打印出来
// 接收多少就倒出多少
// 获取本次数据包接收了多少数据
int len = packet.getLength();
String rs = new String(buffer, 0 , len);
System.out.println(rs);
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
System.out.println("--------------------------------------");
}
}
}
在这个基础上,我们还可以实现多个客户端同时向服务端发送消息
首先,我们需要在IDEA里面修改一下客户端这个程序的配置,让它可以同时运行多个客户端,具体步骤如下
1、在IDEA右上角找到这个,点击进入
2、如图操作,注意是客户端
3、多次执行 Client.java 这个程序,我们就可以得到多个客户端
Java提供了一个 java.net.Socket 类来实现TCP通信
TCP通信之客户端开发
客户端发送消息
Client.java
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* 目标:完成TCP通信快速入门-客户端开发:实现1发1收
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1、创建Socket对象,并同时请求与服务端程序的连接
Socket socket = new Socket("127.0.0.1", 8888);
// 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
OutputStream os = socket.getOutputStream();
// 3、把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
// 4、开始写数据出去了
dos.writeUTF("在一起,好吗?");
dos.close();
socket.close(); // 释放连接资源
}
}
TCP通信之服务端程序的开发
服务端接收消息
Server.java
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把原始的字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
// 5、使用数据输入流读取客户端发送过来的消息
String rs = dis.readUTF();
System.out.println(rs);
// 其实我们也可以获取客户端的IP地址
System.out.println(socket.getRemoteSocketAddress());
dis.close();
socket.close();
}
}
使用TCP通信实现:多发多收消息
Client.java
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 目标:完成TCP通信快速入门-客户端开发:实现客户端可以反复的发消息出去
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1、创建Socket对象,并同时请求与服务端程序的连接。
Socket socket = new Socket("127.0.0.1", 8888);
// 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
OutputStream os = socket.getOutputStream();
// 3、把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 一旦用户输入了exit,就退出客户端程序
if("exit".equals(msg)){
System.out.println("欢迎您下次光临!退出成功!");
dos.close();
socket.close();
break;
}
// 4、开始写数据出去了
dos.writeUTF(msg);
dos.flush();
}
}
}
Server.java
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 目标:完成TCP通信快速入门-服务端开发:实现服务端反复发消息
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把原始的字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
// 5、使用数据输入流读取客户端发送过来的消息
String rs = dis.readUTF();
System.out.println(rs);
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "离线了!");
dis.close();
socket.close();
break;
}
}
}
}
如果我们像刚才UDP通信那样,同时启动多个客户端,你会发现服务端只能接收到一个客户端发送的消息,那么我们该如何实现服务端与多个客户端同时通信呢
目前我们开发的服务端程序,是不可以与多个客户端同时通信的,因为服务端只有一个主线程,只能处理一个客户端的消息
Client.java 不变
添加一个 ServerReaderThread 线程类,每个线程处理一个客户端
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch (Exception e) {
System.out.println("有人下线了:" + socket.getRemoteSocketAddress());
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Server.java
import java.net.ServerSocket;
import java.net.Socket;
/**
* 目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通信
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
System.out.println("有人上线了:" + socket.getRemoteSocketAddress());
// 3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
什么是群聊?怎么实现?
TCP通信-端口转发
代码实现
改进 Client.java
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 目标:完成TCP通信快速入门-客户端开发:实现客户端可以反复的发消息出去
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1、创建Socket对象,并同时请求与服务端程序的连接。
Socket socket = new Socket("127.0.0.1", 8888);
// 改进:创建一个独立的线程,负责随机从socket中接收服务端发送过来的消息
new ClentReaderThread(socket).start();
// 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
OutputStream os = socket.getOutputStream();
// 3、把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 一旦用户输入了exit,就退出客户端程序
if("exit".equals(msg)){
System.out.println("欢迎您下次光临!退出成功!");
dos.close();
socket.close();
break;
}
// 4、开始写数据出去了
dos.writeUTF(msg);
dos.flush();
}
}
}
添加 ClientReaderThread.java
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
// 该线程不停接收服务端转发过来的消息并打印,相当于收到群聊里别人的信息
// 思路和服务端接收消息差不多
public class ClentReaderThread extends Thread{
private Socket socket;
public ClentReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch (Exception e) {
System.out.println("自己下线了:" + socket.getRemoteSocketAddress());
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
改进 Server.java
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* 目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通信
*/
public class Server {
// 改进:添加一个集合保存在线的所有客户端,便于转发消息
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
onLineSockets.add(socket); // 有客户端上线就添加
System.out.println("有人上线了:" + socket.getRemoteSocketAddress());
// 3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理。
new ServerReaderThread(socket).start();
}
}
}
改进 ServerReaderThread.java
import java.io.*;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
// 把这个消息分发给全部客户端进行接收。
sendMsgToAll(msg);
} catch (Exception e) {
System.out.println("有人下线了:" + socket.getRemoteSocketAddress());
Server.onLineSockets.remove(socket); // 客户端下线就删除
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 添加消息转发方法
private void sendMsgToAll(String msg) throws IOException {
// 发送给全部在线的socket管道接收。
for (Socket onLineSocket : Server.onLineSockets) {
OutputStream os = onLineSocket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF(msg);
dos.flush();
}
}
}
需求描述
要求从浏览器中访问服务器,并立即让服务器响应一个很简单的网页给浏览器展示
BS架构的基本原理
BS架构的客户端就是浏览器,客户从浏览器访问服务器的方法就是通过网址来访问,一般的格式是 http://服务器IP:服务器端口
注意:服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不会识别返回的数据,也就是说,服务端返回数据不能还是用以前那种打印语句直接打印
HTTP协议规定:响应给浏览器的数据格式必须满足如下格式
需求实现
Server.java,和前面的一样
import java.net.ServerSocket;
import java.net.Socket;
/**
* 目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通信。
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
System.out.println("有人上线了:" + socket.getRemoteSocketAddress());
// 3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理。
new ServerReaderThread(socket).start();
}
}
}
改进 ServerReaderThread.java
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
// 立即响应一个网页内容给浏览器展示。
try {
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println(); // 必须换行
ps.println("66666666666");
// 访问完就要关闭
ps.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
效果
随便打开一个浏览器,输入网址 127.0.0.1:8080
回车即可,其中 127.0.0.1 代表本机IP,8080 是我们在服务端指定的端口
改进
在上面的程序中,客户端的每次请求都会开一个新的线程,这样做好不好呢?
其实是不好的,当访问人数特别多时,也就是高并发时,就会很容易宕机
很明显,我们都知道要使用线程池来进行优化
优化后的代码
Server.java
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通信。
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口
ServerSocket serverSocket = new ServerSocket(8080);
// 创建出一个线程池,负责处理通信管道的任务
ThreadPoolExecutor pool = new ThreadPoolExecutor(16 * 2, 16 * 2, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
while (true) {
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
// 3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
pool.execute(new ServerReaderRunnable(socket));
}
}
}
ServerReaderRunnable.java
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
// 立即响应一个网页内容给浏览器展示
try {
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println(); // 必须换行
ps.println("