一.概述
什么是网络编程?
可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的)
Java提供了哪些网络编程的解决方案?
java,net.*包下提供了网络编程的解决方案
基本的通信架构
基本的通信架构有2种形式:CS架构(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)
二.网络通信三要素
IP地址:设备在网络中的地址,是唯一的标识
端口号:应用程序在设备中唯一的标识
协议:连接和数据在网络中传输的规则
2.1 IP地址
· IP(Internet Protocol):全称“互联网协议地址”,是分配给上网设备的唯一标志
· IP地址有两种形式:IPv4、IPv6
IP域名
公网IP、内网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地址:检查网络是否连通
2.1.1 InetAddress
InetAddress代表IP地址
InetAddress的常用方法
//demo
public class demo {
public static void main(String[] args) throws IOException {
//获取本机IP地址对象的
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
//获取指定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));
}
}
2.2 端口号
标记正在计算机设备上运行的应用程序的,被规定为一个16位的二进制,范围是0-65535
分类
· 周知端口:0-1023,被预先定义的知名应用占用(如HTTP占用80,FTP占用21)
· 注册端口:1024-49151,分配给用户进程或某些应用程序
· 动态端口:49152-65535,称为动态端口,是因为它一般不固定分配某种进程,而是动态分分配
注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错
2.3 协议
通信协议
网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议
开放式网络互联标准:OSI网络参考模型
OSI网络参考模型:全球网络互联标准
TCP/IP网络模型:事实上的国际标准
2.3.1 传输层的2个通信协议
UDP(User Datagram Protocol):用户数据报协议
TCP(Transmission Control Protocol):传输控制协议
UDP协议
· 特点:无连接,不可靠通信,但通信效率高(常用于语音通话、视频直播等)
· 不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口、目的地IP、程序端口和数据(限制在64KB内)等
· 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的
TCP协议
· 特点:面向连接,可靠通信,但通信效率不高(常用于网页、文件下载、支付等)
· TCP的最终目的:要保证在不可靠的信道上实现可靠的传输
· TCP主要有三个步骤实现可靠的传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接
TCP协议:三次握手建立可靠连接
· 可靠连接:确定通信双方,收发消息都是正常没问题的(全双工)
· 为什么要三次握手建立连接?因为要验证双方收发消息是否正常(传输数据会进行确认,以保证数据传输的可靠性)
TCP协议:四次握手断开连接
目的:确保双方数据的收发都已经完成
三.UDP通信
· 特点:无连接,不可靠通信
· 不事先建立连接;发送端每次要把发送的数据(限制在64KB内)、接收端IP等信息封装成一个数据包,发出去就不管了
· Java提供了一个java.net.DatagramSocket类来实现UDP通信
DatagramSocket:用于创建客户端、服务端
DatagramPacket:创建数据包
UDP通信实现步骤
//Server
public class Server {
public static void main(String[] args) throws IOException {
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.getAddress().getHostName());
System.out.println(packet.getPort());
socket.close(); //释放资源
}
}
//Client
public class Client {
public static void main(String[] args) throws IOException {
//1.创建客户端对象
DatagramSocket socket = new DatagramSocket();
//2.创建数据包对象封装要发出去的数据
// public DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port)
//参数一:封装要发出去的数据
//参数二:发送出去的数据大小(字节个数)
//参数三:服务端的IP地址(找到服务器主机)
//参数四:服务端程序的端口
String s = "一只快乐小狗,自由自在无忧无虑aaa";
byte[] bytes = s.getBytes();
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),6666);
//3.开始正式发送数据包的数据出去
socket.send(packet);
System.out.println("客户端数据发送完毕---");
socket.close(); //释放资源
}
}
3.1 多发多收
多发多收实现步骤
//Server
public class Server {
public static void main(String[] args) throws IOException {
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.getAddress().getHostName());
System.out.println(packet.getPort());
System.out.println("===============================");
}
}
}
//Client
public class Client {
public static void main(String[] args) throws IOException {
//1.创建客户端对象
DatagramSocket socket = new DatagramSocket();
//2.创建数据包对象封装要发出去的数据
// public DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port)
//参数一:封装要发出去的数据
//参数二:发送出去的数据大小(字节个数)
//参数三:服务端的IP地址(找到服务器主机)
//参数四:服务端程序的端口
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入您想要发送的数据:");
String msg = sc.nextLine();
//一旦用户输入exit,就退出客户端
if(msg.equals("exit")){
System.out.println("您已经退出!");
socket.close();
break;
}
byte[] bytes = msg.getBytes();
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),6666);
//3.开始正式发送数据包的数据出去
socket.send(packet);
}
}
}
多开同一个程序的操作
四.TCP通信
· 特点:面向连接,可靠通信
· 通信双方事先会采用“三次握手”方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端
· Java提供了一个java.net.Socket类来实现TCP通信
TCP通信——客户端开发
· 客户端程序就是通过java.net包下的Socket类来实现的
TCP通信——服务端开发
· 服务端是通过java.net包下的ServerSocket类来实现的
//Server
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动成功!");
//1.创建ServerSocket对象,并同时为服务端注册端口
//public ServerSocket(int port)
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();
}
}
//Client
public class Client {
public static void main(String[] args) throws IOException {
//1.创建Socket对象,并同时请求与服务端程序的连接
//public Socket(String host, int port)
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();
System.out.println("客户端发送数据完毕!");
socket.close(); //释放资源
}
}
4.1 多发多收
//Server
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动成功!");
//1.创建ServerSocket对象,并同时为服务端注册端口
//public ServerSocket(int port)
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);
//也可以获取客户端IP地址
System.out.println(socket.getRemoteSocketAddress());
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress()+"离线了");
dis.close();
socket.close();
break;
}
}
}
}
//Client
public class Client {
public static void main(String[] args) throws IOException {
//1.创建Socket对象,并同时请求与服务端程序的连接
//public Socket(String host, int port)
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();
if(msg.equals("exit")){
System.out.println("您已经退出!");
dos.close();
socket.close();
break;
}
//4.开始写数据出去
dos.writeUTF(msg);
dos.flush();
}
}
}
4.2 支持与多个客户端同时通信
//Server
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动成功!");
//1.创建ServerSocket对象,并同时为服务端注册端口
//public ServerSocket(int port)
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();
}
}
}
//ServerReaderThread
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();
}
}
}
//Client
public class Client {
public static void main(String[] args) throws IOException {
//1.创建Socket对象,并同时请求与服务端程序的连接
//public Socket(String host, int port)
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();
if(msg.equals("exit")){
System.out.println("您已经退出!");
dos.close();
socket.close();
break;
}
//4.开始写数据出去
dos.writeUTF(msg);
dos.flush();
}
}
}
4.3 综合案例
4.3.1 即时通信—群聊(客户端—客户端)
TCP通信—端口转发
//Server
public class Server {
public static List<Socket> onLineSocket = new ArrayList<>();
public static void main(String[] args) throws IOException {
System.out.println("服务端启动成功!");
//1.创建ServerSocket对象,并同时为服务端注册端口
//public ServerSocket(int port)
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
//2.使用ServerSocket对象,调用一个accept方法,等待客户端的请求连接
Socket socket = serverSocket.accept();
onLineSocket.add(socket);
System.out.println("有人上线了" + socket.getRemoteSocketAddress());
//3.把这个客户端的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
//ServerReaderThread
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) {
Server.onLineSocket.remove(socket);
System.out.println("有人下线了" + socket.getRemoteSocketAddress());
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendMsgToAll(String msg) throws IOException {
//发送给全部在线的socket管道接收
for (Socket onLineSocket : Server.onLineSocket) {
OutputStream os = onLineSocket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF(msg);
dos.flush();
}
}
}
//Client
public class Client {
public static void main(String[] args) throws IOException {
//1.创建Socket对象,并同时请求与服务端程序的连接
//public Socket(String host, int port)
Socket socket = new Socket("127.0.0.1",8888);
//创建一个独立的线程,负责随时从socket中接收服务端发过来的消息
new ClientReaderThread(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();
if(msg.equals("exit")){
System.out.println("您已经退出!");
dos.close();
socket.close();
break;
}
//4.开始写数据出去
dos.writeUTF(msg);
dos.flush();
}
}
}
//ClientReaderThread
public class ClientReaderThread extends Thread {
private Socket socket;
public ClientReaderThread(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();
}
}
}
4.3.2 实现一个简易版的BS架构
BS架构的基本原理
注意:服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回的数据
HTTP协议规定:响应给浏览器的数据格式必须满足如下格式
//Server
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动成功!");
//1.创建ServerSocket对象,并同时为服务端注册端口
//public ServerSocket(int port)
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
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("<div style='color:red;font-size:120px;text-align:center'>你好呀你好呀<div>");
ps.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
——每次请求都开一个新线程,到底好不好?
——高并发时,容易宕机,使用线程池进行优化
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动成功!");
//1.创建ServerSocket对象,并同时为服务端注册端口
//public ServerSocket(int port)
ServerSocket serverSocket = new ServerSocket(8080);
//创建出一个线程池:负责处理通信管道的任务
ThreadPoolExecutor pool = new ThreadPoolExecutor(8 * 2, 8 * 2, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
while (true) {
//2.使用ServerSocket对象,调用一个accept方法,等待客户端的请求连接
Socket socket = serverSocket.accept();
System.out.println("有人上线了" + socket.getRemoteSocketAddress());
//3.把这个客户端的socket通信管道,交给一个独立的线程负责处理
pool.execute(new ServerReaderRunnable(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("<div style='color:red;font-size:120px;text-align:center'>你好呀你好呀<div>");
ps.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}