【Java】网络编程——TCP/UDP网络对讲机
目录
前言
OSI概述
传输层
端口
Socket
InetAddress类
UDP协议间的通信
UDP协议相关类
UDP 发送端
UDP 接收端 + 发送端
UDP 聊天 + 全局广播
UDP发送大文件
TCP协议间的通信
TCP协议相关类
Socket访问ServerSocket原理
TCP客户端
TCP服务端 + 客户端
TCP客户端与服务端互访
TCP上传文件
TCP下载文件
TCP多客户端并发访问服务器
TCP几个小问题
浏览器与Tomcat
浏览器访问服务器基本原理
模拟服务器接收浏览器发送的HTTP协议请求消息
模拟浏览器接收服务器返回的HTTP协议响应消息
资源定位类
URL类
URLConnection类
URLConnection 下载图片实战
HttpURLConnection类
作用
网络架构
C/S
B/S
HTTP
HTTP响应模型
协议版本
通用协议标头
实体协议标头
请求协议标头
响应协议标头
状态码
常用请求方法
HTTP与HTTPS的区别
Session、Cookie和Token
前言
学习网络编程前,需要有IO流使用基础,以及多线程编程基础。
Java IO流基础
Java 多线程并发基础
OSI概述
层 | 模型 | 功能 | TCP/IP四层模型 | 网络协议 |
7 | 应用层 | 程序,软件的运行操作 | 应用层 | HTTP、TFTP, FTP, NFS, WAIS、SMTP |
6 | 表示层 |
对数据的接收进行解释,加密与解密,压缩与解压缩等,也就是把计算机能够识别的东西转换成人能识别的东西,如图片,音频 |
Telnet, Rlogin, SNMP, Gopher | |
5 | 会话层 |
通过传输层(端口号:传输端口与接收端口)建立连接通道。 主要用于接收回话与发送会话请求,设备之间互相认识可以是IP、MAC、主机名。 |
SMTP, DNS | |
4 | 传输层 |
定义了一些传输协议和端口号(如 www 80端口等)。 TCP(传输控制协议)传输效率低,可靠性强,传输数据量大的数据。 UDP(用户数据报协议)与TCP恰恰相反,用于传输可靠性不高,数据量低的数据。如QQ就是UDP协议传输信息。 此层主要是将下层接收数据进行分段和传输,传输数据数据到大目的后再进行重组,长长把这一层叫做段。 |
传输层 | TCP, UDP |
3 | 网络层 |
主要是将下层接收的数据进行IP地址(如192.168.x.x)的封装与解封装。 这一层的工作设备是路由器,常把这一层叫做数据包。 |
网际层 | IP, ICMP, ARP, RARP, AKP, UUCP |
2 | 数据链路层 |
主要是从将物理层接收到的数据进行MAC地址(网卡地址)的封装与解封装。 常把这一层数据叫做帧,这一层的工作设备是交换机 |
数据链路层 | FDDI, Ethernet, Arpanet, PDN, SLIP, PPP,STP。HDLC,SDLC,帧中继 |
1 | 物理层 |
主要定义物理设备标准,如网卡接口类型,光线接口类型,各种传输介质的传输速率等。 主要作用是传输Bit比特流,(也就是由1、0转换为电流强弱进行传输,到达目的后再转换为1、0,这一层的数据称为bit比特流) |
IEEE 802.1A, IEEE 802.2到IEEE 802. |
传输层
主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务。应用进程利用该服务传送应用层报文。
传输层主要使用一下两种协议
- 传输控制协议-TCP:提供面向连接的,可靠的数据传输服务。
- 用户数据协议-UDP:提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。
UDP | TCP | |
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
场景 | 适用于实时应用(IP电话、视频会议、直播、文字信息等) | 适用于要求可靠传输的应用,例如文件传输 |
1. 运行在TCP协议上的协议:
HTTP(Hypertext Transfer Protocol,超文本传输协议),主要用于普通浏览。
HTTPS(HTTP over SSL,安全超文本传输协议),HTTP协议的安全版本。
FTP(File Transfer Protocol,文件传输协议),用于文件传输。
POP3(Post Office Protocol, version 3,邮局协议),收邮件用。
SMTP(Simple Mail Transfer Protocol,简单邮件传输协议),用来发送电子邮件。
TELNET(Teletype over the Network,网络电传),通过一个终端(terminal)登陆到网络。
SSH(Secure Shell,用于替代安全性差的TELNET),用于加密安全登陆用。
2. 运行在UDP协议上的协议:
BOOTP(Boot Protocol,启动协议),应用于无盘设备。
NTP(Network Time Protocol,网络时间协议),用于网络同步。
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),动态配置IP地址。
3. 运行在TCP和UDP协议上:
DNS(Domain Name Service,域名服务),用于完成地址查找,邮件转发等工作。
端口
- 用于标识不同进程(程序)的地址(程序标识)
- 有效端口:0 ~ 65535
- 保留端口:0 ~ 1024。这是系统系统使用的端口。
- 作用:IP地址仅仅代表一台主机,主机与主机之间的进程要想进行通信,进程必须独立监听一个端口。
Socket
Socket就是为网络服务提供的一种机制
- 网络通信其实就是Socket之间的通信
- 通信两段都有Socket
- 数据在两个Socket之间通过IO传输
InetAddress类
此类用来描述IP地址的,并没有构造IP的公共构造方法,因为计算机底层(网络层)本身已经帮我们封装好了,我们要想拿到本机IP与外网IP只需要调用其静态方法获得InetAddress对象。
Inet4Address(IPv4)和Inet6Address(IPv6)对象都继承自InetAddress类
Java网络操作框架,在java.net包
基础常见方法:
- static InetAddress InetAddress.getLocalHost(),获得本主机IP地址对象。
- static InetAddress getByName(String host),直接传递域名,或IP地址构造InetAddress。
- String getHostAddress(),获得IP地址。
- String getHostName(),获得主机名。
package com.bin.demo;import java.net.InetAddress;public class Main {public static void main(String[] args) throws Exception {//获取本机器的IP及主机名InetAddress my_inetAddress = InetAddress.getLocalHost();String my_ip = my_inetAddress.getHostAddress();String my_name = my_inetAddress.getHostName();System.out.println(my_ip + " ———— " + my_name);//获取指定域名IP地址及主机名InetAddress inetAddress = InetAddress.getByName("www.baidu.com");String str_ip = inetAddress.getHostAddress();String str_name = inetAddress.getHostName();System.out.println(str_ip + " ———— " + str_name);//如果不是直接传字符串IP地址,而是直接传域名,其实就是通过设置的DNS服务器对域名解析成IPmy_inetAddress = InetAddress.getByName("localHost"); //DNS解析会去hosts文件查找是否已有解析缓存my_ip = my_inetAddress.getHostAddress();my_name = my_inetAddress.getHostName();System.out.println(my_ip + " ———— " + my_name);}}
输出:
192.168.0.108 ———— bin
183.232.231.172 ———— www.baidu.com
127.0.0.1 ———— localHost
Windows 本地 hosts DNS 缓存文件:
UDP协议间的通信
UDP没有客户端和服务端之说,因为UDP不关心是否消息是否被安全送达
UDP发送每个数据报包最大限制64k。
UDP协议相关类
DatagramSocket类
- 此类表示用来发送和接收数据报包的套接字,使用UDP协议。
- send( DatagramPacket p ):发送数据。
- receive( DatagramPacket p ):接收数据。阻塞性方法。
- close():涉及到使用系统IO资源,使用完时关闭资源。
- 不同步的,在多线程中 能用一个DatagramSocket来发送和接收。
- 接收端:必需要为程序构造或设置一个 监听端口
- 发送端 和 接收端:都需要指定一个自身运行程序的 监听端口,不指定则默认使用系统未分配的可用端口,一般只需要设置接收端的 监听端口。
- 一个机器里每个DatagramSocket对象只能监听一个独立的端口,如果一个端口已被其它程序占用,再次绑定时会报java.net.BindException: Address already in use: Cannot bind 异常
常用的构造方法:
DatagramPacket类
- 此类表示数据报包。
- 发送端:必需要构造一个 目的IP + 目的端口
- 数据报包:发送端和接收端都需要一个byte[]数组进行存储数据,这个byte[]将成为缓冲区,也就是UDP不能发超过64K的数据。因为封装的UDP类有缓冲区会自动提取 下一段 数据并发送,所以不必担心。
接收端接收数据时,解包获取数据的常见方法:
- InetAddress getAddress():获取源IP对象。
- int getPort():获取源端口。
- byte[] getData():获取接收到的数据,但数据字节的长度可能 不是 数组的长度,需要 getLength()方法获取准确的数据字节长度。
- int getLength():获取已接收到的数据字节长度。
红框圈起来的是接收端的数据报包构造,其余有设置 IP + port 的都是发送端的数据报包构造。
UDP 发送端
package com.bin.demo;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;public class Main {public static void main(String[] args) throws Exception {// TODO}public static void send() throws IOException {// UDP发送端DatagramSocket ds = new DatagramSocket();// 明确数据源byte[] data = "user = bin; passworld = 7758258;".getBytes("UTF-8"); // 使用UTF-8编码// 明确目的地IP + portInetAddress ip = InetAddress.getByName("192.168.0.108"); // 我的本机IP,发给我自己int port = 21024; // 目地程序监听端口// 构造数据报包DatagramPacket dp = new DatagramPacket(data, data.length, ip, port);// 发送数据报包ds.send(dp);// 关闭IO资源ds.close();}}
UDP 接收端 + 发送端
package com.bin.demo;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;public class Main {public static void main(String[] args) throws Exception {new Thread() {@Override public void run() {try {receive(); //先启动接收端} catch (IOException e) {e.printStackTrace();}}}.start();Thread.sleep(100); //让main线程暂停100ms,防止接收端线程未启动send();}public static void send() throws IOException {// UDP发送端DatagramSocket ds = new DatagramSocket();// 明确数据源byte[] buf = "user = bin; passworld = 7758258;".getBytes("UTF-8"); // 使用UTF-8编码// 明确目的地IP + portInetAddress ip = InetAddress.getByName("192.168.0.108"); // 我的本机IP,发给我自己int port = 21024; // 目地程序监听端口// 构造数据报包:封包DatagramPacket dp = new DatagramPacket(buf, buf.length, ip, port);// 发送数据报包ds.send(dp);// 关闭IO资源ds.close();}public static void receive() throws IOException {// UDP接收端DatagramSocket ds = new DatagramSocket(21024); //必须明确监听端口// 构造接收数据报包容器:解包(对象)byte[] buf = new byte[1024]; //缓冲区大小DatagramPacket dp = new DatagramPacket(buf, buf.length);//接收数据报包ds.receive(dp); //等待数据:阻塞性方法//提取数据String str_ip = dp.getAddress().getHostAddress();int port = dp.getPort();// 通过数据解包对象获取byte[]数据,并通过getLength()方法获取已接收的字节长度String data = new String(dp.getData(), 0, dp.getLength(), "UTF-8"); // 指定编码解码System.out.println(str_ip + " : " + port + " data--> " + data); //打印输出//关闭IO资源ds.close();}}
输出:
192.168.0.108 : 59254 data--> user = bin; passworld = 7758258;
UDP 聊天 + 全局广播
这里要实现一个类似对讲机的功能,就是即能发送又能接收的功能。涉及到多线程,一条线程负责发送,一条线程负责接收。
- 255.255.255.255 是局域网全局广播,这样每台机器都能接收到消息。
也可以指定网段网络号 x.x.x. + 最大主机地址,根据不同的子网掩码进行计算。
Phone.java类:用于发送和接收的描述类
package com.bin.demo;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;public class Phone {private InetAddress sendIP; //发送目的地private int receivePort; //接收目的地private class Send implements Runnable {private DatagramSocket ds;Send(DatagramSocket ds) {this.ds = ds;}@Overridepublic void run() {BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //从键盘中读取数据String line;try {while ((line = br.readLine()) != null) { //循环读取,readLine()阻塞性方法,读取一行数据byte[] buf = line.getBytes("UTF-8"); //指定编码DatagramPacket dp = new DatagramPacket(buf, buf.length, sendIP, receivePort); //byte[]数据 + 目地IP + 目地portds.send(dp); //发送数据if ("over".equals(line)) { //定义标记,退出聊天break;}}} catch (IOException e){// ...} finally {if (ds != null && !ds.isClosed()) { //关闭资源ds.close();}}}}private class Receive implements Runnable {private DatagramSocket ds;Receive(DatagramSocket ds) {this.ds = ds;}@Overridepublic void run() {try {while (true) { //无限循环接收消息byte[] buf = new byte[1024]; //缓冲区容器大小DatagramPacket dp = new DatagramPacket(buf, buf.length); //解包对象ds.receive(dp); //等待消息,阻塞性方法String ip = dp.getAddress().getHostAddress(); // 获取源IPint port = dp.getPort(); // 获取源端口String data = new String(dp.getData(), 0, dp.getLength(), "UTF-8"); // 获取数据if ("over".equals(data)) { // 读取到有人退出聊天System.out.println(ip + " : " + port + " [已离线]");if (InetAddress.getLocalHost().getHostAddress().equals(ip)) { //如果是本机发出的over,接收线程也退出break;}} else {System.out.println(ip + " : " + port + " ---> " + data); //打印消息}}} catch (IOException e) {// ...} finally {if (ds != null && !ds.isClosed()) { //关闭资源ds.close();}}}}public Phone(String sendIP, int receivePort) throws UnknownHostException { // 构造方法this.sendIP = InetAddress.getByName(sendIP); // 解析成InetAddress对象this.receivePort = receivePort; // 接收监听的端口}public void start() throws SocketException {// 构造一个DatagramSocket服务DatagramSocket ds = new DatagramSocket(receivePort); // 发送和接收的线程都使用一个UDP服务端口new Thread(new Receive(ds)).start();new Thread(new Send(ds)).start();}}
main.java
package com.bin.demo;public class Main {public static void main(String[] args) throws Exception {Phone phone = new Phone("255.255.255.255", 21024);phone.start();}}
输出:
雷姆
192.168.0.108 : 21024 ---> 雷姆
爱密莉亚
192.168.0.108 : 21024 ---> 爱密莉亚
狂三
192.168.0.108 : 21024 ---> 狂三
over
192.168.0.108 : 21024 [已离线]
UDP发送大文件
思想:
- 发送端:通过不断的发送 UDP 数据包,最后发送一个结束标记。
- 接收端:阻塞式方法 receive(dp) 外部是无线循环,不断地接收数据包,并监听结束标记。
package com.bin.demo;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;public class Main {public static void main(String[] args) throws Exception {new Thread() {@Override public void run() {try {receive();System.out.println("复制完成");} catch (Exception e) {e.printStackTrace();}}}.start();Thread.sleep(200); //mian线程等待200ms,防止接收端未开启System.out.println("正在发送... ...");send();}public static void send() throws Exception {//创建UDP发送端对象DatagramSocket ds = new DatagramSocket();//目的地InetAddress sendIP = InetAddress.getByName("192.168.0.108");int sendPort = 10248;FileInputStream fis = new FileInputStream("F:\\avi.avi"); //读取要发送的文件byte[] buf = new byte[1024]; //每次读取的字节缓冲大小int len = 0; //读取到的字节数while ((len = fis.read(buf)) != -1) {// 封装UDP包DatagramPacket dp = new DatagramPacket(buf, 0, len, sendIP, sendPort);//发送ds.send(dp);}byte[] flag = "over".getBytes(); //结束标记ds.send(new DatagramPacket(flag, flag.length, sendIP, sendPort));//关闭资源fis.close();ds.close();}public static void receive() throws Exception {//创建UDP接收端对象,并指定监听一个端口DatagramSocket ds = new DatagramSocket(10248);//要存放的目地FileOutputStream fos = new FileOutputStream("F:\\avi_back.avi");byte[] buf = new byte[1024]; //接收的容器大小DatagramPacket dp = new DatagramPacket(buf, buf.length);while (true) {ds.receive(dp); //等待消息byte[] data = dp.getData();if ("over".equals(new String(data, 0, dp.getLength()))) {break;}fos.write(data, 0, dp.getLength()); //写入}//关闭资源fos.close();ds.close();}}
输出:
正在发送... ...
复制完成
图:
TCP协议间的通信
- TCP面向连接也就是3此握手4次挥手,分客户端和服务端。
- 客户端和服务端建立连接后,都能进行数据的发送和接收
TCP协议相关类
客户端:
Socket类
此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
基础构造方法:
基础使用方法:
- getInputStream():返回此套接字的输入流。
- getOutputStream():返回此套接字的输出流。
- shutdownOutputStream():禁用此套接字的输出流。也就是通知服务器此套接字已经输出完成。(因为输出如文件类型的大数据,服务端会一直等待读取,客户端输出完毕时,服务端并不知道),其实就是发送了一个输出结束标记给服务器。
- InetAddress getInetAddress():获取此套接字绑定的IP地址对象。
- boolean isConnected():判断此套接字是否成功连接服务器。
- close():关闭IO流。
服务端:
ServerSocket类
此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。
构造方法:
基础使用方法:
- Socket accept():此方法是阻塞性方法,等待客户端连接,返回Socket套接字对象。
- close():服务端一般都是不用关闭的。
Socket访问ServerSocket原理
TCP客户端
package com.bin.demo;import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;public class Main {public static void main(String[] args) throws Exception {// TODO}public static void socket() throws IOException {// TCP客户端Socket s = new Socket("192.168.0.108", 21024); // 明确连接地址// 明确数据源,这里发送一条文本数据byte[] buf = "雷姆".getBytes("UTF-8"); // 指定编码// 获取输出通道OutputStream out = s.getOutputStream();out.write(buf); // 发送数据// 关闭资源,out字节输出流对象本身是Socket套接字所持有,所以只需关闭套接字s.close();}}
TCP服务端 + 客户端
package com.bin.demo;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class Main {public static void main(String[] args) throws Exception {new Thread() {@Override public void run() {try {System.out.println("TCP服务端启动...");serverSocket(); //先启动服务端} catch (IOException e) {e.printStackTrace();}}}.start();Thread.sleep(200); //main线程暂停200ms,防止服务端未启动System.out.println("TCP客户端启动...");socket("雷姆"); //启动客户端}public static void socket(String info) throws IOException {// TCP客户端Socket s = new Socket("192.168.0.108", 21024); // 明确连接地址// 明确数据源,这里发送一条文本数据byte[] buf = info.getBytes("UTF-8"); // 指定编码// 获取输出通道OutputStream out = s.getOutputStream();out.write(buf); // 发送数据// 关闭资源,out字节输出流对象本身是Socket套接字所持有,所以只需关闭套接字s.close();}public static void serverSocket() throws IOException {// TCP服务端ServerSocket ss = new ServerSocket(21024); // 监听一个端口// 获取Socket套接字对象Socket s = ss.accept(); // 阻塞性方法// 获取此套接字的IP地址String ip = s.getInetAddress().getHostAddress();int port = s.getPort();// 打印输出看看是谁连接了服务端System.out.println(ip + " : " + port + " connection"); // 获取此套接字的读取通道InputStream in = s.getInputStream();byte[] buf = new byte[1024]; // 数据容器 int length = in.read(buf); // 读取数据到容器,并返回读取到数据的字节长度String data = new String(buf, 0, length, "UTF-8");//打印输出数据System.out.println(ip + " : " + port + " data————> " + data);//关闭资源,in字节读取流本身是Socket套接字所持有,只需关闭套接字s.close(); //这个套接字是在本服务器内存生成的,所以需要关闭套接字ss.close(); //关闭服务端套接字}}
输出:
TCP服务端启动...
TCP客户端启动...
192.168.0.108 : 51381 connection
192.168.0.108 : 51381 data————> 雷姆
注意:
- 这里服务端获取连接的Socket套接字的读取流时,没有用到循环读取。
- 如果用了循环读取,服务端根本不知道是否已经读取完毕,所以客户端套接字对象需要调用 shutdownOutputStream() 方法发送输出结束标记给服务端,Socket 告诉 ServerSocket 我已输出完成服务端停止读取。
- TCP上传文件例子将运用 shutdownOutputStream() 方法。
TCP客户端与服务端互访
小改一下上面的代码,客户端和服务端,都获取 输出流 和 输入流 通道 并操作。
package com.bin.demo;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class Main {public static void main(String[] args) throws Exception {new Thread() {@Override public void run() {try {System.out.println("TCP服务端启动...");serverSocket(); //先启动服务端} catch (IOException e) {e.printStackTrace();}}}.start();Thread.sleep(200); //main线程暂停200ms,防止服务端未启动System.out.println("TCP客户端启动...");socket("保护雷姆"); //启动客户端}public static void socket(String info) throws IOException {// TCP客户端Socket s = new Socket("192.168.0.108", 21024); // 明确连接地址// 明确数据源,这里发送一条文本数据byte[] buf = info.getBytes("UTF-8"); // 指定编码// 获取输出通道OutputStream out = s.getOutputStream();out.write(buf); // 发送数据// 等待服务端回话InputStream in = s.getInputStream();byte[] inBuf = new byte[1024];int length = in.read(inBuf);String data = new String(inBuf, 0, length, "UTF-8");System.out.println(data);// 关闭资源,out和in字节输出流对象本身是Socket套接字所持有,所以只需关闭套接字s.close();}public static void serverSocket() throws IOException {// TCP服务端ServerSocket ss = new ServerSocket(21024); // 监听一个端口// 获取Socket套接字对象Socket s = ss.accept(); // 阻塞性方法// 获取此套接字的IP地址String ip = s.getInetAddress().getHostAddress();int port = s.getPort();// 打印输出看看是谁连接了服务端System.out.println(ip + " : " + port + " connection"); // 获取此套接字的读取通道InputStream in = s.getInputStream();byte[] buf = new byte[1024]; // 数据容器 int length = in.read(buf); // 读取数据到容器,并返回读取到数据的字节长度String data = new String(buf, 0, length, "UTF-8");//打印输出数据System.out.println(ip + " : " + port + " data————> " + data);// 回复客户端OutputStream out = s.getOutputStream();out.write("已接收到请求".getBytes("UTF-8"));//关闭资源,in和out字节读取流本身是Socket套接字所持有,只需关闭套接字s.close(); //这个套接字是在本服务器内存生成的,所以需要关闭套接字ss.close(); //关闭服务端套接字}}
输出:
TCP服务端启动...
TCP客户端启动...
192.168.0.108 : 53471 connection
192.168.0.108 : 53471 data————> 保护雷姆
已接收到请求
TCP上传文件
- 其实就是文件的Copy过程,客户端读取文件并发送给服务端。
package com.bin.demo;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class Main {public static void main(String[] args) throws Exception {new Thread() {@Override public void run() {try {System.out.println("TCP服务端启动...");serverSocket(); //先启动服务端} catch (IOException e) {e.printStackTrace();}}}.start();Thread.sleep(200); //main线程暂停200ms,防止服务端未启动System.out.println("TCP客户端启动...");socket(); //启动客户端}public static void socket() throws IOException {// TCP客户端Socket s = new Socket("192.168.0.108", 21024); // 明确连接地址// 明确数据源,上传文件FileInputStream file_in = new FileInputStream("F:\\test.txt");// 获取输出通道OutputStream out = s.getOutputStream();byte[] buf = new byte[1024]; //每次读取的字节容器int file_length; //读取到的字节数while ((file_length = file_in.read(buf)) != -1) {out.write(buf, 0, file_length); //写入读取到的字节长度}// 发送输出结束标记s.shutdownOutput();// 等待服务端回话InputStream in = s.getInputStream();byte[] inBuf = new byte[1024];int length = in.read(inBuf);String data = new String(inBuf, 0, length, "UTF-8");System.out.println(data);// 关闭资源,out和in字节输出流对象本身是Socket套接字所持有,所以只需关闭套接字file_in.close(); //关闭Copy文件流s.close();}public static void serverSocket() throws IOException {// TCP服务端ServerSocket ss = new ServerSocket(21024); // 监听一个端口// 获取Socket套接字对象Socket s = ss.accept(); // 阻塞性方法// 获取此套接字的IP地址String ip = s.getInetAddress().getHostAddress();int port = s.getPort();// 打印输出看看是谁连接了服务端System.out.println(ip + " : " + port + " connection"); // 获取此套接字的读取通道 + 明确源文件存放目地InputStream in = s.getInputStream();long time = System.currentTimeMillis(); // 获取当前时间戳设置文件名,避免文件重名或被覆盖问题FileOutputStream file_out = new FileOutputStream("F:\\test_" + ++time +".txt"); //存放目的地byte[] buf = new byte[1024]; // 每次读取的数据容器int length; // 读取到数据的字节长度while ((length = in.read(buf)) != -1) {file_out.write(buf, 0, length);}// 回复客户端OutputStream out = s.getOutputStream();out.write("上传成功".getBytes("UTF-8"));//关闭资源,in和out字节读取流本身是Socket套接字所持有,只需关闭套接字file_out.close(); //关闭写出文件流s.close(); //这个套接字是在本服务器内存生成的,所以需要关闭套接字ss.close(); //关闭服务端套接字}}
输出:
TCP服务端启动...
TCP客户端启动...
192.168.0.108 : 54457 connection
上传成功
TCP下载文件
- 其实就是文件的Copy过程,客户端请求服务端文件并下载。
package com.bin.demo;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class Main {public static void main(String[] args) throws Exception {new Thread() {@Override public void run() {try {System.out.println("TCP服务端启动...");serverSocket(); //先启动服务端} catch (IOException e) {e.printStackTrace();}}}.start();Thread.sleep(200); //main线程暂停200ms,防止服务端未启动System.out.println("TCP客户端启动...");socket(); //启动客户端}public static void socket() throws IOException {// TCP客户端Socket s = new Socket("192.168.0.108", 10248); // 明确连接地址// 请求的文件路径String file = new String("F:\\test.txt");// 获取输出通道OutputStream out = s.getOutputStream();out.write(file.getBytes("UTF-8")); //发送请求// 等待服务端返回数据InputStream in = s.getInputStream();FileOutputStream file_out = new FileOutputStream("F:\\下载_test.txt");byte[] inBuf = new byte[1024]; //每次读取的字节大小int length;while ((length = in.read(inBuf)) != -1) {file_out.write(inBuf, 0, length);}System.out.println("下载完成");// 关闭资源,out和in字节输出流对象本身是Socket套接字所持有,所以只需关闭套接字file_out.close(); //关闭写出文件流s.close();}public static void serverSocket() throws IOException {// TCP服务端ServerSocket ss = new ServerSocket(10248); // 监听一个端口// 获取Socket套接字对象Socket s = ss.accept(); // 阻塞性方法// 获取此套接字的IP地址String ip = s.getInetAddress().getHostAddress();int port = s.getPort();// 打印输出看看是谁连接了服务端System.out.println(ip + " : " + port + " connection"); // 获取此套接字的读取通道InputStream in = s.getInputStream();byte[] buf = new byte[1024];int length = in.read(buf);File file = new File(new String(buf, 0, length));if (file.exists()) { //检查服务器是否存在该文件// 回复客户端OutputStream out = s.getOutputStream(); //获取输出通道FileInputStream file_in = new FileInputStream(file);byte[] file_buf = new byte[1024];int file_len;while ((file_len = file_in.read(file_buf)) != -1) {out.write(file_buf, 0, file_len);}// 发送一个输出结束标记file_in.close(); //关闭读取文件流}//关闭资源,in和out字节读取流本身是Socket套接字所持有,只需关闭套接字s.close(); //这个套接字是在本服务器内存生成的,所以需要关闭套接字ss.close(); //关闭服务端套接字}}
输出:
TCP服务端启动...
TCP客户端启动...
192.168.0.108 : 55706 connection
下载完成
TCP多客户端并发访问服务器
- 服务器一般都是不需要直接关闭的,而是持久的运行着。
- 这里服务端只需要为每个已连接Socket套接字开启新线程,就OK了。+ 服务器无限循环。
这里用了TCP上传文件的例子示范
package com.bin.demo;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class Main {public static void main(String[] args) throws Exception {new Thread() {@Override public void run() {try {System.out.println("TCP服务端启动...");serverSocket(); //先启动服务端} catch (IOException e) {e.printStackTrace();}}}.start();Thread.sleep(200); //main线程暂停200ms,防止服务端未启动for (int i = 0; i < 10; ++i) { //创建10条线程并发访问服务器,上传文件new Thread() {@Override public void run() {try {socket(); //启动客户端} catch (IOException e) {e.printStackTrace();}}}.start();}}public static void socket() throws IOException {// TCP客户端Socket s = new Socket("192.168.0.108", 21024); // 明确连接地址// 明确数据源,上传文件FileInputStream file_in = new FileInputStream("F:\\test.txt");// 获取输出通道OutputStream out = s.getOutputStream();byte[] buf = new byte[1024]; //每次读取的字节容器int file_length; //读取到的字节数while ((file_length = file_in.read(buf)) != -1) {out.write(buf, 0, file_length); //写入读取到的字节长度}// 发送输出结束标记s.shutdownOutput();// 等待服务端回话InputStream in = s.getInputStream();byte[] inBuf = new byte[1024];int length = in.read(inBuf);String data = new String(inBuf, 0, length, "UTF-8");System.out.println(data);// 关闭资源,out和in字节输出流对象本身是Socket套接字所持有,所以只需关闭套接字file_in.close(); //关闭Copy文件流s.close();}public static void serverSocket() throws IOException {// TCP服务端ServerSocket ss = new ServerSocket(21024); // 监听一个端口// 无限循环while (true) {// 获取Socket套接字对象Socket s = ss.accept(); // 阻塞性方法// 服务端只需要为每个已连接Socket套接字开启新线程,就OK了new Thread(new Task(s)).start();}}static class Task implements Runnable {private Socket s;private static int count; //文件计数,防止文件被覆盖Task(Socket s) {this.s = s;}@Override public void run() {try {// 获取此套接字的IP地址String ip = s.getInetAddress().getHostAddress();int port = s.getPort();// 打印输出看看是谁连接了服务端System.out.println(ip + " : " + port + " connection"); // 获取此套接字的读取通道 + 明确源文件存放目地InputStream in = s.getInputStream();long time = System.currentTimeMillis(); // 获取当前时间戳设置文件名,避免文件重名或被覆盖问题FileOutputStream file_out = new FileOutputStream("F:\\test_" + time + "_" + ++count + ".txt"); //存放目的地byte[] buf = new byte[1024]; // 每次读取的数据容器int length; // 读取到数据的字节长度while ((length = in.read(buf)) != -1) {file_out.write(buf, 0, length);}// 回复客户端OutputStream out = s.getOutputStream();out.write("上传成功".getBytes("UTF-8"));//关闭资源,in和out字节读取流本身是Socket套接字所持有,只需关闭套接字file_out.close(); //关闭写出文件流s.close(); //这个套接字是在本服务器内存生成的,所以需要关闭套接字} catch (Exception e) {// ...}}}}
输出:
TCP服务端启动...
192.168.0.108 : 56845 connection
192.168.0.108 : 56848 connection
192.168.0.108 : 56846 connection
192.168.0.108 : 56847 connection
192.168.0.108 : 56849 connection
上传成功
上传成功
上传成功
192.168.0.108 : 56850 connection
192.168.0.108 : 56852 connection
上传成功
上传成功
192.168.0.108 : 56851 connection
192.168.0.108 : 56853 connection
192.168.0.108 : 56854 connection
上传成功
上传成功
上传成功
上传成功
上传成功
TCP几个小问题
- 避免上传或下载重名问题,文件会被覆盖
- Socket套接字上传文件已到末尾结束时,必须调用 Socket套接字的 shutdownOutput() 方法发送输出结束标记给服务端
- Socket下载服务端文件时,服务端输出完成无需调用 Socket 套接字的 shutdownOutput() 方法。
- 多客户端并发访问服务器时,为每个Socket套接字单独用创建一条线程运行。
- 如果一个客户端已连接服务端,但某个时刻客户端挂掉了,服务端不知道,这时候服务端会有计时器,每隔一段时间发送消息到已挂掉的客户端判断是否还在,达到一定次数客户端未回应后,服务端就会自动关闭这个已挂掉的套接字资源。
浏览器与Tomcat
浏览器访问服务器基本原理
- 封装了Socket的程序都是客户端(如浏览器,不同厂商的浏览器)
- 封装了ServerSocket的程序都为服务器(如Tomcat)。
- html文件包含需要的资源信息会继续发送请求(也就是并不是我们表面上看到请求一个html页面时只发送一个请求)
- 不指定资源则会默认返回默认资源(index.html)
- 指定资源请求时可能还会附加请求参数(用户名、密码)
- 浏览器其实就是封装了Socket,服务器封装了ServerSocket
模拟服务器接收浏览器发送的HTTP协议请求消息
package com.bin.demo;import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;public class Main {public static void main(String[] args) throws Exception {// 模拟服务器ServerSocket ss = new ServerSocket(9090);System.out.println("服务器启动 ...");// 获取连接对象Socket s = ss.accept();System.out.println(s.getInetAddress().getHostAddress() + " : " + s.getPort() + " connection"); // 打印谁连接了服务器// 读取请求HTTP协议信息InputStream in = s.getInputStream();byte[] buf = new byte[1024];int length = in.read(buf);String str = new String(buf);System.out.println(str); // 打印请求的HTTP协议信息// 关闭资源s.close();ss.close();}}
输出:
服务器启动 ...
192.168.0.108 : 51853 connection
GET / HTTP/1.1
Host: 192.168.0.108:9090
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
浏览器发送的HTTP协议请求信息图解:
模拟浏览器接收服务器返回的HTTP协议响应消息
package com.bin.demo;import java.io.InputStream;
import java.io.PrintWriter;
import java.net.Socket;public class Main {public static void main(String[] args) throws Exception {// 模拟浏览器Socket s = new Socket("www.baidu.com", 80); // web资源的域名,一般默认为80端口// 发送请求PrintWriter out = new PrintWriter(s.getOutputStream(), true); //使用打印流,第二个参数的自动刷新out.println("GET / HTTP/1.1");out.println("Host: " + s.getInetAddress().getHostAddress() +":80");out.println("Connection: close");out.println("Accept: */*"); //告诉服务器我支持的文件查看类型,这里使用通配符告诉服务器我支持全部类型的文件out.println("Accept-Language: zh");out.println();// 接收返回的HTTP响应协议信息InputStream in = s.getInputStream();byte[] buf = new byte[3024]; //这里只读取3024,自己调调也可以int length = in.read(buf);System.out.println(new String(buf, 0, length, "UTF-8")); //国际标准的UTF-8编码解码方式// 关闭资源s.close();}}
这里只提取一部分HTTP响应内容:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Content-Length: 14615
Content-Type: text/html
Date: Sun, 05 Apr 2020 17:07:16 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Pragma: no-cache
Server: BWS/1.1
Set-Cookie: BAIDUID=F42521B806B3D4A92243E42336518FED:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=F42521B806B3D4A92243E42336518FED; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1586106436; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BAIDUID=F42521B806B3D4A9493E1FA4E62E6E4D:FG=1; max-age=31536000; expires=Mon, 05-Apr-21 17:07:16 GMT; domain=.baidu.com; path=/; version=1; comment=bd
Traceid: 1586106436030523265010215562608745898825
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
Connection: close<!DOCTYPE html><!--STATUS OK-->
<html>
<head><meta http-equiv="content-type" content="text/html;charset=utf-8"><meta http-equiv="X-UA-Compatible" content="IE=Edge"><link rel="dns-prefetch" href="//s1.bdstatic.com"/><link rel="dns-prefetch" href="//t1.baidu.com"/><link rel="dns-prefetch" href="//t2.baidu.com"/><link rel="dns-prefetch" href="//t3.baidu.com"/><link rel="dns-prefetch" href="//t10.baidu.com"/><link rel="dns-prefetch" href="//t11.baidu.com"/><link rel="dns-prefetch" href="//t12.baidu.com"/><link rel="dns-prefetch" href="//b1.bdstatic.com"/><title>百度一下,你就知道</title><link href="http://s1.bdstatic.com/r/www/cache/static/home/css/index.css" rel="stylesheet" type="text/css" /><!--[if lte IE 8]><style index="index" >#content{height:480px\9}#m{top:260px\9}</style><![endif]--><!--[if IE 8]><style index="index" >#u1 a.mnav,#u1 a.mnav:visited{font-family:simsun}</style><![endif]--><script>var hashMatch = document.location.href.match(/#+(.*wd=[^&].+)/);if (hashMatch && hashMatch[0] && hashMatch[1]) {document.location.replace("http://"+location.host+"/s?"+hashMatch[1]);}var ns_c = function(){};</script><script>function h(obj){obj.style.behavior='url(#default#homepage)';var a = obj.setHomePage('//www.baidu.com/');}</script><noscript><meta http-equiv="refresh" content="0; url=/baidu.html?from=noscript"/></noscript><script>window._ASYNC_START=new Date().getTime();</script>
</head>
简单的图解:
资源定位类
URL类
URL链接地址字符串的解析对象
基本的方法:
- getProtocol():获取协议
- getHost():获取IP地址
- getPort():获取端口
- getFile():获取资源路径及请求参数
- getQuery():只获取参数
- URLConnection openConnection():创建返回一个URLConnection连接对象,调用此方法将会进行连接,它代表应用程序和 URL 之间的通信链接。此类的实例可用于读取和写入此 URL 引用的资源(重要方法)
package com.bin.demo;import java.net.URL;public class Main {public static void main(String[] args) throws Exception {//创建URL对象URL url = new URL("http://192.168.0.108:9090/myapp/1.html?user=bin&password=520"); //请求这个页面时携带数据参数//基本方法,解析URL中的数据System.out.println("getProtocol : " + url.getProtocol()); //获取协议System.out.println("getHost : " + url.getHost());System.out.println("getPort : " + url.getPort());System.out.println("getFile : " + url.getFile()); //获取资源路径及请求参数System.out.println("getQuery : " + url.getQuery()); //获取参数}}
输出:
getProtocol : http
getHost : 192.168.0.108
getPort : 9090
getFile : /myapp/1.html?user=bin&password=520
getQuery : user=bin&password=520
URLConnection类
此类封装了Socket,也就是能和服务器进行读写操作的类。
它代表应用程序和 URL 之间的通信链接。此类的实例可用于读取和写入此 URL 引用的资源。
此类是抽象类,通过URL对象创建URLConnection连接对象
基础常用方法:
- OutputStream getOutputStream():写出数据到此资源定位路径。
- InputStream getInputStream():读取此定位资源。
- URL getURL():返回URL对象。
package com.bin.demo;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;public class Main {public static void main(String[] args) throws Exception {//创建URL对象URL url = new URL("http://www.baidu.com");//获得URL连接对象URLConnection conn = url.openConnection();//读取此定位资源BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));String str = null;while ((str = in.readLine()) != null) {System.out.println(str);}}}
输出(这里是被解析后的应答体数据):
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a> 京ICP证030173号 <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
注意:
返回的数据已经帮我们解析好了,读取服务器返回的HTTP协议消息应答头已被解析,甚至连应答体中包含的资源信息也被解析了,所以给程序员返回的只有数据内容。要想拿到应答头调用相应方法即可。
URLConnection 下载图片实战
基础常用方法:
- setRequestProperty(String key, String value):设置一般请求属性。如果已存在具有该关键字的属性,则用新值改写其值。设置的一般请求属性取决于协议。
常用方法(连接成功(URL调用openConnection()方法)后可调用的方法):
- OutputStream getOutputStream():写出数据到此资源定位路径。
- InputStream getInputStream():读取此定位资源。
- String getContentEncoding():获取数据的压缩方式,如gzip、deflate、compress
- String getContentType():获取内容数据类型,如文件的类型。
- int getContentLength():获取响应体内容数据的长度,为int类型,单位字节byte。
- long getContentLengthLong():同getContentLength()方法,这里返回long类型。
- long getLastModifed():获取此资源的上一次修改时间,单位为毫秒值。
- String getHeaderField(String key):获取返回的响应头信息。
package com.bin.demo;import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.Date;public class Main {public static void main(String[] args) throws Exception {//创建URL对象URL url = new URL("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586181338511&di=0ef74d62555781d08e5a0968b397c75e&imgtype=0&src=http%3A%2F%2Fb.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2F83025aafa40f4bfb1209aa1d0b4f78f0f6361899.jpg");//获得URL连接对象URLConnection conn = url.openConnection();//获取参数String http = conn.getHeaderField(null); // 获取第一行应答头信息,也就是协议头String encoding = conn.getContentEncoding();String type = conn.getContentType();int length = conn.getContentLength();long lengthLong = conn.getContentLengthLong();long motifyTime = conn.getLastModified();System.out.println("协议头 :" + http);System.out.println("数据压缩方式 :" + encoding);System.out.println("类型 :" + type);System.out.println("int字节长度 :" + length);System.out.println("long自己长度 :" + lengthLong);System.out.println("上一次修改时间 :" + new SimpleDateFormat("yyyy年MM/dd日 k:m:s:S").format(new Date(motifyTime)));System.out.println("——————————————————");//确定存放路径FileOutputStream out = new FileOutputStream("F:\\LeiMu.jpg");//读取此定位资源BufferedInputStream in = new BufferedInputStream(conn.getInputStream());byte[] buf = new byte[1024]; //每次读取的字节数int len = 0;long count = 0; //判断文件是否下载成功的统计字节数(根据真实内容的字节大小 对比 已下载的内容字节大小)while ((len = in.read(buf)) != -1) {count += len;out.write(buf, 0, len);}//关闭下载Copy资源out.close();// 判断是否下载完成,(数据是否完整)if (count == lengthLong) {System.out.println("下载成功");}}}
输出:
协议头 :HTTP/1.1 200 OK
数据压缩方式 :null
类型 :image/jpeg
int字节长度 :44160
long自己长度 :44160
上一次修改时间 :2016年08/14日 19:51:29:0
——————————————————
下载成功
下面是一些常见的Content-Type
字段的值:
- text/plain
- text/html
- text/css
- image/jpeg
- image/png
- image/svg+xml
- audio/mp4
- video/mp4
- application/javascript
- application/pdf
- application/zip
- application/atom+xml
HttpURLConnection类
- 此类是抽象类并继承自URLConnection类,通养通过URL的openConnection()获取 URLConnection对象并强转为HttpURLConnection对象。
- 此类支持 HTTP 特定功能的 URLConnection。
常见基础方法:
setRequestMethod(String method):设置 URL 请求的方法,以上方法之一是合法的,具体取决于协议的限制。默认方法为 GET。
- GET
- POST
- HEAD
- OPTIONS
- PUT
- DELETE
- TRACE
- int getResponseCode():获取返回的状态码。
- String getRequestMethod():获取当前请求的方法。
- String getRequestMessage():获取返回的状态码描述信息。
- disconnect() : 关闭连接。
静态常量状态码,还有很多没截图完:
package com.bin.demo;import java.net.HttpURLConnection;
import java.net.URL;public class Main {public static void main(String[] args) throws Exception {//创建URL对象URL url = new URL("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586181338511&di=0ef74d62555781d08e5a0968b397c75e&imgtype=0&src=http%3A%2F%2Fb.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2F83025aafa40f4bfb1209aa1d0b4f78f0f6361899.jpg");//获得URL连接对象HttpURLConnection conn = (HttpURLConnection) url.openConnection();String method = conn.getRequestMethod(); //请求方法int code = conn.getResponseCode(); //响应状态码String codeInfo = conn.getResponseMessage(); //状态码描述信息System.out.println(method); //GETSystem.out.println(code); //200System.out.println(codeInfo); //OKconn.disconnect(); //关闭连接}}
输出:
GET
200
OK
作用
Socket 和 ServerSocket:
- 能拿到请求与应答(头与体)消息的全部消息,但并没有进一步的资源信息解析
- Socket 接收 ServerSocket 返回的应答体也就是源码
URLConnection对象(返回的数据已经帮我们解析好了):
- 读取服务器返回的HTTP协议消息应答头已被解析,甚至连应答体中包含的资源信息也被解析了,所以给程序员返回的只有数据内容。
- 想要得到应答头内容,只需要调用相应API方法。
HttpURLConnection:
- 看名字就知道是基于HTTP的,加入了响应码,以及请求参数的设置如 GET POST 方法。
Socket ,URLConnection,HttpURLConnection 这几个对象同样能进行从网络数据的发送与接收,同样可以进行图片的下载。
网络架构
C/S
Client Server (客户端服务器)
- 客户端和服务端都需要编写
- 客户端需要维护
- 客户端可以分担运算
如大型运算的网络游戏,3D建模,技能特效。
B/S
Browser Server (浏览器服务器)
- 只需要编写服务端,(客户端就是浏览器)
- 客户端是不必关心的,不需要维护的
- 运算全在服务器端
HTTP
HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范
HTTP响应模型
模型 | 简介作用 |
单进程I/O模型 | 服务端开启一个进程,一个进程仅能处理一个请求,并且对请求顺序处理 |
多进程I/O模型 | 服务端并行开启多个进程,同样的一个进程只能处理一个请求,这样服务端就可以同时处理多个请求 |
复用I/O模型 | 服务端开启一个进程,但是呢,同时开启多个线程,一个线程响应一个请求,同样可以达到同时处理多个请求,线程间并发执行 |
复用多线程I/O模型 | 服务端并行开启多个进程,同时每个进程开启多个线程,这样服务端可以同时处理进程数M*每个进程的线程数N个请求。 |
协议版本
协议版本 | 简介作用 |
HTTP/0.9 | HTTP协议的最初版本,功能简陋,仅支持请求方式GET,并且仅能请求访问HTML格式的资源。 |
HTTP/1.0 |
在0.9版本上做了进步,增加了请求方式POST和HEAD;不再局限于0.9版本的HTML格式,根据Content-Type可以支持多种数据格式。 但是1.0版本的工作方式是每次TCP连接只能发送一个请求,当服务器响应后就会关闭这次连接,下一个请求需要再次建立TCP连接,就是不支持Connection: keep-alive |
HTTP/1.1 |
1.1 版的最大变化,就是引入了持久连接(persistent connection),即TCP连接默认不关闭,可以被多个请求复用,不用声明 客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。不过,规范的做法是,客户端在最后一个请求时,发送 |
HTTP/2.0 |
为了解决1.1版本利用率不高的问题,提出了HTTP/2.0版本。增加双工模式,即不仅客户端能够同时发送多个请求,服务端也能同时处理多个请求,解决了队头堵塞的问题(HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级)并以压缩的方式传输,提高利用率。 当前主流的协议版本还是HTTP/1.1版本。 |
HTTP/1.0与HTTP.1.1比较图:
通用协议标头
可以出现在请求标头和响应标头中。
字段 | 作用 |
Date | 返回的值为距离格林威治标准时间 1970 年 1 月 1 日的毫秒数。 |
Cache-Control |
有四个参数:
|
Connection |
Connection 决定当前事务(一次三次握手和四次挥手)完成后,是否会关闭网络连接。Connection 有两种,一种是
|
HTTP1.1 其他通用标头如下:
实体协议标头
实体标头是描述消息正文内容的 HTTP 标头。实体标头用于 HTTP 请求和响应中。
字段 | 作用 |
Content-Length | 体报头指示实体主体的大小,以字节为单位,发送到接收方。 |
Content-Language | 实体报头描述了客户端或者服务端能够接受的语言 |
Content-Encoding |
这个实体报头用来压缩媒体类型。Content-Encoding 指示对实体应用了何种编码。
|
实体标头字段图:
请求协议标头
字段 | 作用 | ||||||||||
Host | Host 请求头指明了服务器的域名(对于虚拟主机来说),以及(可选的)服务器监听的TCP端口号。如果没有给定端口号,会自动使用被请求服务的默认端口(比如请求一个 HTTP 的 URL 会自动使用80作为端口)。 | ||||||||||
Referer | HTTP Referer 属性是请求标头的一部分,当浏览器向 web 服务器发送请求的时候,一般会带上 Referer,告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理。 | ||||||||||
Upgrade-Insecure-Requests | Upgrade-Insecure-Requests 是一个请求标头,用来向服务器端发送信号,表示客户端优先选择加密及带有身份验证的响应。如:Upgrade-Insecure-Requests: 1 | ||||||||||
If-Modified-Since |
HTTP 的 If-Modified-Since 使其成为条件请求:
If-Modified-Since 通常会与 If-None-Match 搭配使用,If-Modified-Since 用于确认代理或客户端拥有的本地资源的有效性。获取资源的更新日期时间,可通过确认首部字段 Last-Modified 来确定。 大白话说就是如果在 Last-Modified 之后更新了服务器资源,那么服务器会响应200,如果在 Last-Modified 之后没有更新过资源,则返回 304。 |
||||||||||
If-None-Match |
If-None-Match HTTP请求标头使请求成为条件请求。 对于 GET 和 HEAD 方法,仅当服务器没有与给定资源匹配的 ETag 时,服务器才会以200状态发送回请求的资源。 对于其他方法,仅当最终现有资源的ETag 与列出的任何值都不匹配时,才会处理请求。
|
||||||||||
Accept |
告知服务端,客户端都安装了那些媒体软件,可接收那种数据类型 文本文件: text/html、text/plain、text/css、application/xhtml+xml、application/xml 图片文件: image/jpeg、image/gif、image/png 视频文件: video/mpeg、video/quicktime 应用程序二进制文件: application/octet-stream、application/zip 如:Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 q 表示的是权重,媒体类型增加优先级,没有显示权重的时候默认值是1.0 :
这是一个放置顺序,权重高的在前,低的在后 |
||||||||||
Accept-Charset |
规定服务器处理表单数据所使用的字符集。 常用的字符集有: UTF-8 - Unicode 字符编码 ; |
||||||||||
Accept-Language | 告知服务器用户代理能够处理的自然语言 |
基于 HTTP 1.1:
响应协议标头
字段 | 作用 |
Access-Control-Allow-Origin | 一个返回的 HTTP 标头可能会具有 Access-Control-Allow-Origin ,Access-Control-Allow-Origin 指定一个来源,它告诉浏览器允许该来源进行资源访问。 否则-对于没有凭据的请求 *通配符,告诉浏览器允许任何源访问资源。例如,要允许源 https://mozilla.org 的代码访问资源 |
Keep-Alive |
Keep-Alive 表示的是 Connection 非持续连接的存活时间,如下: Connection: Keep-Alive Keep-Alive: timeout=5, max=997 有两个参数:
|
Server | 服务器使用的软件的信息。 |
Set-Cookie | 服务器返回的Cookie认证标识 |
Transfer-Encoding |
首部字段 Transfer-Encoding 规定了传输报文主体时采用的编码方式。 |
X-Frame-Options |
首部字段 X-Frame-Options 属于 HTTP 响应首部,用于控制网站内容在其他 Web 网站的 Frame 标签内的显示问题。其主要目的是为了防止点击劫持(clickjacking)攻击。
|
基于 HTTP 1.1:
状态码
状态码类别:
类别 | 原因短语 |
1XX | Informational(信息性状态码) 接受的请求正在处理 |
2XX | Success(成功状态码) 请求正常处理完毕 |
3XX | Redirection(重定向状态码) 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误状态码) 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) 服务器处理请求出错 |
常用HTTP状态码:
2XX | 成功(这系列表明请求被正常处理了) |
200 | OK,表示从客户端发来的请求在服务器端被正确处理 |
204 | No content,表示请求成功,但响应报文不含实体的主体部分 |
206 | Partial Content,进行范围请求成功 |
3XX | 重定向(表明浏览器要执行特殊处理) |
301 | moved permanently,永久性重定向,表示资源已被分配了新的 URL |
302 | found,临时性重定向,表示资源临时被分配了新的 URL |
303 | see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源(对于301/302/303响应,几乎所有浏览器都会删除报文主体并自动用GET重新请求) |
304 | not modified,表示服务器允许访问资源,但请求未满足条件的情况(与重定向无关) |
307 | temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求 |
4XX | 客户端错误 |
400 | bad request,请求报文存在语法错误 |
401 | unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 |
403 | forbidden,表示对请求资源的访问被服务器拒绝,可在实体主体部分返回原因描述 |
404 | not found,表示在服务器上没有找到请求的资源 |
5XX | 服务器错误 |
500 | internal sever error,表示服务器端在执行请求时发生了错误 |
501 | Not Implemented,表示服务器不支持当前请求所需要的某个功能 |
503 | service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 |
常用请求方法
HTTP协议中定义了浏览器和服务器进行交互的不同方法,基本方法有4种,分别是GET,POST,PUT,DELETE。这四种方法可以理解为,对服务器资源的查,改,增,删。
- GET:从服务器上获取数据,也就是所谓的查,仅仅是获取服务器资源,不进行修改。
- POST:向服务器提交数据,这就涉及到了数据的更新,也就是更改服务器的数据。
- PUT:英文含义是放置,也就是向服务器新添加数据,就是所谓的增。
- DELETE:从字面意思也能看出,这种方式就是删除服务器数据的过程。
HTTP与HTTPS的区别
区别 | HTTP | HTTPS |
协议 | 运行在 TCP 之上,明文传输,客户端与服务器端都无法验证对方的身份 | 身披 SSL( Secure Socket Layer )外壳的 HTTP,运行于 SSL 上,SSL 运行于 TCP 之上, 是添加了加密和认证机制的 HTTP。 |
端口 | 80 | 443 |
资源消耗 | 较少 | 由于加解密处理,会消耗更多的 CPU 和内存资源 |
开销 | 无需证书 | 需要证书,而证书一般需要向认证机构购买 |
加密机制 | 无 | 共享密钥加密和公开密钥加密并用的混合加密机制 |
安全性 | 弱 | 由于加密机制,安全性强 |
Session、Cookie和Token
HTTP协议本身是无状态的。什么是无状态呢,即服务器无法判断用户身份。
什么是cookie:
cookie是由Web服务器保存在用户浏览器上的小文件(key-value格式),包含用户相关的信息。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户身份。
什么是session:
session是依赖Cookie实现的。session是服务器端对象
session 是浏览器和服务器会话过程中,服务器分配的一块储存空间。服务器默认为浏览器在cookie中设置 sessionid,浏览器在向服务器请求过程中传输 cookie 包含 sessionid ,服务器根据 sessionid 获取出会话中存储的信息,然后确定会话的身份信息。
cookie与session区别:
- 存储位置与安全性:cookie数据存放在客户端上,安全性较差,session数据放在服务器上,安全性相对更高;
- 存储空间:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,session无此限制
- 占用服务器资源:session一定时间内保存在服务器上,当访问增多,占用服务器性能,考虑到服务器性能方面,应当使用cookie。
什么是Token:
Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。
Token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
使用Token的目的:Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。
Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。
session与token区别:
- session机制存在服务器压力增大,CSRF跨站伪造请求攻击,扩展性不强等问题;
- session存储在服务器端,token存储在客户端
- token提供认证和授权功能,作为身份认证,token安全性比session好;
- session这种会话存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上,token适用于项目级的前后端分离(前后端代码运行在不同的服务器下)
深入学习参考:重学TCP/IP协议和三次握手四次挥手
参考:【计算机网络】MAC、IP、DNS、DHCP 浅谈
【Java】网络编程——TCP/UDP网络对讲机相关推荐
- 网络编程(Tcp/Udp实现聊天、文件上传)
网络编程 1.1 概述 计算机网络是指将位置不同的多台[计算机 通过通信线路连接起来,实现资源共享和信息传递的计算机系统 1.2 网络通信的要素 ip和端口 网络通信协议(tcp/udp) 1.3 I ...
- 大数据 -- java基础16 网络编程 TCP UDP
1.网络编程的三要素:(1)IP地址(2)端口号(3)协议 2.IP地址 : 网络中计算机唯一标识. (1)IP地址在计算机中存储的时候是由二进制的形式进行存储的:IP:192.168.7.42 十进 ...
- 网络编程-tcp/udp
Java网络编程 计算机网络就是通过传输介质.通信设施和网络协议,把分散在不同地点的计算设备互连起来,实现资 源共享和数据传输的系统. TCP/IP协议簇 TCP/IP协议栈是一系列网络协议的总和,是 ...
- 网络编程 TCP/UDP
网络编程 打电话–连接–接了–通话->TCP连接 发短信------发送了就完事了---->UDP连接 网络编程的目的: 传播交流信息,数据交换.资源共享.通信 想要打到这个效果需要什么: ...
- [windows网络编程]tcp/udp编程初步详解-转
#pragma comment (lib,"ws2_32.lib") #include <Winsock2.h> #include <stdio.h> 如你 ...
- 网络编程---TCP/UDP套接字编程原理
本篇介绍的是Linux下的网络编程,故有些接口是不适用于Windows的,但是具体概念和实现方法是大体一致的 本篇重在讲解原理,具体实现请戳这里->UDP套接字编程实现 介绍 网络编程套接字(s ...
- JDK -- 网络编程(TCP/UDP)
三要素: IP地址: 设备在网络中的地址,是唯一的标识 端口: 应用程序在设备中唯一的标识 协议 : 数据在网络中传输的规则,常见的协议有UDP协议和TCP协议 InetAddress -- 代表IP ...
- 网络编程---tcp/udp协议
协议使用场景: udp协议(User Datagram Protocol) 即时通讯类的软件 :qq 微信 飞信 歪歪 tcp协议(Transmission Control Protocol) 发邮件 ...
- 网络编程 TCP电子网络词库
电子词典: 要求: 登录注册功能,不能重复登录,重复注册.用户信息也存储在数据库中. 单词查询功能 历史记录功能,存储单词,意思,以及查询时间,存储在数据库 基于TCP,支持多客户端连接 采用数据库保 ...
最新文章
- 统计s=hello alex alex hello haiyan cc haiyan com中每个单词的个数
- 2021-08-27 思考:1000瓶药水,1瓶有毒,老鼠毒发24h,如何用最少的老鼠在24h内找出毒药?
- elasticSearch6源码分析(4)indices模块
- 【Asp.Net】:如何处理大量页面的身份验证跳转
- 用JavaScript将字符串中的单词大写
- python acme_Python Hashlib模块 · Seacme Huang
- mysql的备份与恢复_实验十一 MySQLl备份与恢复1
- mysql学生成绩视图_mysql的视图
- Bailian2995 登山【LIS】
- Android -很全的android操作内容丰富
- kuangbin专题-简单搜索
- 关于NMDS的一知半解
- [常用办公软件] wps怎么自动生成目录?wps自动生成目录的设置教程
- Google快讯-UTStarcom
- html table中td内容超出显示.怎么实现
- 如何在 vscode 中更换炫酷的背景图
- 50 岁的程序员该何去何从?
- 阿米洛键盘失灵_改变静电容键盘手感单一限制,阿米洛静电容机械轴V2上手
- windows10下激活conda环境报错CommandNotFoundError: Your shell has not been properly configured to use conda
- 鲤鱼溪传说:神鱼与三仙姑
热门文章
- 未知坐标系CAD网格图转换为真实坐标的矢量kml/shp数据
- 哪个版本Rhinoceros支持M1intel Monterey系统?Rhinoceros for mac(犀牛建模)v7.3.21053.23032中文激活方法 功能介绍
- linux安装fortran_Intel Parallel Studio XE 2019安装设置
- java 线程状态_关于JAVA线程状态
- 大数据文字游戏_[评论]微信优化2.5G网络疑似文字游戏
- Meta Learning 元学习
- 怎样修改火狐的默认搜索引擎_如何将Firefox的默认搜索引擎更改回Google
- go语言MVC框架beego快速入门
- 无线通信零基础学习记录(2)——GSM空中接口物理层的设计
- Android 一键清理内存,缓存,文件代码