进程已经可以和进程通信(OS已经帮我们做到了,传输层及传输层以下)
我们只需要考虑:
1.如何使用OS提供的机制 ——Socket编程API
2.使用这些机制能用来走什么——不同的业务场景不同
套接字,OS提供的一套标准接口(不是)java中的接口,可以让所有应用层的程序使用网络进行数据的交换
就好比你要去取钱,你会操作ATM达到目的,而不是直接操作银行内部的货币系统,这里的ATM所起的作用就是Soket在网络中所起的作用
实现a进程和b进程的通信,其中a、b进程可以在同一个结点上,也可以不在同一个结点上。
这里就会面临一个问题:如果是两个不同的结点(这里的结点可以理解为一台设备)通信,如何让唯一确定通信的两个进程?
答:IP地址可以在互联网上唯一确定一个结点,port端口可以确定唯一的进程。(所谓端口,就是一个在[0,65535]之间的数字)
所以我们如何在互联网中确定一个进程? IP+端口
本地ip+本地port+远端ip+远端port(就是确定了两个结点上各自的一个进程)
将上面这个一般称为四元组,四元组标识互联网唯一一个通信通道,五元组=四元组+协议信息
这里提到的ip地址,都是公网IP(不包括127.0.0.1)
为了让IP地址可以唯一标识一台设备:一个IP地址只属于一台设备,一台设备可以有多个IP地址
为了让port端口可以唯一标识一个进程:一个port只能属于一个进程,一个进程可以有多个端口,
(可以理解为银行卡和持卡人的关系,或者是手机号和一个人的关系)
Client:请求服务的角色(主动)——可以理解为吃饭的人
Server:提供服务的角色(被动)——那服务器可以理解为饭店
这是一个相对的概念,比如饭店和菜市场这个关系里面,饭店又可以看成是一个Client,菜市场可以看成是Server
传输层最常见的有两个协议:
Server.java
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* Created with IntelliJ IDEA.
* 实现一个最简单的打印服务
* @Created by A TOMATO
* @Description: UDP 打印服务 Server
* @Date 2020/4/6 18:33
*/
public class Server {
//Server要提供服务,必须把port公开,否则客户端找不到服务器
static final int PORT = 9527;
//规定客户端和服务器端必须遵循同一种编码
static final String CHARSET = "UTF-8";
public static void main(String[] args) throws IOException {
//1.要处理,必须要先创建套接字,DatagramSocket是UDP专用的套接字
try (DatagramSocket serverSocket = new DatagramSocket(PORT)){//指定PORT地址上开启了服务
byte[] receiveBuffer = new byte[8192];//用于存放接收到的数据(请求)
while (true){//一次循环是 请求-响应 的处理过程
//2.接收对方发来的数据(请求)
//2.1 必须先创建 DatagramPacket
DatagramPacket packetFromClient = new DatagramPacket(receiveBuffer,0,receiveBuffer.length);
//2.2 接收数据
serverSocket.receive(packetFromClient);//客户端不发数据的时候是绝对不会返回的
//2.3 收到的是字节格式的数据,使用指定字符集解码,这里使用String的一个构造方法
String request = new String(receiveBuffer,0,packetFromClient.getLength(),CHARSET);
System.out.println("收到的请求是:" + request);
}
}
}
}
Client.java
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* Created with IntelliJ IDEA.
* 实现一个最简单的打印服务
* @Created by A TOMATO
* @Description: UDP 打印服务 Client
* @Date 2020/4/6 18:57
*/
public class Client {
private static final String serverIP = "127.0.0.1";
public static void main(String[] args) throws IOException {
//1.创建UDP Socket, Client的Socket不需要传入端口,OS自动分配,Client地址不重要,Server地址很重要
try(DatagramSocket clientSocket = new DatagramSocket()) {
//2.准备好请求
String request = "你好,我是西红柿!";
byte[] requestBytes = request.getBytes(Server.CHARSET);//使用指定字符集编码
//3.发送请求
//3.1 先准备 DatagramPacket 需要提供Server的IP+PORT
DatagramPacket packetToServer = new DatagramPacket(
requestBytes,0,requestBytes.length,//要发送的数据
InetAddress.getByName(serverIP),Server.PORT);//要发送到哪个地址
//3.2 发送
clientSocket.send(packetToServer);
}
}
}
这里的回显服务需要重构的有:
第一步: Client部分:将固定的请求改为用户自己输入
第二步: Server部分:把收到的请求内容,作为响应直接发送回去
第三步: Client部分:读取第二步Server发回的响应并打印
Server.java
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Server {
//Server要提供服务,必须把port公开,否则客户端找不到服务器
static final int PORT = 9527;
//规定客户端和服务器端必须遵循同一种编码
static final String CHARSET = "UTF-8";
public static void main(String[] args) throws IOException {
//1.要处理,必须要先创建套接字,DatagramSocket是UDP专用的套接字
try (DatagramSocket serverSocket = new DatagramSocket(PORT)){//指定PORT地址上开启了服务
byte[] receiveBuffer = new byte[8192];//用于存放接收到的数据(请求)
while (true){//一次循环是 请求-响应 的处理过程
//2.接收对方发来的数据(请求)
//2.1 必须先创建 DatagramPacket
DatagramPacket packetFromClient = new DatagramPacket(receiveBuffer,0,receiveBuffer.length);
//2.2 接收数据
serverSocket.receive(packetFromClient);//客户端不发数据的时候是绝对不会返回的
//2.3 收到的是字节格式的数据,使用指定字符集解码,这里使用String的一个构造方法
String request = new String(receiveBuffer,0,packetFromClient.getLength(),CHARSET);
System.out.println("收到的请求是:" + request);
//3.业务处理
String response = request;//把请求作为响应
//4.发送响应,请求和响应只是一个概念上的区别,实际上处理逻是一样的
//4.1所以这里要获取Client的IP+PORT
InetAddress clientAddress = packetFromClient.getAddress();
int clientPort = packetFromClient.getPort();
byte[] responseBytes = response.getBytes(Server.CHARSET);
DatagramPacket packetToClient = new DatagramPacket(
responseBytes,0,responseBytes.length,//要发送的数据
clientAddress,clientPort);//Client的IP+PORT
serverSocket.send(packetToClient);
}
}
}
}
Client.java
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class Client {
private static final String serverIP = "127.0.0.1";
public static void main(String[] args) throws IOException {
//1.创建UDP Socket, Client的Socket不需要传入端口,OS自动分配,Client地址不重要,Server地址很重要
try(DatagramSocket clientSocket = new DatagramSocket()) {
Scanner scanner = new Scanner(System.in);
byte[] receiveBuffer = new byte[8192];//接收Server响应的buffer
System.out.print("请输入请求: ");
while (scanner.hasNextLine()){
//2.准备好请求
String request = scanner.nextLine();
byte[] requestBytes = request.getBytes(Server.CHARSET);//使用指定字符集编码
//3.发送请求
//3.1 先准备 DatagramPacket 需要提供Server的IP+PORT
DatagramPacket packetToServer = new DatagramPacket(
requestBytes,0,requestBytes.length,//要发送的数据
InetAddress.getByName(serverIP),Server.PORT);//要发送到哪个地址
//3.2 发送
clientSocket.send(packetToServer);
//4.接收响应
//4.1先准备Packet
DatagramPacket packetFromServer = new DatagramPacket(receiveBuffer,0,receiveBuffer.length);
//4.2接收
clientSocket.receive(packetFromServer);
//4.3 转换
String response = new String(receiveBuffer,0,packetFromServer.getLength(),Server.CHARSET);
System.out.println("服务器的应答: "+response);
System.out.print("请输入请求: ");
}
}
}
}
上面这两种服务都是运行在我PC机器上的(没有公网IP),所以其他人是无法通过互联网访问我的服务器的,这时就需要云服务器了,需要购买
这里是一个固定的,还没有应用数据库动态实现,先实现一个写死的字典翻译,自己用Map提前准备好一份字典,Map<英文,中文意思>,Map<英文,例句>,这里数据先放到内存中
Server:
就添加了一个Map和业务处理部分的代码
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Server {
//Server要提供服务,必须把port公开,否则客户端找不到服务器
static final int PORT = 9527;
//规定客户端和服务器端必须遵循同一种编码
static final String CHARSET = "UTF-8";
//Map<英文单词,中文含义>
private static final Map<String,String> meaningMap = new HashMap<>();
//Map<英文单词,例句>,例句不止一条
private static final Map<String, List<String>> exampleSentenceMap = new HashMap<>();
//初始化Map
static {
//dog
meaningMap.put("dog","n. 狗;卑鄙的人;(俚)朋友 vt. 跟踪;尾随");
exampleSentenceMap.put("dog",new ArrayList<>());
exampleSentenceMap.get("dog").add("The British are renowned as a nation of dog lovers.");
//cat
meaningMap.put("cat","n. 猫,猫科动物");
exampleSentenceMap.put("cat",new ArrayList<>());
exampleSentenceMap.get("cat").add("The lion is perhaps the most famous member of the cat family.");
//galaxy
meaningMap.put("galaxy","n. [天] 星系;银河系;一群显赫的人 n. 三星智能手机品牌 2015年三星中国公布Galaxy的中文名称为“盖乐世”");
exampleSentenceMap.put("galaxy",new ArrayList<>());
exampleSentenceMap.get("galaxy").add("The Galaxy consists of 100 billion stars.");
}
public static void main(String[] args) throws IOException {
//1.要处理,必须要先创建套接字,DatagramSocket是UDP专用的套接字
try (DatagramSocket serverSocket = new DatagramSocket(PORT)){//指定PORT地址上开启了服务
byte[] receiveBuffer = new byte[8192];//用于存放接收到的数据(请求)
while (true){//一次循环是 请求-响应 的处理过程
//2.接收对方发来的数据(请求)
//2.1 必须先创建 DatagramPacket
DatagramPacket packetFromClient = new DatagramPacket(receiveBuffer,0,receiveBuffer.length);
//2.2 接收数据
serverSocket.receive(packetFromClient);//客户端不发数据的时候是绝对不会返回的
//2.3 收到的是字节格式的数据,使用指定字符集解码,这里使用String的一个构造方法
String request = new String(receiveBuffer,0,packetFromClient.getLength(),CHARSET);
System.out.println("收到的请求是:" + request);
//3.业务处理
//3.1根据请求的英文单词,获取含义+例句,需要考虑用户输入的单词不支持
String response = "Sorry ! No such vocabulary";
String template = "含义:\r\n%s\r\n示例语句:%s\r\n";
String exampleTemplate = "%d. %s\r\n";
if (meaningMap.containsKey(request)){
String meaning = meaningMap.get(request);
List<String> sentenceList = exampleSentenceMap.get(request);
StringBuilder exampleSB = new StringBuilder();
for (int i = 0 ;i < sentenceList.size(); i++){
exampleSB.append(String.format(exampleTemplate,i+1,sentenceList.get(i)));
}
response = String.format(template,meaning,exampleSB.toString());
}
//4.发送响应,请求和响应只是一个概念上的区别,实际上处理逻是一样的
//4.1所以这里要获取Client的IP+PORT
InetAddress clientAddress = packetFromClient.getAddress();
int clientPort = packetFromClient.getPort();
byte[] responseBytes = response.getBytes(Server.CHARSET);
DatagramPacket packetToClient = new DatagramPacket(
responseBytes,0,responseBytes.length,//要发送的数据
clientAddress,clientPort);//Client的IP+PORT
serverSocket.send(packetToClient);
}
}
}
}
将数据全部保存到MySQL的表中,通过JDBC来获取数据(不一定要使用MySQL,只要是持久化存储就可以,我这里使用的MySQL)
除了使用持久化存储外,还可以把字典存成本地文件,读取文件也可以,由于只是简单的字典应用,在建表那块仅仅为了说明问题就插入了三行数据
首先需要导入jar包并加到依赖箱中去
建库和建表.sql
(这里可以直接在IDEA配置好数据库直接运行,不用到cmd去建数据库的,但是我配置的时候出了点问题,所以就拿到cmd建库建表了。配置就是直接点击右侧Database,设置下默认库什么的就ok了,具体过程可以百度)
CREATE DATABASE IF NOT EXISTS Trans0406 CHARSET utf8mb4;
USE Trans0406;
CREATE TABLE IF NOT EXISTS vocabulary (
en VARCHAR(20) NOT NULL UNIQUE,
cn VARCHAR(100) NOT NULL
);
INSERT INTO vocabulary (en, cn) VALUES
("dog", "狗"),
("pig","猪"),
("cat", "猫");
package UDP;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Server {
//Server要提供服务,必须把port公开,否则客户端找不到服务器
static final int PORT = 9527;
//规定客户端和服务器端必须遵循同一种编码
static final String CHARSET = "UTF-8";
//创建一个连接池并初始化
private static final DataSource dataSource;
static {
MysqlDataSource mysqlDataSource = new MysqlDataSource();
//通过IP+PORT确定网络上唯一的进程——MySQL
mysqlDataSource.setServerName("127.0.0.1");
mysqlDataSource.setPort(3306);
//MySQL登录需要用到的信息,是为了应用层使用的
mysqlDataSource.setUser("root");
mysqlDataSource.setPassword("");
mysqlDataSource.setDatabaseName("Trans0406");
mysqlDataSource.setCharacterEncoding("utf8");
//设置一些MySQL连接需要的属性——不用SSL连接
mysqlDataSource.setUseSSL(false);
dataSource = mysqlDataSource;
}
public static void main(String[] args) throws IOException {
//1.要处理,必须要先创建套接字,DatagramSocket是UDP专用的套接字
try (DatagramSocket serverSocket = new DatagramSocket(PORT)){//指定PORT地址上开启了服务
byte[] receiveBuffer = new byte[8192];//用于存放接收到的数据(请求)
while (true){//一次循环是 请求-响应 的处理过程
//2.接收对方发来的数据(请求)
//2.1 必须先创建 DatagramPacket
DatagramPacket packetFromClient = new DatagramPacket(receiveBuffer,0,receiveBuffer.length);
//2.2 接收数据
serverSocket.receive(packetFromClient);//客户端不发数据的时候是绝对不会返回的
//2.3 收到的是字节格式的数据,使用指定字符集解码,这里使用String的一个构造方法
String request = new String(receiveBuffer,0,packetFromClient.getLength(),CHARSET);
System.out.println("收到的请求是:" + request);
//3.业务处理
String response = "No Such Vocabulary,Please try Again ^ - ^";
try (Connection con = dataSource.getConnection()){
String sql = "SELECT cn FROM vocabulary WHERE en = ?";
try (PreparedStatement statement = con.prepareStatement(sql)) {
statement.setString(1,request);//尤其要注意SQL注入的问题,不要完全信任用户
try (ResultSet rs = statement.executeQuery()){
if (rs.next()){
response = rs.getString("cn");
}
}
}
} catch (SQLException e) {
e.printStackTrace();//打印在Server的
response = e.getMessage();//准备发送给客户端的响应
}
//4.发送响应,请求和响应只是一个概念上的区别,实际上处理逻是一样的
//4.1所以这里要获取Client的IP+PORT
InetAddress clientAddress = packetFromClient.getAddress();
int clientPort = packetFromClient.getPort();
byte[] responseBytes = response.getBytes(Server.CHARSET);
DatagramPacket packetToClient = new DatagramPacket(
responseBytes,0,responseBytes.length,//要发送的数据
clientAddress,clientPort);//Client的IP+PORT
serverSocket.send(packetToClient);
}
}
}
}
运行结果:
至此就告一段落,在这里只要数据库有新插入的数据,我们的客户端和服务器端都不需要改动,就可以查询新插入的数据,这就是把逻辑和数据进行了一个分离,是一个好处
后面是利用JavaFX加上简单的客户端界面:
package sample;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Controller {
@FXML TextField en;
@FXML Label cn;
private DatagramSocket clientSocket;
{
try {
clientSocket = new DatagramSocket();
} catch (IOException e) {
e.printStackTrace();
}
}
@FXML public void handleSubmit(ActionEvent event) {
String request = en.getText();
byte[] receiveBuffer = new byte[8192];
try {
byte[] requestBytes = request.getBytes("UTF-8");
// 2. 发送请求
// 2.1 先准备 DatagramPacket
// 需要指定服务器的 ip + port
// 创建 发送用的 Packet 的时候,需要提供两类信息
// 1) 需要发送的数据信息 requestBytes + 0 + requestBytes.length
// 2) 接收信息的唯一标识(ip + port)
// InetAddress.getByName("127.0.0.1") 会把 ip 地址转成 InetAddress 对象
DatagramPacket packetToServer = new DatagramPacket(
requestBytes, 0, requestBytes.length, // 要发送的数据
InetAddress.getByName("127.0.0.1"), 9527 // 要发送到互联网的哪个进程上
);
clientSocket.send(packetToServer);
// 接收响应
DatagramPacket packetFromServer = new DatagramPacket(
receiveBuffer, 0, receiveBuffer.length // 提供的是用来装数据的容器信息
);
clientSocket.receive(packetFromServer);
String response = new String(
receiveBuffer, 0, packetFromServer.getLength(), // 已经取到的数据
"UTF-8"
);
cn.setText(response);
} catch (IOException e) {
e.printStackTrace();;
}
}
}
Main.java
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
将Server和Client跑起来之后运行客户端
运行结果:
未完结