一、网络编程
1.概述
- 可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的)。
- java.net,*包下提供了网络编程的解决方案!
基本的通信架构
基本的通信架构有2种形式:CS架构(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)。
网络通信的关键三要素
IP地址
- IP(Internet Protocol):全称”互联网协议地址”,是分配给上网设备的唯一标志。
- IP地址有两种形式:IPV4、IPV6
- IPv6:共128位,号称可以为地球每一粒沙子编号。
- IPV6分成8段表示,每段每四位编码成一个十六进制位表示,数之间用冒号(:)分开。
IP域名
- 公网IP:是可以连接互联网的IP地址;内网IP:也叫局域网IP,只能组织机构内部使用。
- 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用。
特殊IP地址
127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机。
IP常用命令:
ipconfig:查看本机IP地址。
ping IP地址:检查网络是否连通。
1.1 InetAddress
- 代表IP地址。
端口
- 标记正在计算机设备上运行的应用程序的,被规定为一个16 位的二进制,范围是 0~65535
分类
- 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用 80,FTP占用21)
- 注册端口:1024~49151,分配给用户进程或某些应用程序。
- 动态端口:49152到65535,之所以称为动态端口,是因为它 一般不固定分配某种进程,而是动态分配。
- 注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
通信协议
- 网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议
开放式网络互联标准:OSI网络参考模型
- OSI网络参考模型:全球网络互联标准。
- TCP/IP网络模型:事实上的国际标准。
1.2 重要知识点:传输层的2个通信协议
- UDP(User Datagram Protocol):用户数据报协议;
- TCP(Transmission ControlProtocol):传输控制协议。
UDP协议
- 特点:无连接、不可靠通信。
- 通讯效率高!语音通话 视频直播
- 不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口,目的地IP、程序端口和数据(限制在64KB内)等。
- 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的。
TCP协议
- 特点:面向连接、可靠通信。
- 通信效率相对不高 网页 文件下载 支付
- TCP的最终目的:要保证在不可靠的信道上实现可靠的传输。
- TCP主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接。
- TCP协议:三次握手建立可靠连接
- 可靠连接:确定通信双方,收发消息都是正常无问题的!(全双工)
- 传输数据会进行确认,以保证数据传输的可靠性
- TCP协议:四次握手断开连接
- 目的:确保双方数据的收发都已经完成!
2. UDP通信-快速入门
UDP通信
- 特点:无连接、不可靠通信。
- 不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端IP、等信息封装成一个数据包,发出去就不管了。
- Java提供了一个java.net.DatagramSocket类来实现UDP通信。
import java.net.*;
public class Client {
public static void main(String[] args) throws Exception {
//1. 创建客户端对象(发韭菜出去的人)
DatagramSocket socket = new DatagramSocket();
//2. 创建数据包对象封装要发出去得数据(创建一个韭菜盒子)
/* public DatagramPacket(byte buf[], int length,InetAddress address,int port)
参数一:封装要发出去的数据。
参数二:发送出去的数据大小(字节个数)
参数三:服务端的IP地址(找到服务端主机)
参数四:服务端程序的端口。
*/
byte[] bytes = "我是客户端,向您发送数据!".getBytes();
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,
InetAddress.getLocalHost(),6666);
//3. 开始正式发送这个数据包出去了
socket.send(packet);
System.out.println("客户端数据发送完毕~~~");
socket.close();//释放资源!
}
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
public static void main(String[] args) throws Exception {
//1. 创建一个服务端对象(创建一个接韭菜的人) 注册端口
DatagramSocket socket = new DatagramSocket(6666);
//2. 创建一个数据包对象,用于接收数据的(创建一个韭菜盒子)
byte[] buf = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 开始正式使用数据包来接收客户端发来的数据
socket.receive(packet);
//4. 从字节数组中,把接收到的数据直接打印出来
// 接收多少就倒出多少 获取本次数据包接收了多少数据
int len = packet.getLength();
String s = new String(buf, 0, len);
System.out.println(s);
String hostAddress = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println(hostAddress + ":" + port);
socket.close();//释放资源
}
}
UDP通信 多发多收
edit configures->allow 多开 apply即可躲开client程序的
package com.jingwei;
import java.net.*;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
//1. 创建客户端对象(发韭菜出去的人)
DatagramSocket socket = new DatagramSocket();//可以分配端口 或者不填写自动分配
//2. 创建数据包对象封装要发出去得数据(创建一个韭菜盒子)
/* public DatagramPacket(byte buf[], int length,InetAddress address,int port)
参数一:封装要发出去的数据。
参数二:发送出去的数据大小(字节个数)
参数三:服务端的IP地址(找到服务端主机)
参数四:服务端程序的端口。
*/
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说");
String msg = sc.nextLine();
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);
}
System.out.println("客户端数据发送完毕~~~");
}
}
package com.jingwei;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
public static void main(String[] args) throws Exception {
//1. 创建一个服务端对象(创建一个接韭菜的人) 注册端口
DatagramSocket socket = new DatagramSocket(6666);
//2. 创建一个数据包对象,用于接收数据的(创建一个韭菜盒子)
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 s = new String(buf, 0, len);
System.out.println(s);
String hostAddress = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println(hostAddress + ":" + port);
System.out.println("-----------------------");
}
}
}
3. TCP通信
- 特点:面向连接、可靠通信。
- 通信双方事先会采用“三次握手”方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端。
- Java提供了一个java.net.Socket类来实现TCP通信。
一发一收
package com.jingwei;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
public class SocketClient {
public static void main(String[] args) throws IOException {
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 s = dis.readUTF();
System.out.println(s);
//其实我们也可以获取客户端的IP地址
SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();
System.out.println(remoteSocketAddress);
dis.close();
socket.close();
}
}
package com.jingwei;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) throws IOException {
//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();//释放连接资源
}
}
多发多收
package com.jingwei;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class SocketServer {
public static void main(String[] args) throws IOException {
//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("请说:");
//4. 开始写数据出去了
String msg = sc.nextLine();
if("exit".equals(msg)){
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
// dos.close();
//
// socket.close();//释放连接资源
}
}
package com.jingwei;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
public class SocketClient {
public static void main(String[] args) throws IOException {
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) {
//5. 使用数据输入流读取客户端发送过来的消息
try {
String s = dis.readUTF();
System.out.println(s);
//其实我们也可以获取客户端的IP地址
SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();
System.out.println(remoteSocketAddress);
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress()+"离线了!");
dis.close();
socket.close();
break;
}
}
// dis.close();
// socket.close();
}
}
目前我们开发的服务端程序,是否可以支持与多个客户端同时通信?
- 不可以的。
- 因为服务端现在只有一个主线程,只能处理一个客户端的消息,
package com.jingwei;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class SocketServer {
public static void main(String[] args) throws IOException {
//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("请说:");
//4. 开始写数据出去了
String msg = sc.nextLine();
if("exit".equals(msg)){
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
// dos.close();
//
// socket.close();//释放连接资源
}
}
package com.jingwei;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketClient {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动成功!");
//1. 创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
//2. 使用ServerSocket对象,调用accept方法,等待客户端的连接请求。
while (true) {
Socket socket = serverSocket.accept();
System.out.println("有人上线了"+socket.getRemoteSocketAddress());
new ServerReaderThread(socket).start();
}
// dis.close();
// socket.close();
}
}
package com.jingwei;
import java.io.DataInputStream;
import java.io.IOException;
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 inputStream = socket.getInputStream();
DataInputStream dataInputStream = new DataInputStream(inputStream);
while (true){
try {
String msg = dataInputStream.readUTF();
System.out.println(msg);
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress()+"下线了");
dataInputStream.close();
socket.close();
break;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
TCP通信-综合案例
- 即时通信-群聊
- 实现一个简易版的BS架构
题目:要求从浏览器中访问服务器
并立即让服务器响应一个很简单的网页给浏览器展示
网页内容就是“我666”
注意:服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回的数据
package com.jingwei;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketClient {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动成功!");
//1. 创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
//2. 使用ServerSocket对象,调用accept方法,等待客户端的连接请求。
while (true) {
Socket socket = serverSocket.accept();
System.out.println("有人上线了"+socket.getRemoteSocketAddress());
// new ServerReaderThread(socket).start();
new ServerWriterThread(socket).start();
}
// dis.close();
// socket.close();
}
}
package com.jingwei;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ServerWriterThread extends Thread {
private Socket socket;
public ServerWriterThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream outputStream = socket.getOutputStream();
PrintStream printStream = new PrintStream(outputStream);
printStream.println("Http/1.1 200 OK");
printStream.println("Content-Type: text/html;charset=utf-8");
printStream.println();
printStream.println("<!DOCTYPE html>");
printStream.println("<html>");
printStream.println("<head>");
printStream.println("<title>Server Writer</title>");
printStream.println("</head>");
printStream.println("<body>");
printStream.println("<h1>Server Writer</h1>");
printStream.println("</body>");
printStream.println("</html>");
printStream.flush();
printStream.close();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
拓展知识
每次请求都开一个新线程,到底好不好?
高并发时,容易宕机!
package com.day19;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
public class SocketClient {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动成功!");
//1. 创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
//创建出一个线程池,负责处理通信管道的任务
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(20 * 2, 20 * 2, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//2. 使用ServerSocket对象,调用accept方法,等待客户端的连接请求。
while (true) {
Socket socket = serverSocket.accept();
System.out.println("有人上线了"+socket.getRemoteSocketAddress());
// new ServerReaderThread(socket).start();
//3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理。
threadPoolExecutor.execute(new ServerWriterRunnable(socket));
}
// dis.close();
// socket.close();
}
}
package com.day19;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ServerWriterRunnable implements Runnable {
private Socket socket;
public ServerWriterRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream outputStream = socket.getOutputStream();
PrintStream printStream = new PrintStream(outputStream);
printStream.println("Http/1.1 200 OK");
printStream.println("Content-Type: text/html;charset=utf-8");
printStream.println();
printStream.println("<!DOCTYPE html>");
printStream.println("<html>");
printStream.println("<head>");
printStream.println("<title>Server Writer</title>");
printStream.println("</head>");
printStream.println("<body>");
printStream.println("<h1>Server Writer</h1>");
printStream.println("</body>");
printStream.println("</html>");
printStream.flush();
printStream.close();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}