一、JavaSocket编程
1.1HTTP协议
- 后端原理
2. 特点
同步:就是两个任务执行的过程中,其中一个任务要等另一个任务完成某各阶段性工作才能继续执行,如厨师A炒番茄,将葱花放入锅中,然后需要放入番茄,但是厨师B还没有把番茄切好,厨师A就得等厨师A把番茄切好才能继续,期间处于等待状态。
1.2HTTP请求/响应格式
1.3.1请求头
1.3.2响应头
注意:GET请求的请求体是空的 ,POST请求可以不为空
1.3.3响应体
1.3网络编程
1.3.1计算机网络
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
1.3.2网络通信协议
通过计算机网络可以实现不同计算机之间的连接和通信,但是计算机网络中实现通信必须有一些约定即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
国际标准化组织(ISO,即International Organization for Standardization)定义了网络通信协议的基本框架,被称为OSI(Open System Interconnect,即开放系统互联)模型。要制定通讯规则,内容很多,比如需要考虑A电脑如何找到B电脑,A电脑在发送信息给B电脑时是否需要B电脑进行反馈,A电脑传送给B电脑的数据格式又是怎样的?内容太多太杂,所以OSI模型将这些通讯标准进行层次划分,每一层次解决一个类别的问题,使得标准的制定没那么复杂。OSI模型制定的七层标准模型,分别是应用层,表示层,会话层,传输层,网络层,数据链路层和物理层。
网络协议的分层(互联网使用最多的网络通信协议是TCP/IP网络通信协议)
按照层次划分,共四层:应用层、传输层、网络层、网络接口层(物理+数据链路层)
OSI和TCP/IP模型的对应关系:
1.3.3IP地址
IP是Internet Protocol Address(互联网协议地址)
用来标识网络中的一个通信实体的地址。通信实体可以是计算机、路由器等。比如互联网的每个服务器都要有自己的IP地址,每个局域网事物计算机通信也要配置IP地址。
路由器是连接两个或多个网络的网络设备。
类别 最大网络数 IP地址范围 单个网络最大主机数 私有IP地址范围
IP地址分类
A 126(2^7-2) 1.0.0.1-127.255.255.254 16777214 10.0.0.0-10.255.255.255
B 1638(2^14) 128.0.0.1-191.255.255.254 65534 172.16.0.0-172.31.255.255
C 2097152(2^21) 192.0.0.1-223.255.255.254 254 192.168.0.0-192.168.255.255
目前主流使用的IP地址是IPV4,但是随着网络规模的不断扩大,IPV4面临着枯竭的危险,推出了IPV6。
IPV4采用了32位地址长度,只有大约43亿个地址,它只有4段数字,每一段最大不超过255,。随着互联网的发展,IP地址不够用了,在2019年11月25日IPv4位地址分配完毕。IP地址实际上是一个32位整数(称为IPv4),以字符串表示的IP地址如192.168.0.1实际上是把32位整数按8位分组后的数字表示,目的是方便阅读
IPV6采用了128位地址长度,几乎可以部首限制地提供住址。按保守方法估算IPv6实际可分配地址,整个地球的每平方米面积上仍可分配1000多个地址。IPv6地址实际上是一个128位整数。它是目前使用的IPv4的升级版,以字符串表示类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334
1.3.4端口号Port
- 概念:端口号用来识别计算机中进行通信的应用程序,也被称为程序地址。一台计算机上同时可以运行多个程序。传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并准确地进行数据传输。
- IP地址好比每个人的地址(门牌号),端口号好比是房间号。必须同时指定IP地址和端口号才能正确的发送数据。
1.3.5URL
URL(Uniform Resource Locator),是互联网的统一资源定位符。用于识别互联网中的信息资源。通过URL我们可以访问文件、数据库、图像、新闻等。
在互联网上,每一条信息资源都有统一且唯一的地址,该地址就叫URL。URL由4部分组成:协议、存放资源的主机域名、资源文件名和端口号。如果未指定该端口号,则使用该协议默认的端口。如http协议的默认端口为80.在浏览器中访问网页时,地址显示的地址就是URL。
在java.net包中提供了URL类,该类封装了大量复杂的涉及从远程站点获取信息的细节。
1.3.6Socket
应用层和传输层之间使用套接字Socket来进行分离。
套接字像传输层为应用层开的一个小口,应用程序通过这个小口向远程发送数据,或者接收远程发来的数据;在这个小口以内,数据进入这个口之后,或者数据从这个口出来之前,是属于网络其他层次的工作。
Socket 实际就是传输层供给应用层的编程接口。Socket就是应用层与传输层之间的桥梁。
使用Socket编程可以开发客户机和服务器应用程序,可以在本地网络上进行通信,也可以通过Internet在全球范围内通信。
1.3.7TCP(面向连接,可靠,传输效率低)
- TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟链接。然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。
- TCP在建立连接时分三步走(三次握手 Three-way-Handshake)
客户端:
package JavaWeb.TCP;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class TCP_Client {
public static void main(String[] args) throws IOException {
InetAddress localHost = InetAddress.getLocalHost();
int port=8091;
//01创建Socket对象
Socket socket = new Socket(localHost,port);
//02获取输出流对象
OutputStream outputStream=socket.getOutputStream();
//03发送数据
outputStream.write("hello TCP".getBytes());
System.out.println("成功发送");
//04释放资源
outputStream.close();
socket.close();
}
}
服务器端:
package JavaWeb.TCP;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 1.创建接收端Socket对象;
* 2.监听(阻塞:如果建立连接失败,程序会一直阻塞,不往下执行;
* 3.获取输入流对象;
* 4.获取数据;
* 5.输出数据;
* 6.释放资源;
*/
public class TCP_Service {
public static void main(String[] args) throws IOException {
//创建接收端Socket对象;
ServerSocket serverSocket = new ServerSocket(8091);
// 监听客户端发送数据给服务器 如果客户端一直没有发送数据给服务器 导致服务器端 会在该方法 一直阻塞
System.out.println("服务器正在等待客户端数据...");
Socket socket = serverSocket.accept();
// 获取输入流对象;
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
System.out.println("服务器端接受客户端发送的数据:" + new String(bytes, 0, len));
inputStream.close();
socket.close();
serverSocket.close();
}
}
问题1:使用8090端口报错
查找谁占用了此端口
netstat -ano | findstr 8090
解决:换一个端口
//创建接收端Socket对象;
ServerSocket serverSocket = new ServerSocket(8091);
问题2:
案例:
package JavaWeb.TCP案例;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.sql.SQLOutput;
import java.util.Scanner;
public class TCP_Client {
public static void main(String[] args) throws IOException {
InetAddress localHost = InetAddress.getLocalHost();
int port=8091;
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("请输入发送的数据:");
String s = scanner.nextLine();
//01创建Socket对象
Socket socket = new Socket(localHost,port);
//02获取getOutputStream对象
OutputStream outputStream = socket.getOutputStream();
//03发送数据
outputStream.write(s.getBytes());
/*接收服务器算的回应信息*/
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
System.out.println("接收的回应:"+new String(bytes,0,len));
//04关闭资源
outputStream.close();
socket.close();
}
}
}
package JavaWeb.TCP案例;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCP_Service {
public static void main(String[] args) throws IOException {
//01创建Socket对象
ServerSocket serverSocket = new ServerSocket(8091);
System.out.println("服务器端启动成功");
//实现服务器端可以一直接收数据
while (true){
//02监听客户端,如果一直没有发送数据会一直阻塞
Socket socket = serverSocket.accept();
//03接收客户端数据
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
System.out.println("接收到的数据"+new String(bytes,0,len));
/*服务端接收到数据后回应客户端代码*/
OutputStream outputStream = socket.getOutputStream();
outputStream.write("已收到信息".getBytes());
socket.close();
inputStream.close();
}
}
}
客户端:
服务端:
1.3.8UDP(面向无连接,不可靠,传输效率高,但可能丢包)
- 发送数据
(1)创建Socket对象:DatagramSocket()
(2)封装数据:DatagramPacket(),参数如下: - byte[] buf:保存传入数据报的缓冲区。
- int length:表示要接收或发送的数据长度,必须小于或等于buf.length。
- 对于发送数据的情况,还需要知道发送目标的InetAddress addr(目标IP地址)
- int port(目标端口号)。
public static void main(String[] args) throws IOException {
//01创建Socket对象
DatagramSocket datagramSocket = new DatagramSocket();
//02封装发送的数据到数据包中,设置成byte数组
byte[] msg="Hello UDP".getBytes();
/*
- byte[] buf:保存传入数据报的缓冲区。
- int length:表示要接收或发送的数据长度,必须小于或等于buf.length。
- 对于发送数据的情况,还需要知道发送目标的InetAddress addr(目标IP地址)
- int port(目标端口号)。*/
InetAddress localHost = InetAddress.getLocalHost();
int port=8080;//服务器端需要监测此端口
DatagramPacket datagramPacket = new DatagramPacket(msg, msg.length,localHost,port);
//03发送数据
datagramSocket.send(datagramPacket);
System.out.println("发送成功");
//04释放资源
datagramSocket.close();
}
public static void main(String[] args) throws IOException {
//01创建Socket对象
int port=8080;//监听端口
DatagramSocket datagramSocket = new DatagramSocket(port);
//02接收数据
byte[] reserve=new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(reserve, reserve.length);
//以数据包的形式进行接收,开始监听客户端发送的数据,如果没有监听到会一直阻塞
System.out.println("开始监测释放客户端发送数据数据");
datagramSocket.receive(datagramPacket);
System.out.println("接收数据完成");
//03解析数据
byte[]data=datagramPacket.getData();
String s = new String(data);
//04输出数据
System.out.println(s);
//05释放资源
datagramSocket.close();
}
客户端
package JavaWeb.UDP案例;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
//实现客户端与服务器可以一直发送与接收信息,当客户端发送666结束会话
public class UDP_Client {
public static void main(String[] args) throws IOException {
//01创建Socket对象
DatagramSocket datagramSocket = new DatagramSocket();//不用每次循环都创建,浪费资源
while (true){
//02打包数据
int port=8080;//定义端口
InetAddress localHost = InetAddress.getLocalHost();//获取IP
Scanner scanner = new Scanner(System.in);
System.out.println("请输入发送信息的");
String msg=scanner.nextLine();
if ("666".equals(msg)){
System.out.println("退出客户端!");
break;//退出循环
}
byte[] bytes=msg.getBytes();
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length,localHost,port);
//03发送数据
datagramSocket.send(datagramPacket);
System.out.println("发送完成");
}
//04退出之后才关闭
datagramSocket.close();
}
}
服务端
package JavaWeb.UDP案例;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDP_Server {
public static void main(String[] args) throws IOException {
//01创建Socket对象
int port=8080;//监听的端口对象
DatagramSocket datagramSocket = new DatagramSocket(port);
int count=0;
while (true){
if (count>1000){
break;
}
//02接收数据
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
System.out.println("开始监测释放客户端发送数据数据");
datagramSocket.receive(datagramPacket);
System.out.println("接收数据完成");
//03解析
byte[] data = datagramPacket.getData();
String s = new String(data);//将byte数组转换未string
System.out.println("接收到的数据:"+s);
count++;
}
//04释放资源,由于前面的While是死循环导致后面的代码无法执行会报错
/*优化上面代码,执行1000此自动退出*/
datagramSocket.close();
}
}
1.3.9Java中InetAddress获取本机信息(无法new对象)
这个类没有构造方法,如果要得到对象,只能通过静态方法:getLocalHost()、getByName()、getAllByName()、getAddress()、getHostName()
//使用Java程序获取电脑IP
public static void main(String[] args) throws UnknownHostException {
//实例化InetAddress对象
InetAddress localHost = InetAddress.getLocalHost();//需要抛出异常
//返回当前计算机的IP地址
String hostName = localHost.getHostName();
//返回当前计算机名
String hostAddress = localHost.getHostAddress();
System.out.println("本机名:"+hostName);
System.out.println("本机IP:"+hostAddress);
}
2.通过域名获取IP
public static void main(String[] args) throws UnknownHostException {
/*
* InetAddress根据域名获取计算机的信息,需要使用getByName(“域名”)方法创建InetAddress对象*/
InetAddress byName = InetAddress.getByName("www.baidu.com");
System.out.println(byName.getHostAddress());
System.out.println(byName.getHostName());
}
1.3.10优化TCP服务器端,实现多线程
- 分析当前问题:服务器同时只能接收一个客户端发送的数据
- 将服务器端通过多线程实现可以同时接收多个客户端请求
服务器端
package JavaWeb.TCP案例优化;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCP_Service {
public static void main(String[] args) throws IOException {
//01创建Socket对象
ServerSocket serverSocket = new ServerSocket(8091);
System.out.println("服务器端启动成功");
//实现服务器端可以一直接收数据
while (true){
//02监听客户端,如果一直没有发送数据会一直阻塞
Socket socket = serverSocket.accept();
//开启子线程,一般不允许单独创建子线程,都是通过线程池维护
new Thread(new Runnable() {
@Override
public void run() {
try{
//03接收客户端数据
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
System.out.println("接收到的数据"+new String(bytes,0,len));
/*服务端接收到数据后回应客户端代码*/
OutputStream outputStream = socket.getOutputStream();
outputStream.write("已收到信息".getBytes());
socket.close();
inputStream.close();
}catch (Exception e){
System.out.println(e);
}
}
}).start();
}
}
}
客户端
package JavaWeb.TCP案例优化;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class TCP_Client {
public static void main(String[] args) throws IOException {
InetAddress localHost = InetAddress.getLocalHost();
int port=8091;
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("请输入发送的数据:");
String s = scanner.nextLine();
//01创建Socket对象
Socket socket = new Socket(localHost,port);
//02获取getOutputStream对象
OutputStream outputStream = socket.getOutputStream();
//03发送数据
outputStream.write(s.getBytes());
/*接收服务器算的回应信息*/
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
System.out.println("接收的回应:"+new String(bytes,0,len));
//04关闭资源
outputStream.close();
socket.close();
}
}
}
1.3.11练习题:通过TCP实现登录验证
需求如下:
package JavaWeb.TCP登录验证案例;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCP_Service {
public static void main(String[] args) throws IOException {
//01创建Socket对象
ServerSocket serverSocket = new ServerSocket(8091);
System.out.println("服务器端启动成功");
//实现服务器端可以一直接收数据
while (true){
//02监听客户端,如果一直没有发送数据会一直阻塞
Socket socket = serverSocket.accept();
//开启子线程,一般不允许单独创建子线程,都是通过线程池维护
new Thread(new Runnable() {
@Override
public void run() {
try{
//03接收客户端数据
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
// 服务器端接受客户端数据包样式:userName=mayikt&userPwd=meite、
String UserPwd = new String(bytes, 0, len);
String User = UserPwd.split("&")[0].split("=")[1];
String Pwd = UserPwd.split("&")[1].split("=")[1];
// 回应数据给客户端 如果验证账户和密码正确 则响应 ok 否则 响应fail
OutputStream outputStream = socket.getOutputStream();
if ("mayikt".equals(User) && "123456".equals(Pwd)) {
outputStream.write("ok".getBytes());
} else {
outputStream.write("fail".getBytes());
}
//关闭资源
socket.close();
inputStream.close();
outputStream.close();
}catch (Exception e){
System.out.println(e);
}
}
}).start();
}
}
}
package JavaWeb.TCP登录验证案例;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class TCP_Client {
public static void main(String[] args) throws IOException {
InetAddress localHost = InetAddress.getLocalHost();
int port=8091;
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("请输入用户名:");
String User = scanner.nextLine();
System.out.println("请输入密码:");
String Pwd = scanner.nextLine();
//拼接内容样式:userName=mayikt&userPwd=meite
String reqText = "userName=" + User + "&userPwd=" + Pwd;
//01创建Socket对象
Socket socket = new Socket(localHost,port);
//02获取getOutputStream对象
OutputStream outputStream = socket.getOutputStream();
//03发送数据
outputStream.write(reqText.getBytes());
/*接收服务器算的回应信息*/
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
//判断服务器那边是否登录成功
String Loginresult= new String(bytes, 0, len);
if ("ok".equals(Loginresult)) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
//04关闭资源
outputStream.close();
socket.close();
}
}
}