无论是CS架构,还是BS架构的软件都必须依赖网络编程
IPv4
IPv6
IP域名
由于IP地址比较难记住,所以有了IP域名,例如 www.baidu.com 等等
公网IP,内网IP
特殊IP地址
IP常用命令
InetAddress
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.comSystem.out.println(ip2.isReachable(6000));}}
端口
分类
注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则会出错
通信协议
开放式网络互联标准:OSI网络参考模型
传输层的两个通信协议
UDP协议
TCP协议
TCP协议:三次握手建立可靠连接
TCP协议:四次挥手断开连接
Java提供了一个 java.net.DatagramSocket 类来实现UDP通信
DatagramSocket: 用于创建客户端、服务端
DatagramPacket:创建数据包
实战:使用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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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("88888");ps.close();socket.close();} catch (Exception e) {e.printStackTrace();}}}