一、概述(CS-BS架构)
1.1 网络编程
可以计设备中的程序与网络上其他设备中的程序进行数据交互的技术(实现网络通信)。
1.2 基本的通信架构
本的通信架构有2种形式:CS架构(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)。
client-Server(cS)架构
Browser-Server(BS)架构
二、IP
2.1、IP地址
IP(Internet Protocol):全称"互联网协议地址",是分配给上网设备的唯一标识。
目前,被广泛采用的IP地址形式有两种:IPv4、IPv6。
2.2、IPv4
IPv4是InternetProtocolversion 4的缩写,它使用32位地址,通常以点分十进制表示。
2.3、IPv6
IPv6是Internet Protocolversion 6的缩写,它使用128位地址,号称可以为地球上的每一粒沙子编号。
IPv6分成8段,每段每四位编码成一个十六进制位表示,每段之间用冒号(:)分开,将这种方式称为冒分十六进制。
2.4、IP域名(Domain Name)
用于在互联网上识别和定位网站的人类可读的名称。
例如:
DNS域名解析(Domain Name System)
是互联网中用于将域名转换为对应IP地址的分布式命名系统,它充当了互联网的"电话薄",
将易记的域名映射到数字化2的IP地址,使用用户可以通过域名来访问网站和其他网络资源
2.5、公网IP、内网IP
公网IP:是可以连接到互联网的IP地址
内网IP:也叫局域网IP是只能组织机构内部使用的I P地址;例如,192.168. 开头就是常见的局域网地址,范围为192.168.0.0--192.168.255.255 专门为组织机构内部使用
本机IP
127.0.0.1 、localhost:代表本机IP 只会寻找当前程序所在的主机
IP常用命令
ipconfig:查看本机IP地址。
ping 地址 :检查网络是否连通
2.6、InetAddress
代表IP地址。
InetAddress的常用方法
|------------------------------------------------------------------------------|------------------------------|
| InetAddress类的常用方法 | 说明 |
| public static InetAddress getlocalHosto throws UnknownHostException | 获取本机IP,返回一个InetAddress对象 |
| public String getHostNameO | 获取该IP地址对象对应的主机名 |
| public String getHostAddresS( | 获取该ip地址对象中的ip地址信息。 |
| public static InetAddress getByName(String host) throws UnknownHostException | 根据ip地址或者域名,返回一个inetAddress对象 |
| public boolean]isReachable(int timeout) thhrows IOException | 判断主机在指定毫秒内与该ip对应的主机是否能连通 |
代码:
java
package Demo04;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IntetAddressDemo01 {
public static void main(String[] args) {
// 获取本机IP
try {
//1、获取本级IP对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1);
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
//2、获取对象IP对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
//3、判断本机是否与对象主机互通
System.out.println(ip2.isReachable(5000));
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、端口-协议
3.1、端口
用来标记标记正在计算机设备上运行的应用程序,被规定为一个16位的二进制,利,范围是0~65535。
端口分类
周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
注册端口:1024~49151,1,分配给用户进程或某些应用程序。
动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动 态分配。
注意:我们自己开发的程序-般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则报错。
3.2、通通信协协议
网络上通信的设备事先规定的连接规则以及传输数据的规则被称为网络通信协议。
3.3、 OSI网络参考模型
OSI网络参考模型:全球网络互联标准。
TCP/IP络模型:事实上的国际标准。
3.4、传输层的2个通信协议
UDP(User Datagram Protocol):用户数据报协议。
UDP协议 通信效率高 (视频直播)
特点:无连接、不可靠通信
不事先建立连接,数据按照包发,一包数据包含:自己的IP、端口、目的地IP、端口和数据(限制在64KB内)等。
发送方不管对方是否在线,数据在中间丢失也不管,如果接受方收到数据也不返回确认,故是不可靠的。
TCP(Transmission Control Protocol):传输控制协议。
特点:面向连接、可靠通信。
TCP的最终目的:要保证在不可靠的信道上实现可靠的数据传输。
TCP主要有三个步骤实现可靠传输:三次握手建立连接,传输数据确认,四次握手挥手断开连接。
三次握手建立靠连接
可靠连接:确保通信的双方收发消息都是没问题的(全双工)
四次挥手断开连接
确保通信的双方收发消息都已经完成
四、UDP通信
4.1、UDP通信的实现
特点:无连接、不可靠通信。
不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端IP、等信息封装成一个数据包,发出去就不管了。
Java提供了一个java.net.DatagramSocket类来实现UDP通信。
DatagramSocket**:**用于创建客户端、服务端
|---------------------------------|-----------------------------------|
| 构造器 | 说明 |
| public DatagramSocket() | 创建客户端的Socket对象, 系统会随机分配一个端口号。 |
| public DatagramSocket(int port) | 创建服务端的Socket对象, 并指定端口号 |
|-----------------------------------------------------|-----------|
| 方法 | 说明 |
| public void send(DatagramPacket dp) | 发送数据包 |
| public void receive(DatagramPacketp) | 使用数据包接收数据 |
DatagramPacket**:创建数据包**
|--------------------------------------------------------------------------------|--------------|
| 构造器 | 说明 |
| public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发出去的数据包对象 |
| public DatagramPacket(byte[] buf, int length) | 创建用来接收数据的数据包 |
|----------------------------|------------------|
| 方法 | 说明 |
| public int getLength() | 获取数据包,实际接收到的字节个数 |
4.2、使用UDP通信实现:发送消息、接收消息
4.2.1、一发一收
1、客户端实现步骤
① 创建 DatagramSocket 对象(客户端对象) 扔韭菜的人
② 创建 DatagramPacket 对象封装需要发送的数据(数据包对象) 韭菜盘子
③ 使用 DatagramSocket 对象的 send 方法,传入 DatagramPacket 对象 开始抛出韭菜
④ 释放资源
java
package Demo05;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPClientDemo1 {
public static void main(String[] args) throws Exception {
//完成UDP通信 客户端开发
System.out.println("客户端启动了~");
//1、创建发送对象
DatagramSocket socket = new DatagramSocket();
//2、创建数据包对象封装要发送的数据
byte[] bytes = "我是客户端 发送一份小龙虾".getBytes();
/**
* 参数一,发送的数据,字节数组
* 参数二,发送的字节长度
* 参数三,目的地的IP地址
* 参数四,服务器的端口号
*/
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(),8080);
//3、让发送端对象发送数据包的数据
socket.send(packet);
//4、关闭套接字
socket.close();
}
}
2、服务端实现步骤
① 创建 DatagramSocket 对象并指定端口(服务端对象) 接韭菜的人
② 创建 DatagramPacket 对象接收数据(数据包对象) 韭菜盘子
③ 使用 DatagramSocket 对象的 receive 方法,传入 DatagramPacket 对象 开始接收韭菜
④ 释放资源
java
package Demo05;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPServerDemo2 {
public static void main(String[] args) throws Exception {
// 完成UDP通信 服务端开发
System.out.println("服务端启动了~");
//1、创建接收端对象,注册端口
DatagramSocket socket = new DatagramSocket(8080);
//2、创建数据包对象,用于接收数据
byte[] buf = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3、让接收端对象开始接收数据
socket.receive(packet);
//4、获取接收的数据
String data = new String(buf);
System.out.println("服务器接受了:" + data);
//5、获取对方的ip对象和程序端口
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println("对方ip:" + ip + "对方端口:" + port);
}
}
4.2.2、多发多收
1、客户端可以反复发送数据
客户端实现步骤
① 创建 DatagramSocket 对象(发送端对象
② 使用 while 死循环不断的接收用户的数据输入,如果用户输入的 exit 则退出程序
③ 如果用户输入的不是 exit, 把数据封装成 DatagramPacket
④ 使用 DatagramSocket 对象的 send 方法将数据包对象进行发送
⑤ 释放资源
java
package Demo06;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UDPClientDemo1 {
public static void main(String[] args) throws Exception {
//完成UDP通信 多发多收 客户端开发
System.out.println("客户端启动了~");
//1、创建发送对象
DatagramSocket socket = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true) {
//2、创建数据包对象封装要发送的数据
System.out.println("请输入:");
String msg = sc.nextLine(); //你好 在干嘛?
if ("exit".equals(msg)) {
System.out.println("客户端成功退出~");
socket.close();
break;
}
byte[] bytes = msg.getBytes();
/**
* 参数一,发送的数据,字节数组
* 参数二,发送的字节长度
* 参数三,目的地的IP地址
* 参数四,服务器的端口号
*/
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8080);
//3、让发送端对象发送数据包的数据
socket.send(packet);
}
}
}
2、接收端可以反复接收数据
接收端实现步骤
① 创建 DatagramSocket 对象并指定端口(接收端对象) 接韭菜的人
② 创建 DatagramPacket 对象接收数据(数据包对象) 韭菜盘子
③ 使用 DatagramSocket 对象的 receive 方法传入 DatagramPacket 对象
④ 使用 while 死循环不断的进行第 3 步
java
package Demo06;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServerDemo2 {
public static void main(String[] args) throws Exception {
// 完成UDP通信 多发多收 服务端开发
System.out.println("服务端启动了~");
//1、创建接收端对象,注册端口
DatagramSocket socket = new DatagramSocket(8080);
//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 data = new String(buf, 0, len);
System.out.println("服务器接受了:" + data);
//5、获取对方的ip对象和程序端口
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println("对方ip:" + ip + "对方端口:" + port);
System.out.println("--------------------------------");
}
}
}
五、TCP通信
特点:面向连接、可靠通信。
通信双方事先会采用"三次握手"方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端。
Java提供了一个java.net.Socket类来实现TCP通信。
客户端程序就是通过java.net包下的Socket类来实现的。
|---------------------------------------|---------------------------------------------|
| 构造器 | 说明 |
| public Socket(string host , int port) | 根据指定的服务器ip、端口号请求与服务端建立连接,连接通过,就获得了客户端socket |
|---------------------------------------|-----------|
| 方法 | 说明 |
| public Outputstream getoutputstream() | 获得字节输出流对象 |
| public Inputstream getInputstream() | 获得字节输入流对象 |
5.1、TCP通信的实现一发一收
客户端开发
① 创建客户端的 Socket 对象,请求与服务端的连接。
② 使用 socket 对象调用 getOutputStream () 方法得到字节输出流。
③ 使用字节输出流完成数据的发送。
④ 释放资源:关闭 socket 管道。
java
package Demo07;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class ClientDemo01 {
public static void main(String[] args) throws Exception {
//实现tcp通信 一发一收 客户端开发
//1、常见Socket管道对象
Socket socket = new Socket("127.0.0.1", 9999);
//2、从socket通信管道得到一个字节输出流
OutputStream os = socket.getOutputStream();
//3、特殊数据流
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(1);
dos.writeUTF("hello world");
//4、释放资源
socket.close();
}
}
服务端开发
服务端是通过java.net包下的ServerSocket类来实现的
ServerS****ocket
|-------------------------------|------------|
| 构造器 | 说明 |
| public ServerSocket(int port) | 为服务端程序注册端口 |
|------------------------|----------------------------------------------|
| 方法 | 说明 |
| public Socket accept() | 阻塞等待客户端的连接请求,一旦与某个客户端成功连接,则返回服务端这边的Socket对象。 |
TCP通信的实现一发一收-服务端开发
① 创建 ServerSocket 对象,注册服务端端口。
② 调用 ServerSocket 对象的 accept() 方法,等待客户端的连接,并得到 Socket 管道对象。
③ 通过 Socket 对象调用 getInputStream () 方法得到字节输入流、完成数据的接收。
④ 释放资源:关闭 socket 管道
java
package Demo07;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo02 {
public static void main(String[] args) throws Exception {
//目标:实现TCP通信下一发一收:服务器开发
System.out.println("服务器端口启动了...");
//1、实现tcp通信 多发多收 服务器开发
ServerSocket ss = new ServerSocket(9999);
//2、调用accept方法,等待客户端连接
Socket socket = ss.accept();
//3、从socket中获取输入流,读取客户端数据
InputStream is = socket.getInputStream();
//4、把字节输入流包装成特殊的数据属入流
DataInputStream dis = new DataInputStream(is);
//5、读取数据
int id = dis.readInt();
String msg = dis.readUTF();
System.out.println("id= " + id + ",收到的客户端消息msg= " + msg);
//6、客户端的ip和端口(谁给我发的)
System.out.println("客户端的ip:" + socket.getInetAddress().getHostAddress());
System.out.println("客户端的端口:" + socket.getPort());
}
}
5.2、TCP通信的实现多发多收
使用TCP通信实现:多发多收消息
① 客户端使用死循环,让用户不断输入消息。
② 服务端也 使用死循环,控制服务端程序收完消息 后 ,继续 去 接收下一个消息。
客户端
java
package Demo08;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo01 {
public static void main(String[] args) throws Exception {
//实现tcp通信 多发多收 客户端开发
//1、常见Socket管道对象
Socket socket = new Socket("127.0.0.1", 9999);
//2、从socket通信管道得到一个字节输出流
OutputStream os = socket.getOutputStream();
//3、特殊数据流
DataOutputStream dos = new DataOutputStream(os);
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入:");
String msg = scanner.nextLine();
if ("exit".equals(msg)) {
System.out.println("退出成功!");
dos.close();
//4、释放资源
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
服务端
java
package Demo08;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo02 {
public static void main(String[] args) throws Exception {
//目标:实现TCP通信下 多发多收:服务器开发
System.out.println("服务器端口启动了...");
//1、实现tcp通信 多发多收 服务器开发
ServerSocket ss = new ServerSocket(9999);
//2、调用accept方法,等待客户端连接
Socket socket = ss.accept();
//3、从socket中获取输入流,读取客户端数据
InputStream is = socket.getInputStream();
//4、把字节输入流包装成特殊的数据属入流
DataInputStream dis = new DataInputStream(is);
//5、读取数据
while (true) {
String msg = dis.readUTF();
System.out.println("收到的客户端msg:" + msg);
//6、客户端的ip和端口(谁给我发的)
System.out.println("客户端的ip:" + socket.getInetAddress().getHostAddress());
System.out.println("客户端的端口:" + socket.getPort());
System.out.println("----------------------------------------------------------------");
}
}
}
目前我们开发的服务端程序,是否可以支持同时与多个客户端通信**?**
不可以
因为服务端现在只有一个主线程,只能处理一个客户端的消息。
java
package Demo06;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo01 {
public static void main(String[] args) throws Exception {
//实现tcp通信 多发多收 客户端开发
//1、常见Socket管道对象
Socket socket = new Socket("127.0.0.1", 9999);
//2、从socket通信管道得到一个字节输出流
OutputStream os = socket.getOutputStream();
//3、特殊数据流
DataOutputStream dos = new DataOutputStream(os);
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入:");
String msg = scanner.nextLine();
if ("exit".equals(msg)) {
System.out.println("退出成功!");
dos.close();
//4、释放资源
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
java
package Demo06;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo02 {
public static void main(String[] args) throws Exception {
//目标:实现TCP通信下 多发多收:服务器开发
System.out.println("----------------------------------------------------------------");
System.out.println("服务器端口启动了...");
//1、实现tcp通信 多发多收 服务器开发
ServerSocket ss = new ServerSocket(9999);
while (true) {
//2、调用accept方法,等待客户端连接
Socket socket = ss.accept();
System.out.println("一个客户端上线了~" + socket.getInetAddress().getHostAddress());
//3、把这个客户端管道交给一个独立的子线程专门负责接收这个管道的消息
new ServerReader(socket).start();
}
}
}
java
package Demo06;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class ServerReader extends Thread {
private Socket socket;
public ServerReader(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//读取管道的消息
//3、获取字节输入流
InputStream is = socket.getInputStream();
//4、把字节输入流包装成特殊的数据属入流
DataInputStream dis = new DataInputStream(is);
//5、读取数据
while (true) {
String msg = dis.readUTF();
System.out.println("收到的客户端msg:" + msg);
//6、客户端的ip和端口(谁给我发的)
System.out.println("客户端的ip:" + socket.getInetAddress().getHostAddress());
System.out.println("客户端的端口:" + socket.getPort());
System.out.println("----------------------------------------------------------------");
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("客户端断开连接~" + socket.getInetAddress().getHostAddress());
}
}
}
5.3、BS架构
BS架构的原理
注意:服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回的数据。
HTTP 协议规定:响应给浏览器的数据格式必须满足如下格式
使用线程池进行优化
java
package Demo07;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
public class ServerDemo02 {
public static void main(String[] args) throws Exception {
//目标:实现TCP通信下 多发多收:服务器开发
System.out.println("----------------------------------------------------------------");
System.out.println("服务器端口启动了...");
//1、实现tcp通信 多发多收 服务器开发
ServerSocket ss = new ServerSocket(8080);
ExecutorService pool = new ThreadPoolExecutor(
3,
10,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
while (true) {
//2、调用accept方法,等待客户端连接
Socket socket = ss.accept();
System.out.println("一个客户端上线了~" + socket.getInetAddress().getHostAddress());
//3、把这个客户端管道包装成一个任务给线程池处理
pool.execute(new ServerReaderRunnable(socket));
}
}
}
java
package Demo07;
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;
}
@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("<!DOCTYPE html>\n" +
"<html>\n" +
"<head>\n" +
" <meta charset=\"utf-8\">\n" +
" <title>段落p标签</title>\n" +
"</head>\n" +
"<body>\n" +
"<p>\n" +
" 窗前明月光\n" +
" <br>\n" +
" 疑是地上霜\n" +
" <br>\n" +
" 举头望明月\n" +
" <br>\n" +
" 低头思故乡\n" +
"</p>\n" +
"</body>\n" +
"</html>");
ps.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
System.out.println("客户端断开连接~" + socket.getInetAddress());
}
}
}
构造器
构造器