1.网络编程三要素
实现网络编程关键的三要素
IP地址:设备在网络中的地址,是唯一的标识。
端口:应用程序在设备中唯一的标识。
协议: 数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。
1.1、IP地址
IP(Internet Protocol):全称”互联网协议地址”,是分配给上网设备的唯一标志。
常见的IP分类为:IPv4和IPv6
IPv4:
IPv6:128位(16个字节),号称可以为地球每一粒沙子编号。
IPv6分成8个整数,每个整数用四个十六进制位表示, 数之间用冒号(:)分开。
IP地址基本寻路
IP地址形式:
公网地址、和私有地址(局域网使用)。
192.168. 开头的就是常见的局域网地址,范围即为192.168.0.0--192.168.255.255,专门为组织机构内部使用。
IP常用命令:
ipconfig:查看本机IP地址
ping IP地址:检查网络是否连通
特殊IP地址:
本机IP: 127.0.0.1或者localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本机。
InetAddress 的使用
此类表示Internet协议(IP)地址。
名称 | 说明 |
public static InetAddress getLocalHost() | 返回本主机的地址对象 |
public static InetAddress getByName(String host) | 得到指定主机的IP地址对象,参数是域名或者IP地址 |
public String getHostName() | 获取此IP地址的主机名 |
public String getHostAddress() | 返回IP地址字符串 |
public boolean isReachable(int timeout) | 在指定毫秒内连通该IP地址对应的主机,连通返回true |
package com.itwpf.d1_interAddress;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InterAddressDemo1 {
public static void main(String[] args) throws IOException {
//获取本机地址对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println("本机名:"+ip1.getHostName());
System.out.println("本机ip地址:"+ip1.getHostAddress());
System.out.println("-------------------");
//获取域名ip对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
System.out.println("-------------------");
//获取公网ip对象
InetAddress ip3 = InetAddress.getByName("36.152.44.95");
System.out.println(ip3.getHostName());
System.out.println(ip3.getHostAddress());
System.out.println("-------------------");
//判断是否能通,ping 5秒之前测试是否可通
System.out.println(ip3.isReachable(5000));
}
}
1.2、端口号
端口号:
标识正在计算机设备上运行的进程(程序),被规定为一个 16 位的二进制,范围是 0~65535。他唯一标识正在计算机设备上运行的进程(程序)
端口类型
周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用 80,FTP占用21)
注册端口:1024~49151,分配给用户进程或某些应用程序。(如:Tomcat占 用8080,MySQL占用3306)
动态端口:49152到65535,之所以称为动态端口,是因为它 一般不固定分配某种进程,而是动态分配。
注意:我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
1.3、协议
通信协议:连接和通信数据的规则被称为网络通信协议
传输层的2个常见协议
TCP(Transmission Control Protocol) :传输控制协议
UDP(User Datagram Protocol):用户数据报协议
TCP协议特点
使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
传输前,采用“三次握手”方式建立连接,所以是可靠的 。
在连接中可进行大数据量的传输 。
连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。
TCP协议通信场景
对信息安全要求较高的场景,例如:文件下载、金融等数据通信。
UDP协议:
UDP是一种无连接、不可靠传输的协议。
将数据源IP、目的地IP和端口封装成数据包,不需要建立连接 每个数据包的大小限制在64KB内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送 ,发送数据结束时无需释放资源,开销小,速度快。
UDP协议通信场景
语音通话,视频会话等。
2.UDP通信 快速入门
2.1、快速入门
DatagramPacket:数据包对象
构造器 | 说明 |
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发送端数据包对象 buf:要发送的内容,字节数组 length:要发送内容的字节长度 address:接收端的IP地址对象 port:接收端的端口号 |
public DatagramPacket(byte[] buf, int length) | 创建接收端的数据包对象 buf:用来存储接收的内容 length:能够接收内容的长度 |
DatagramPacket常用方法
方法 | 说明 |
public int getLength() | 获得实际接收到的字节个数 |
DatagramSocket:发送端和接收端对象
构造器 | 说明 |
public DatagramSocket() | 创建发送端的Socket对象,系统会随机分配一个端口号。 |
public DatagramSocket(int port) | 创建接收端的Socket对象并指定端口号 |
DatagramSocket类成员方法
方法 | 说明 |
public void send(DatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 接收数据包 |
发送端
package com.itwpf.d2_udp1;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
public class ClientDemo1 {
public static void main(String[] args) throws IOException {
//创建发送端对象,发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket();
//创建一个数据对象封装数据
/*
public DatagramPacket(byte buf[], int length,
InetAddress address, int port)
参数1:封装要发送的数据
参数2:发送数据的大小
参数3:服务端的主机IP地址
参数4:服务端的端口
*/
byte []buffer = "你好世界".getBytes();
DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8080);
//发送数据出去
socket.send(packet);
//释放资源
socket.close();
}
}
接收端
package com.itwpf.d2_udp1;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
public class ServerDemo1 {
public static void main(String[] args) throws IOException {
//创建接收端对象
DatagramSocket socket = new DatagramSocket(8080);
//创建一个数据包对象,接收数据
byte[]buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
//等待接收数据
socket.receive(packet);
int len = packet.getLength();
String rs = new String(buffer,0,len);
System.out.println(rs);
SocketAddress socketAddress = packet.getSocketAddress();
int port = packet.getPort();
System.out.println(socketAddress);
System.out.println(port);
socket.close();
}
}
2.2、多发多收
发送端
package com.itwpf.d3_udp2;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class ClientDemo1 {
public static void main(String[] args) throws IOException {
//创建发送端对象,发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg = scanner.nextLine();
if("exit".equals(msg)){
System.out.println("离线成功");
socket.close();
break;
}
//创建一个数据对象封装数据
byte []buffer = msg.getBytes();
DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8080);
//发送数据出去
socket.send(packet);
}
}
}
接受端
package com.itwpf.d3_udp2;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
public class ServerDemo1 {
public static void main(String[] args) throws IOException {
//创建接收端对象
DatagramSocket socket = new DatagramSocket(8080);
//创建一个数据包对象,接收数据
byte[]buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
while(true){
//等待接收数据
socket.receive(packet);
int len = packet.getLength();
String rs = new String(buffer,0,len);
SocketAddress socketAddress = packet.getSocketAddress();
int port = packet.getPort();
System.out.println("收到了来自:"+socketAddress+" 对方端口:"+port+" 消息"+rs);
}
}
}
3.UDP通信 广播,组播
package com.itwpf.d4_udp_broadcast;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class ClientDemo1 {
public static void main(String[] args) throws IOException {
//创建发送端对象,发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg = scanner.nextLine();
if("exit".equals(msg)){
System.out.println("离线成功");
socket.close();
break;
}
//创建一个数据对象封装数据
byte []buffer = msg.getBytes();
DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getByName("255.255.255.255"),8080);
//发送数据出去
socket.send(packet);
}
}
}
package com.itwpf.d4_udp_broadcast;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
public class ServerDemo1 {
public static void main(String[] args) throws IOException {
//创建接收端对象
DatagramSocket socket = new DatagramSocket(8080);
//创建一个数据包对象,接收数据
byte[]buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
while(true){
//等待接收数据
socket.receive(packet);
int len = packet.getLength();
String rs = new String(buffer,0,len);
SocketAddress socketAddress = packet.getSocketAddress();
int port = packet.getPort();
System.out.println("收到了来自:"+socketAddress+" 对方端口:"+port+" 消息"+rs);
}
}
}
package com.itwpf.d5_udp_multicast;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class ClientDemo1 {
public static void main(String[] args) throws IOException {
//创建发送端对象,发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg = scanner.nextLine();
if("exit".equals(msg)){
System.out.println("离线成功");
socket.close();
break;
}
//创建一个数据对象封装数据
byte []buffer = msg.getBytes();
DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getByName("224.0.1.1"),8080);
//发送数据出去
socket.send(packet);
}
}
}
package com.itwpf.d5_udp_multicast;
import java.io.IOException;
import java.net.*;
public class ServerDemo1 {
public static void main(String[] args) throws IOException {
//创建接收端对象
MulticastSocket socket = new MulticastSocket(8080);
socket.joinGroup(InetAddress.getByName("224.0.1.1"));
//创建一个数据包对象,接收数据
byte[]buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
while(true){
//等待接收数据
socket.receive(packet);
int len = packet.getLength();
String rs = new String(buffer,0,len);
SocketAddress socketAddress = packet.getSocketAddress();
int port = packet.getPort();
System.out.println("收到了来自:"+socketAddress+" 对方端口:"+port+" 消息"+rs);
}
}
}
4.TCP通信 快速入门
TCP协议回顾:
TCP是一种面向连接,安全、可靠的传输数据的协议
传输前,采用“三次握手”方式,点对点通信,是可靠的
在连接中可进行大数据量的传输
构造器 | 说明 |
public Socket(String host , int port) | 创建发送端的Socket对象与服务端连接, 参数为服务端程序的ip和端口。 |
方法 | 说明 |
OutputStream getOutputStream() | 获得字节输出流对象 |
InputStream getInputStream() | 获得字节输入流对象 |
4.1、客户端
package com.itheima.d5_socket1;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
/**
目标:完成Socket网络编程入门案例的客户端开发,实现1发1收。
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
// 4、发送消息
ps.println("我是TCP的客户端,我已经与你对接,并发出邀请:约吗?");
ps.flush();
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.2、服务端
package com.itheima.d5_socket1;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:开发Socket网络编程入门代码的服务端,实现接收消息
*/
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
if ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.TCP通信 多发多收消息
package com.itheima.d6_socket2;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
目标:实现多发和多收
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.itheima.d6_socket2;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:开发Socket网络编程入门代码的服务端,实现接收消息
*/
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
while (true) {
// 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
6.TCP通信 同时接受多个客户端消息
主线程定义了循环负责接收客户端Socket管道连接
每接收到一个Socket通信管道后分配一个独立的线程负责处理它。
package com.itheima.d7_socket3;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
目标:实现服务端可以同时处理多个客户端的消息。
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.itheima.d7_socket3;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:实现服务端可以同时处理多个客户端的消息。
*/
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 3、开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.itheima.d7_socket3;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
存在问题:
客户端并发越多,系统瘫痪的越快。
客户端与服务端的线程模型是: N-N的关系。
7.TCP通信 使用线程池优化
package com.itheima.d8_socket4;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
拓展:使用线程池优化:实现通信。
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 6666);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.itheima.d8_socket4;
import com.itheima.d7_socket3.ServerReaderThread;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
目标:实现服务端可以同时处理多个客户端的消息。
*/
public class ServerDemo2 {
// 使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(300,
1500, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(6666);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 任务对象负责读取消息。
Runnable target = new ServerReaderRunnable(socket);
pool.execute(target);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.itheima.d8_socket4;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
8.TCP通信 模拟BS系统
package com.itheima.d10_bs;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
了解:BS-浏览器-服务器基本了解。
引入:
之前客户端和服务端都需要自己开发。也就是CS架构。
接下来模拟一下BS架构。
客户端:浏览器。(无需开发)
服务端:自己开发。
需求:在浏览器中请求本程序,响应一个网页文字给浏览器显示
*/
public class BSserverDemo {
// 使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(3,
5, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
// 1.注册端口
ServerSocket ss = new ServerSocket(8080);
// 2.创建一个循环接收多个客户端的请求。
while(true){
Socket socket = ss.accept();
// 3.交给一个独立的线程来处理!
pool.execute(new ServerReaderRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.itheima.d10_bs;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
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 {
// 浏览器 已经与本线程建立了Socket管道
// 响应消息给浏览器显示
PrintStream ps = new PrintStream(socket.getOutputStream());
// 必须响应HTTP协议格式数据,否则浏览器不认识消息
ps.println("HTTP/1.1 200 OK"); // 协议类型和版本 响应成功的消息!
ps.println("Content-Type:text/html;charset=UTF-8"); // 响应的数据类型:文本/网页
ps.println(); // 必须发送一个空行
// 才可以响应数据回去给浏览器
ps.println("<span style='color:red;font-size:90px'>《你是最牛的!!!!》 </span>");
ps.close();
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
在浏览器上访问8080端口:http://127.0.0.1:8080,即可出现以下效果