一. 网络编程
1. 网络编程:可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信)
2. Java.net.*包下提供了网络编程的解决方法
二. 基本的通信架构
1. CS架构(Clicnt客户端/Server服务端);如微信
2. BS架构(Browser浏览器/Server服务端);如浏览器里面的网页
三. 网络通信三要素
1. IP地址
① IP(Internet Protocol):全称"互联网协议地址",设备在网络中的地址,是唯一的标识
② IP地址有两种形式:IPv4、IPv6
③ 公网IP: 是可以连接互联网的IP地址;内网IP:也叫局域网IP,只能组织机构内部使用
④ 127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机
⑤ IP常用命令:
ipconfig:查看本机IP地址;ping IP地址:检查网络是否连通;
⑥ Java InetAddress类:代表IP地址
InetAddress的常用方法 | 说明 |
public static InetAddress getLocalHost() | 获取本机IP,会以一个inetAddress的对象返回 |
public static InetAddress getByName(String host) | 根据IP地址或者域名,返回一个inetAddress对象 |
public String getHostName() | 获取该IP地址对象对应的主机名 |
public String getHostAddress() | 获取该IP地址对象中的IP地址信息 |
public boolean isReadchable(int timeout) | 在指定毫秒内,判断主机与该IP地址是否能连通 |
public static void main(String[] args) throws Exception {
//public static InetAddress getLocalHost() 获取本机IP,会以一个inetAddress的对象返回
InetAddress address = InetAddress.getLocalHost();
System.out.println(address);
//public String getHostName() 获取该IP地址对象对应的主机名
System.out.println(address.getHostName());
//public String getHostAddress() 获取该IP地址对象中的IP地址信息
System.out.println(address.getHostAddress());
//public static InetAddress getByName(String host) 根据IP地址或者域名,返回一个inetAddress对象
InetAddress address2 = InetAddress.getByName("www.baidu.com");
System.out.println(address2);
System.out.println(address2.getHostName());
System.out.println(address2.getHostAddress());
//public boolean isReadchable(int timeout) 在指定毫秒内,判断主机与该IP地址是否能连通
System.out.println(address2.isReachable(100));
}
2. 端口号
应用程序在设备中的唯一标识;标记正在计算机设备上运行的应用程序的,被规定为1个16位的二进制,范围是0-65535。
分类:
周知端口:0-1023,被预先定义的知名应用占用(如HTTP占用80,FTP占用21)
注册端口:1024-49151,分配给用户进程或某些应用程序
动态端口:49152-65535,一般不固定分配给某种进程,进行动态分配
我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则会出错
3. 协议
通信协议:网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议;
开放式网络互联标准:OSI网络参考模型
TCP/IP网络模型:事实上国际标准
传输层的两个通信协议:
① UDP(User Datagram Protocol):用户数据报协议;
特点:无连接、不可靠通信、通信效率高(语音通话、视频直播);不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口、目的地IP、程序端口和数据(限制在64KB内)等;发送方不管对方是否在线,数据在中间是否丢失,如果接收方收到数据也不返回确认,所以是不可靠的。
② TCP(Transmission Control Protocol):传输控制协议
特点:面向连接、可靠通信。TCP的最终目的:要保证在不可靠的信道上实现可靠的传输;三个步骤: 三次握手建立连接;传输数据进行确认;四次挥手断开连接
三次握手建立连接:确定通信双方,收发消息都是正常无问题的!(全双工)
传输数据进行确认:
四次挥手断开连接:确保双方数据的收发都已经完成。
四. UDP通信
1. 特点:无连接、不可靠通信、通信效率高(语音通话、视频直播);不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口、目的地IP、程序端口和数据(限制在64KB内)等;发送方不管对方是否在线,数据在中间是否丢失,如果接收方收到数据也不返回确认,所以是不可靠的
2. Java提供了一个Java.net.DatagramSocket类来实现UDP通信。
DatagramSocket:用于创建客户端、服务端
构造器 | 说明 |
public DatagramSocket() | 创建客户端的Socket对象,系统会随机分配一个端口号 |
public DatagramSocket(int port) | 创建服务端的Socket对象,并指定端口号 |
方法 | 说明 |
public void send(DatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 使用数据包接收数据 |
DatagramPacket:创建数据包
构造器 | 说明 |
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发出去的数据包对象 |
public DatagramPacket(byte[] buf, int length) | 创建用来接收数据的数据包 |
方法 | 说明 |
public int getLength() | 获取数据包,实际接收到的字节个数 |
一发一收
/*
* UDP-客户端 一发一收
* */
public class Client {
public static void main(String[] args) throws Exception {
System.out.println("客户端启动");
//1. 创建客户端对象
//public DatagramSocket() 创建客户端的Socket对象,系统会随机分配一个端口号
DatagramSocket socket = new DatagramSocket();
//2. 创建数据包对象 封装要发出去的数据
//public DatagramPacket(byte[] buf, int length, InetAddress address, int port) 创建发出去的数据包对象
/*
* byte[] buf 要发出去的数据
* int length 发出去的数据大小
* InetAddress address 服务端的IP地址 找到服务端主机
* int port 服务端的IP地址 的端口号
* */
byte[] buf ="卡莎:艾卡西亚暴雨".getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 9999);
//3. 开始发送数据包
socket.send(packet);
System.out.println("客户端发送数据完毕");
//4. 释放资源
socket.close();
}
}
/*
* UDP-服务端 一发一收
* */
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
//1. 创建服务端对象
//public DatagramSocket(int port) 创建服务端的Socket对象,并指定端口号
DatagramSocket socket = new DatagramSocket(9999);
//创建数据包对象 接收数据
//public DatagramPacket(byte[] buf, int length) 创建用来接收数据的数据包
byte[] buf = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 接收数据
socket.receive(packet);
//4.打印接收到的数据
int len = packet.getLength();
String rs = new String(buf, 0, len);
System.out.println(rs);
//获取客户端的IP地址
System.out.println(packet.getAddress());
//获取客户端的端口
System.out.println(packet.getPort());
//5.关闭
socket.close();
}
}
多发多收
/*
* UDP-客户端 多发多收
* */
public class Client1 {
public static void main(String[] args) throws Exception {
System.out.println("客户端启动");
//1. 创建客户端对象
//public DatagramSocket() 创建客户端的Socket对象,系统会随机分配一个端口号
DatagramSocket socket = new DatagramSocket();
//2. 创建数据包对象 封装要发出去的数据
//public DatagramPacket(byte[] buf, int length, InetAddress address, int port) 创建发出去的数据包对象
/*
* byte[] buf 要发出去的数据
* int length 发出去的数据大小
* InetAddress address 服务端的IP地址 找到服务端主机
* int port 服务端的IP地址 的端口号
* */
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入消息:");
String msg = scanner.nextLine();
//输入“exit”表示退出
if ("exit".equals(msg)) {
System.out.println("退出成功");
socket.close();
break;
}
byte[] data = msg.getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getLocalHost(), 9999);
//3. 开始发送数据包
socket.send(packet);
}
}
}
/*
* UDP-服务端 多发多收
* */
public class Server1 {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
//1. 创建服务端对象
//public DatagramSocket(int port) 创建服务端的Socket对象,并指定端口号
DatagramSocket socket = new DatagramSocket(9999);
//创建数据包对象 接收数据
//public DatagramPacket(byte[] buf, int length) 创建用来接收数据的数据包
byte[] buf = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
while (true) {
//3. 接收数据
socket.receive(packet);
//4.打印接收到的数据
int len = packet.getLength();
String rs = new String(buf, 0, len);
System.out.println(rs);
//获取客户端的IP地址
System.out.println(packet.getAddress());
//获取客户端的端口
System.out.println(packet.getPort());
System.out.println("----------------------");
}
}
}
五. TCP通信
特点:面向连接、可靠通信。通信双方事先会采用“三次握手”方式来建立连接,实现端到端的通信;底层能保证数据成功传给服务端
客户端:Java.net.Socket类实现TCP通信。
客户端构造器 | 说明 |
public Socket(String host, int port) | 根据指定的服务器IP、端口号请求与服务端建立连接,连接通过,就获得了客户端的socket |
方法 | 说明 |
public OutputStream getOutputStream() | 获得字节输出流对象 |
public InputStream getInputStream() | 获得字节输入流对象 |
服务端:Java.net.ServerSocket类实现
构造器 | 说明 |
public ServerDocket(int port) | 为服务端程序注册端口 |
方法 | |
public Socket accept | 阻塞等待客户端的连接请求,一旦与某个客户端连接成功,则返回服务端的Socket对象 |
一发一收
/*
* TCP 服务端 一发一收
* */
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
//创建服务端对象
ServerSocket serverSocket = new ServerSocket(9999);
//等待客户端连接请求
Socket socket = serverSocket.accept();
//接收客户端的数据
InputStream inputStream = socket.getInputStream();
//把原始的字节输入流包装成数据输入流
DataInputStream dataInputStream = new DataInputStream(inputStream);
String str = dataInputStream.readUTF();
System.out.println(str);
//获取客户端的IP地址
System.out.println(socket.getRemoteSocketAddress());
//释放资源
dataInputStream.close();
socket.close();
}
}
/*
* TCP 客户端 一发一收
* */
public class Client {
public static void main(String[] args) throws Exception {
System.out.println("客户端启动");
//创建Socket对象
Socket socket = new Socket("127.0.0.1", 9999);
//字节输出流 发送数据
OutputStream os = socket.getOutputStream();
//把低级字节输出流包装成高级的数据输出流
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("卡莎、;艾卡西亚");
dos.close();
socket.close();
System.out.println("消息发送成功");
}
}
多发多收
/*
* TCP 服务端 多发多收
* */
public class Server1 {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
//创建服务端对象
ServerSocket serverSocket = new ServerSocket(9999);
//等待客户端连接请求
Socket socket = serverSocket.accept();
//接收客户端的数据
InputStream inputStream = socket.getInputStream();
//把原始的字节输入流包装成数据输入流
DataInputStream dataInputStream = new DataInputStream(inputStream);
while (true) {
try {
String str = dataInputStream.readUTF();
System.out.println(str);
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + "离线了");
socket.close();
dataInputStream.close();
break;
}
}
}
}
/*
* TCP 客户端 多发多收
* */
public class Client1 {
public static void main(String[] args) throws Exception {
System.out.println("客户端启动");
//创建Socket对象
Socket socket = new Socket("127.0.0.1", 9999);
//字节输出流 发送数据
OutputStream os = socket.getOutputStream();
//把低级字节输出流包装成高级的数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入消息");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("退出成功");
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
与多个客户端同时通信
上面的服务端程序只能支持一个客户端通信;因为服务端只有一个线程,只能处理一个客户端的消息。
使用多线程与多个客户端同时通信
/*
* 线程类
* */
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
public void run() {
try {
//接收客户端的数据
InputStream inputStream = socket.getInputStream();
//把原始的字节输入流包装成数据输入流
DataInputStream dataInputStream = new DataInputStream(inputStream);
while (true) {
try {
String str = dataInputStream.readUTF();
System.out.println(str);
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了");
dataInputStream.close();
socket.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* TCP 服务端 多线程-服务端支持与多个客户端通信
* */
public class Server2 {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
//创建服务端对象
ServerSocket serverSocket = new ServerSocket(9999);
while (true) {
//等待客户端连接请求
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上线了");
//把这个客户端对应的socket通信管道,交给一个独立的线程
new ServerReaderThread(socket).start();
}
}
}
/*
* TCP 客户端 多线程-服务端支持与多个客户端通信
* */
public class Client2 {
public static void main(String[] args) throws Exception {
System.out.println("客户端启动");
//创建Socket对象
Socket socket = new Socket("127.0.0.1", 9999);
//字节输出流 发送数据
OutputStream os = socket.getOutputStream();
//把低级字节输出流包装成高级的数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入消息");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("退出成功");
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
六. 群聊
TCP通信-端口转发
多个客户端与客户端通信
/*
* 群聊-服务端-线程类
* */
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
public void run() {
try {
//接收客户端的数据
InputStream inputStream = socket.getInputStream();
//把原始的字节输入流包装成数据输入流
DataInputStream dataInputStream = new DataInputStream(inputStream);
while (true) {
try {
String str = dataInputStream.readUTF();
System.out.println(str);
sendAllOnLine(str);
//把消息分发个全部客户端
} catch (IOException e) {
e.printStackTrace();
System.out.println(socket.getRemoteSocketAddress() + "下线了");
Server3.onLineSockets.remove(socket);
dataInputStream.close();
socket.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void sendAllOnLine(String str) throws IOException {
for (Socket socket : Server3.onLineSockets) {
OutputStream outputStream = socket.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
dataOutputStream.writeUTF(str);
dataOutputStream.flush();
}
}
}
/*
* 群聊-客户端-线程类接收消息
* */
public class ClentReaderThread extends Thread {
private Socket socket;
public ClentReaderThread(Socket socket) {
this.socket = socket;
}
public void run() {
try {
//接收服务端的数据
InputStream inputStream = socket.getInputStream();
//把原始的字节输入流包装成数据输入流
DataInputStream dataInputStream = new DataInputStream(inputStream);
while (true) {
try {
String str = dataInputStream.readUTF();
System.out.println(str);
} catch (IOException e) {
System.out.println("自己下线了");
dataInputStream.close();
socket.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* TCP 服务端 群聊
* */
public class Server3 {
public static ArrayList<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
//创建服务端对象
ServerSocket serverSocket = new ServerSocket(9999);
while (true) {
//等待客户端连接请求
Socket socket = serverSocket.accept();
onLineSockets.add(socket);
System.out.println(socket.getRemoteSocketAddress() + "上线了");
//把这个客户端对应的socket通信管道,交给一个独立的线程
new ServerReaderThread(socket).start();
}
}
}
/*
* TCP 客户端 群聊
* */
public class Client3 {
public static void main(String[] args) throws Exception {
System.out.println("客户端启动");
//创建Socket对象
Socket socket = new Socket("127.0.0.1", 9999);
//创建线程接收服务端的消息
new ClentReaderThread(socket).start();
//字节输出流 发送数据
OutputStream os = socket.getOutputStream();
//把低级字节输出流包装成高级的数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入消息");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("退出成功");
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
七. 简易版的BS架构
简易版的BS架构
BS架构的基本原理:http://服务器IP:服务器端口(本地-http://127.0.0.1:8080)
/*
* 服务端-线程类
* */
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
public void run() {
//立即响应
try {
OutputStream outputStream = socket.getOutputStream();
//服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回的数据
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("HTTP/1.1 200 OK");
printWriter.println("Content-Type: text/html; charset=utf-8");
printWriter.println();//必须换行
printWriter.println("<html>");
printWriter.println("<head>");
printWriter.println("<title>嘿嘿嘿</title>");
printWriter.println("</head>");
printWriter.println("<body>");
printWriter.println("<h1>你真帅</h1>");
printWriter.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* TCP 服务端 简易版BS架构
* */
public class Server2 {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
//创建服务端对象
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上线了");
new ServerReaderThread(socket).start();
}
}
}
使用线程池进行优化
/*
* 服务端-任务类
* */
public class ServerReaderRunnable implements Runnable {
private Socket socket;
public ServerReaderRunnable(Socket socket) {
this.socket = socket;
}
public void run() {
//立即响应
try {
OutputStream outputStream = socket.getOutputStream();
//服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回的数据
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("HTTP/1.1 200 OK");
printWriter.println("Content-Type: text/html; charset=utf-8");
printWriter.println();//必须换行
printWriter.println("<html>");
printWriter.println("<head>");
printWriter.println("<title>嘿嘿嘿</title>");
printWriter.println("</head>");
printWriter.println("<body>");
printWriter.println("<h1>你真帅</h1>");
printWriter.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* TCP 服务端 简易版BS架构-使用线程池优化
* */
public class Server3 {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
//创建服务端对象
ServerSocket serverSocket = new ServerSocket(8080);
//创建线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(12 * 2, 12 * 2, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
while (true) {
//等待客户端连接请求
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上线了");
//把这个客户端对应的socket通信管道,交给一个独立的线程
pool.execute(new ServerReaderRunnable(socket));
}
}
}