本文是学习网络通信入门和简单了解UDP协议和TCP协议,学习和了解CS架构和简单了解BS架构和HTTP协议(部分图片来自黑马程序员)
目录
1.网络通信三要素
(1)IP地址
(2)端口号
(3)协议
2.UDP通信—快速入门
3.UDP通信—多发多收
4.TCP通信—快速入门
5.TCP通信—多发多收
6.TCP通信—同时接收多个客户端
7.TCP通信—综合案例
(1)即时通信-群聊
(2)实现一个简易的BS架构
*线程优化
网络编程
可以让设备中的程序与网络其他设备中的程序进行数据交互
基本通信框架
有两种形式:CS架构(CLient客户端/Server服务器)和 BS架构(Browser浏览器/Server服务器)
1.网络通信三要素
(1)IP地址
- IP(InternetProtocol)全称“互联网协议地址”,是分配给上网设备的唯一标志
- 有两种形式 IPV4 和 IPV6
IPV4 由四个字节组成一般使用点分十进制表示法
IPV6 由8段组成 一段每四位编码成一个十六进制数 一般使用冒分十六进制表示
127.0.0.1/ localhost 代表本机IP,只会寻求当前所在的主机
InetAddress
代表IP地址
名称 | 说明 |
public static InetAddress getLocalHost() | 读取本机IP,会以一个inetAddress的对象返回 |
public static InetAddress getByname(String host) | 根据ip地址或者域名 返回一个inetAddress对象 |
public String getHostName() | 获取该IP地址对象对应的主机名 |
public String getHostAddress() | 获取该IP地址中的IP地址信息 |
public boolean isReachable(int timeout) | 在指定毫秒内,判断主机与该IP是否能连通 |
(2)端口号
- 标记正在计算机设备上运行的应用程序 被固定为一个16位的二进制 范围是0~65535
分类
- 周知端口:0~1023 被预先定义的知名应用占用(如HTTP占用80,FTP占用21)
- 注册端口:1024~49151 分配给用户或某些应用程序
- 动态端口:49152~65535 一般不固定分配某种进程,而是动态分配
注意:我们自己开发的程序一般选择使用注册端口,且一个设备不能出现两个程序的端口号一致,否则出错
(3)协议
网络上通信的设备 事先固定的连接规则 以及传输数据的规则被称为网络通信协议
UDP协议 (通信效率高 用于语音 视频通话)
- 特点:无连接,不可靠通信
- 不事先建立连接数据,按照包发一包数据包含自己的IP程序端口,目的地IP程序端口的数据限制在64kb内的。
- 发送方不管对方是否在线树立在中间丢失也不管如果接受方式的数据也不返回确认故而不可靠
TCP协议 (通信效率相对不高 网页 文件下载,支付)
- 特点:面向连接 可靠通信
- TCP的最终目的:要保障在不可信的信道上实现可靠的传输
- 主要三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接(确保双方数据的收发都已经完成)
- 三次握手
2.UDP通信—快速入门
实现单发单收
package IP_Study.upd01;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建客户端
DatagramSocket socket = new DatagramSocket();
// 2.创建数据包
// public DatagramPacket(byte buff[], int offset, int length,
// InetAddress address, int port)
// 参数一:封装要发出去的数据
// 参数二:发送要发出去的数据大小(字节个数)
// 参数三:服务端的IP地址
// 参数四:服务端程序的端口
String rs = "万水千山 我陪你去看";
byte[] buff = rs.getBytes();
DatagramPacket packet = new DatagramPacket(buff,buff.length, InetAddress.getLocalHost(),8088);
// 3.正式发送这个数据包数据
socket.send(packet);
System.out.println("客户端数据发送完毕");
socket.close(); //关闭流 释放资源
}
}
package IP_Study.upd01;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建一个服务端对象
DatagramSocket socket = new DatagramSocket(8088);
System.out.println("服务端启动");
// 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()); //获取发起IP
}
}
3.UDP通信—多发多收
改造UDP单发单收代码已实现多发多收
package IP_Study.upd01;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建客户端
DatagramSocket socket = new DatagramSocket();
// 2.创建数据包
// public DatagramPacket(byte buff[], int offset, int length,
// InetAddress address, int port)
// 参数一:封装要发出去的数据
// 参数二:发送要发出去的数据大小(字节个数)
// 参数三:服务端的IP地址
// 参数四:服务端程序的端口
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String rs = sc.next();
//一旦发现客户输入exit 则退出客户端
if("exit".equals(rs)){
System.out.println("退出成功");
socket.close();
break;
}
byte[] buff = rs.getBytes();
DatagramPacket packet = new DatagramPacket(buff,buff.length, InetAddress.getLocalHost(),8088);
// 3.正式发送这个数据包数据
socket.send(packet);
}
}
}
package IP_Study.upd01;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建一个服务端对象
DatagramSocket socket = new DatagramSocket(8088);
System.out.println("服务端启动");
// 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()); //获取发起IP
System.out.println(packet.getPort());
System.out.println("---------------");
}
}
}
4.TCP通信—快速入门
package IP_Study.tcp01;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
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(); //释放资源
}
}
package IP_Study.tcp01;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建ServerSocket对象,同时为服务器注册端口
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);
// 获取客户端的IP地址
System.out.println(socket.getRemoteSocketAddress());
dis.close();
socket.close();
}
}
5.TCP通信—多发多收
package IP_Study.tcp01;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
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) {
// 4.开始写出数据
System.out.println("请说");
String rs = sc.nextLine();
// 一旦输入 exit 就退出客户端
if("exit".equals(rs)){
System.out.println("退出成功");
dos.close();
socket.close();
break;
}
dos.writeUTF(rs);
dos.flush(); //刷新数据流 以免数据没有发出去
}
}
}
package IP_Study.tcp01;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建ServerSocket对象,同时为服务器注册端口
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);
while (true) {
// 5.使用数据输入流读取客户端发送的消息
try {
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;
}
}
}
}
此时的代码是不能实现与多个客户端进行通信的 因为服务器只搭建一个通道
6.TCP通信—同时接收多个客户端
改造代码时 不能单纯依靠死循环 还需要使用线程技术进行处理
package IP_Study.tcp01;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
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) {
// 4.开始写出数据
System.out.println("请说");
String rs = sc.nextLine();
// 一旦输入 exit 就退出客户端
if("exit".equals(rs)){
System.out.println("退出成功");
dos.close();
socket.close();
break;
}
dos.writeUTF(rs);
dos.flush(); //刷新数据流 以免数据没有发出去
}
}
}
package IP_Study.tcp01;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建ServerSocket对象,同时为服务器注册端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端启动成功");
while (true) {
// 2.使用ServerSocket对象 带哦用一个accept方法 等待客户端连接请求
Socket socket = serverSocket.accept();
System.out.println("有人上线了"+socket.getRemoteSocketAddress());
// 把这个客户端的Socket通信管道交给一个独立的线程去完成
new SeverReadder(socket).start();
}
}
}
使用一个线程类来实现功能
package IP_Study.tcp01;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class SeverReadder extends Thread{
private Socket socaket;
public SeverReadder(Socket socket) {
this.socket = socket;
}
@Override
public void run(){
// 3.从Socket中获得一个字节数入流管道
try {
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 (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + "离线了");
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
7.TCP通信—综合案例
(1)即时通信-群聊
TCP通信-端口转发 实现思路是将存储好当前在线的socket都进行一次信息发送
package IP_Study.tcp01;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建socket对象 并同时请求与服务端程序的连接
Socket socket = new Socket("127.0.0.1",8888);
// 创建一个独立线程 负责随机从socket中接收服务器发送的信息
new ClientReader(socket);
// 2.从Socket中得到一个字节输出流并包装成数据输出流
OutputStream os = socket.getOutputStream();
// 3.把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
// 4.开始写出数据
System.out.println("请说");
String rs = sc.nextLine();
// 一旦输入 exit 就退出客户端
if("exit".equals(rs)){
System.out.println("退出成功");
dos.close();
socket.close();
break;
}
dos.writeUTF(rs);
dos.flush(); //刷新数据流 以免数据没有发出去
}
}
}
package IP_Study.tcp01;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class ClientReader extends Thread{
private Socket socket;
public ClientReader(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
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 (IOException e) {
System.out.println("自己离线了"+socket.getRemoteSocketAddress());
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package IP_Study.tcp01;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Server {
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) throws Exception {
// 1.创建ServerSocket对象,同时为服务器注册端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端启动成功");
while (true) {
// 2.使用ServerSocket对象 带哦用一个accept方法 等待客户端连接请求
Socket socket = serverSocket.accept();
onLineSockets.add(socket);
System.out.println("有人上线了"+socket.getRemoteSocketAddress());
// 把这个客户端的Socket通信管道交给一个独立的线程去完成
new SeverReader(socket).start();
}
}
}
package IP_Study.tcp01;
import java.io.*;
import java.net.Socket;
public class SeverReader extends Thread{
private Socket socket;
public SeverReader(Socket socket) {
this.socket = socket;
}
@Override
public void run(){
// 3.从Socket中获得一个字节数入流管道
try {
InputStream is = socket.getInputStream();
// 4.把原始的字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
// 5.使用数据输入流读取客户端发送的消息
String rs = dis.readUTF(); //信息格式要统一
System.out.println(rs);
//把这个信息发给全部客户端进行接收
SendMsgToAll(rs);
// 获取客户端的IP地址
System.out.println(socket.getRemoteSocketAddress());
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + "离线了");
Server.onLineSockets.remove(socket);
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void SendMsgToAll(String rs) throws Exception {
// 发送给全部在线的socket管道接受
for (Socket onLineSocket : Server.onLineSockets) {
OutputStream os = onLineSocket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF(rs);
dos.flush();
}
}
}
实现思路:
当客户端有信息传输到服务器中,先将信息在服务端调用,在向所有在线的通信管道传输信息
(2)实现一个简易的BS架构
package IP_Study.tcp02;
import IP_Study.tcp01.SeverReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Sever {
public static void main(String[] args) throws Exception {
// 1.创建ServerSocket对象,同时为服务器注册端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端启动成功");
while (true) {
// 2.使用ServerSocket对象 带哦用一个accept方法 等待客户端连接请求
Socket socket = serverSocket.accept();
System.out.println("有人上线了"+socket.getRemoteSocketAddress());
// 把这个客户端的Socket通信管道交给一个独立的线程去完成
new SeverReader(socket).start();
}
}
}
package IP_Study.tcp02;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class SeverReader extends Thread{
private Socket socket;
public SeverReader(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:purple;font-size:101px;text-align:center' >万水千山 我陪你去看");
ps.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
*线程优化
在高并发下 处理任务容易宕机 所以使用线程池来处理线程(注意线程池的参数)
package IP_Study.tcp02;
import IP_Study.tcp01.SeverReader;
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;
public class Sever {
public static void main(String[] args) throws Exception {
// 1.创建ServerSocket对象,同时为服务器注册端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端启动成功");
// 创建线程池 负责处理通信管道任务
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();
pool.execute(new SeverReader(socket));
System.out.println("有人上线了"+socket.getRemoteSocketAddress());
// 把这个客户端的Socket通信管道交给一个独立的线程去完成
new SeverReader(socket).start();
}
}
}
学习时间:2024.09.04