网络通信
网络编程及网络通信结构
什么是网络编程
- 可以让设备中的程序与网络上其他设备中的程序进行数据交互的(实现网络通信的)
java.net.*包下提供了网络编程的解决方案
基本的通信架构
- 基本的通信架构有2种形式:CS架构(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)
- 无论是CS架构,还是BS架构的软件都必须依赖网络编程
网络通信三要素
IP地址
-
IP(Internet Protocol):全称“互联网协议地址”,是分配给上网设备的唯一标志
-
IP地址有两种形式:
- IPv4
- IPv6
IP域名
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 lP地址:检查网络是否连通。
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());//当前ip地址对象对应的主机名
System.out.println(ip1.getHostAddress());//获取当前ip地址对象中的ip地址信息
//2.获取指定IP或者指定域名的IP地址对向
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
//3. 判断指定毫秒内主机能否与指定ip相连通
System.out.println(ip2.isReachable(6000));
}
}
端口号
端口
- 标记正在计算机设备上运行的应用程序的,被规定为一个16位的二进制,范围是0~65535。
分类
- 周知端口∶O~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
- 注册端口:1024~49151,分配给用户进程或某些应用程序。
- 动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配。
==注意:==我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
协议
- 网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议
- 可以实现全球所有的上网设备都能够互联。
传输层的2个通信协议
- UDP(User Datagram Protocol):用户数据报协议;
- TCP(Transmission Control Protocol):传输控制协议。
UDP协议
- 特点:无连接、不可靠通信。
- 不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口,目的地IP、程序端口和数据〈限制在64KB内)等。
- 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的。
- 使用场景:通信效率高,可用于语音通话、视频直播等
TCP协议
-
特点:面向连接、可靠通信。
-
TCP的最终目的:要保证在不可靠的信道上实现可靠的传输。
-
TCP主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接。
-
TCP的三次握手建立连接
-
TCP的四次握手断开连接
-
-
使用场景:通信效率相对不高,可用于网页、文件下载、支付
UDP通信-快速入门
UDP通信
- 特点:无连接、不可靠通信。
- 不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端IP、等信息封装成一个数据包,发出去就不管了。
- Java提供了一个java.net.DatagramSocket类来实现UDP通信。
心
UDP通信常用API
使用UDP通信实现:1发1收
客户端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
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)
buf字节数组存储需要发送的内容,length记录字节数组的长度以便服务端创建一个等长的数组接收,
address是服务端的IP地址,port是服务端的某一特定断号
*/
byte[] bytes = "我爱你中国666".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 {
System.out.println("--------服务端需要先进行启动!-------- ");
//1.创建服务端对象,注册端口
DatagramSocket socket = new DatagramSocket(6666);
//2.创建数据包对象,用来接收数据
byte[] bytes = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
//3.接收数据包
socket.receive(packet);
//4.从字节数组种,把接收到的数据直接打印出来
//接收多少就倒出多少
int len = packet.getLength();//获取接收到的字节数
System.out.println(new String(bytes, 0, len));
System.out.println("服务端数据已接收");
//通过数据包也可以得到客户端的IP地址和端口号
System.out.println("客户端的IP地址:" + packet.getAddress().getHostAddress());
System.out.println("客户端的端口号:" + packet.getPort());
socket.close();//释放资源
}
}
使用UDP通信实现:多发多收
客户端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
//1.创建客户端对象
DatagramSocket socket = new DatagramSocket();
System.out.println("-------客户端已启动-------");
Scanner sc = new Scanner(System.in);
while (true) {
//2. 创建数据包对象封装要发出去的数据
/*
public DatagramPacket(byte buf[], int length,
InetAddress address, int port)
buf字节数组存储需要发送的内容,length记录字节数组的长度以便服务端创建一个等长的数组接收,
address是服务端的IP地址,port是服务端的某一特定断号
*/
System.out.println("请输入:");
String s = sc.nextLine();
byte[] bytes = s.getBytes();
DatagramPacket packet = new DatagramPacket(bytes,bytes.length
, InetAddress.getLocalHost(),6666);
//输入exit退出客户端
if(s.equals("exit")){
System.out.println("客户端数据全部发送完毕~");
socket.close();
break;
}
//3.把数据包的数据发送出去
socket.send(packet);
}
}
}
服务端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("--------服务端需要先进行启动!-------- ");
//1.创建服务端对象,注册端口
DatagramSocket socket = new DatagramSocket(6666);
//2.创建数据包对象,用来接收数据
byte[] bytes = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {
//3.接收数据包
socket.receive(packet);
//4.从字节数组种,把接收到的数据直接打印出来
//接收多少就倒出多少
int len = packet.getLength();//获取接收到的字节数
System.out.println(new String(bytes, 0, len));
System.out.println("服务端数据已接收");
//通过数据包也可以得到客户端的IP地址和端口号
System.out.println("客户端的IP地址:" + packet.getAddress().getHostAddress());
System.out.println("客户端的端口号:" + packet.getPort());
System.out.println("---------------------------");
}
}
}
ps:UDP的接收端为什么可以接收很多发送端的消息?
- 接收端只负责接收数据包,无所谓是哪个发送端的数据包
TCP通信-快速入门
TCP通信
-
特点:面向连接、可靠通信。
-
通信双方事先会采用“三次握手”方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端。
-
Java提供了一个java.net.Socket类来实现TCP通信。
TCP通信API
使用TCP实现:发1收1
客户端开发(发送1份数据)
/**
* 目标:完成TCP通信快速入门-客户端开发,实现1发1收
*/
public class Client {
public static void main(String[] args) throws Exception {
//1.创建Socket对象,并同时请求与服务端程序的连接(传入服务端的IP地址与端口号)
// System.out.println(InetAddress.getLocalHost());// name + IP
// System.out.println(InetAddress.getLocalHost().getHostAddress());
// System.out.println(InetAddress.getLocalHost().getHostName());
// System.out.println("---------------");
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8888);
//2.从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
OutputStream os = socket.getOutputStream();
//3.把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
final String msg = sc.nextLine();
//4.开始写数据出去了
dos.writeUTF(msg);
dos.close();//关闭字节输出流
socket.close();//关闭客户端 释放连接资源
}
}
服务端程序的开发(接收1份数据)
/**
* 目标:完成TCP通信快速入门-服务端开发,实现1发1收
*/
public class Server {
public static void main(String[] args) throws Exception {
//1.创建一个服务端对象
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("---------服务端开始运行----------");
//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);
dis.close();
//其实我们也可以获取客户端的IP地址
System.out.println(socket.getRemoteSocketAddress());
socket.close();
System.out.println("-----------------");
// socket.close();
}
}
TCP通信-多发多收
客户端:
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
/**
* 目标:完成TCP通信快速入门-客户端开发,实现多发多收
*/
public class Client {
public static void main(String[] args) throws Exception {
//1.创建Socket对象,并同时请求与服务端程序的连接(传入服务端的IP地址与端口号)
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8888);
//2.从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
OutputStream os = socket.getOutputStream();
//3.把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
//4.开始写数据出去了
while (true) {
System.out.println("请输入:");
String msg = sc.nextLine();
//输入exit,退出客户端程序
if("exit".equals(msg)){
System.out.println("欢迎您下次光临!客户端成功退出!");
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
服务端:
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 {
//1.创建一个服务端对象
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("---------服务端开始运行----------");
//2.使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
//3.从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//4.将低级的字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
//5.使用数据输入流读取客户端发送过来的消息
while (true) {
try {
String rs = dis.readUTF();
System.out.println(rs);
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "客户端已退出");
dis.close();
socket.close();
break;
}
}
}
}
目前我们开发的服务端程序不可以支持多个客户端同时通信
- 因为服务端下载只有一个主线程,只能处理一个客户端消息(ps:可以理解为一个商店只有一个老板去迎接客人并讲解)
TCP通信-同时接收多个客户端
客户端与多发多收
案例的客户端写法相同,不变
服务端:
main主线程负责接收不同客户端的连接请求,建立连接;
使用多线程处理每一个客户端对应的socket通信管道
ps:可以理解为一个商店有一个老板去迎接客人,然后让店员去为每个客人讲解
import java.net.ServerSocket;
import java.net.Socket;
/**
* 目标:完成TCP通信快速入门-服务端开发,实现1发1收
*/
public class Server {
public static void main(String[] args) throws Exception {
//1.创建一个服务端对象
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("---------服务端开始运行----------");
while (true) {
//2.使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
System.out.println("有人上线了:" + socket.getRemoteSocketAddress());
//3.把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
线程:
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;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
String rs = dis.readUTF();
System.out.println(rs);
} catch (Exception e) {
System.out.println("有人下线了:" + socket.getRemoteSocketAddress());
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
UDP和TCP区别
个人理解:
UDP可以理解为一种单向通道,服务端可以接收所有指向其的客户端所发送的数据
而TCP则是一种双向通道,某一客户端申请连接服务端后需要服务端作出回应,然后才能正式连接建立双向通道
TCP通信-综合案例
- 群聊
实现逻辑:服务端main主线程while循环负责接收多个客户端的通信请求,建立socket通信管道并用集合存储在线的通信管道,另外创建一个线程来实现数据的接收并将数据再转发给其他在线的通信管道进而发给客户端;而其他客户端除了原有的写数据给服务端的功能(main线程或再创一个线程)外还需添加接收服务端发送的数据(另外创一个线程),进而实现了群聊功能
- BS架构
实现逻辑:BS架构只需我们建立服务端,接收浏览器传过来的连接请求即可,但要注意代码格式