手把手教会你|Sockets多用户-服务器数据库编程

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 手把手教会你|Sockets多用户-服务器数据库编程

网络编程经常涉及数据库访问,电子商务更离不开数据库。例如用户请求股票报价、产品价格查询、网上交易等请求,服务器则需要连接对应的数据库,发送查询指令,得到数据库记录,经过处理后,发送给提出这个请求的用户。

在实际应用中,数据库经常由专门管理数据库的服务器运行。由于用户端程序通过服务器端程序,而不直接访问数据库服务器,我们称这种服务器为后台服务器(Back-end server)。而运行服务器端程序的服务器常常需要与更多的后台服务器,如文件服务器、网页服务器等进行通信,构成多层次-多用户-服务器系统程序设计(Multi-tier client-server programming)。

下面利用一个实战项目一步步详细讨论多用户-服务器数据库编程以及模拟运行测试。


1、项目分析


这个实战项目是多层次-用户-服务器程序开发的实例。具体讲,是一个利用Socket技术实现多用户-服务器-数据库编程的典型例子。为了增加程序的可读性和实用性,在用户端代码中应用GUI组件,如窗口、选项框、单选按钮、文本框以及按钮来实现对服务器发出对数据库指定记录的提取和显示指令。用户可以对MySQL数据库ProductDB中的两个不同数据表Products以及Books的记录,按照单选按钮组中的不同价格选项,进行查询访问。如下是在Eclipse中运行服务器程序MultTierSocketServer并显示服务器正在与两个本机用户连接并运行的信息:

Welcome! The multiple-tier client-server is running....
Database connection is succeeded...
dbURL:jdbc:mysql://localhost:3306/ProductDB
Connection: com.mysql.cj.jdbc.ConnectionImpl@24313fcc
The client address: /127.0.0.1
Database connection is succeeded...
dbURL:jdbc:mysql://localhost:3306/ProductDB
Connection: com.mysql.cj.jdbc.ConnectionImpl@77f1baf5
The client address: /127.0.0.1

图1显示了这个例子通过本地计算机模拟两个用户的典型运行结果。

image.png

图1 多用户-服务器-数据库典型运行结果


类的设计和分析:


服务器端程序MultiTierSocketServer——主要应用Socket、ServerSocket、Thread以及来连接数据库的Connection等API类,实现对用户请求、数据库访问、送还回答结果等功能。具体代码如下:


自定义方法connectDatabase()用来进行对MySQL数据库的连接操作。具体代码如下:构建查询、执行查询、产生格式化查询结果,以及返回用户请求结果的自定义方法:

class MultiTierClientThread extends Thread {
  Socket client; //声明
  InputStream inData;
  OutputStream outData;
  PrintWriter toClient;
  Scanner data;
  Connection connection;
  ResultSet rs;
  public MultiTierClientThread(Socket fromClient) { //构造方法
    try {
      client = fromClient; //用户Socket
      inData = fromClient.getInputStream(); //用户输入数据流
      outData = fromClient.getOutputStream(); //至用户输出流
      toClient = new PrintWriter(outData, true); //写至用户
      connectDatabase(); //调用自定义方法连接数据库
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  public void run() { //覆盖run()
    String requestedDb = ""; //初始化
    String requestedPrice = "";
    String response = "";
    data = new Scanner(inData); //得到用户输入请求
    System.out.println("The client address: " + client.getInetAddress()); //显示用户地址
    if (data.hasNextLine()) {
    requestedDb = data.nextLine(); //得到数据表选择
    requestedPrice = data.nextLine(); //得到价格选择
    }
    if (!requestedDb.equals("") || !requestedPrice.equals("")) { //如果选择不是空
      String query = buildQuery(requestedDb); //调用自定义方法构造查询
      double price = buildPrice(requestedPrice); //调用自定义方法构造价格
      rs = getResult(query, price); //调用自定义方法得到查询结果
      if (requestedDb.equals("Products")) //如果是Products数据表
        response = buildProductsResponse();//调用自定义方法得到产品格式化记录
      else
        response = buildBooksResponse(); //否则调用自定义方法得到书籍格式化记录
      toClient.println(response); //向用户送还回答
      toClient.close(); //关闭
      }
     }
  ...
  }

自定义方法buildQuery()用来构建查询指令。具体代码如下:

private String buildQuery(String requestedDb) { //参数为用户数据表请求
  String query = ""; //初始化
  if (requestedDb.equals("Products")) //如果是Products数据表
    query = "SELECT * FROM Products WHERE price >= ?"; //形成Products预备指令
  else if (requestedDb.equals("Books")) //否则
    query = "SELECT * FROM Books WHERE price >= ?"; //形成Books预备指令
  return query; //返回查询指令
}

自定义方法buildPrice()用来构建查询指令的价格参数。具体代码如下:

private double buildPrice(String requestedPrice) { //参数为用户价格请求
  double price = 0.0; //初始化(预设为第一单选钮)
  if (requestedPrice.equals("1")) //如果选择第二个单选钮
    price = 100.00; //设置价格
  return price; //返回价格
}

自定义方法getResult()以预备查询指令和其价格值作为参数,用来执行查询指令,并返回查询对象:

private ResultSet getResult(String query, double price) {
  try {
    PreparedStatement ps = connection.prepareStatement(query);
                    //执行预备查询指令
    ps.setDouble(1, price); //指定其价格参数
    rs = ps.executeQuery(); //执行查询
  }
  catch (SQLException e) {e.printStackTrace();}
  return rs; //返回程序对象
}

自定义方法buildProductsResponse()构建格式化的产品数据表查询记录:

private String buildProductsDbResponse() {
  String result = ""; //初始化
  try {
    while (rs.next()) { //如果还有记录
      String code = rs.getString("Code"); //得到产品编号
      String title = rs.getString("Title"); //得到产品名称
      double price = rs.getDouble("Price"); //得到产品价格
      result += "Code: " + code + " Title: " + title + " Price: " + price + "\n"; //格式化结果
    }
  }
  catch (SQLException e) {e.printStackTrace();}
  return result; //返回结果
}

同样地,自定义方法buildBooksResponse()用来构建格式化的书籍表查询记录。


用户端程序MultiTierClientFrame­——利用JFrame、JPanel、JComboBox、JRadioButton、JTextArea、JButton以及布局管理形成用户GUI窗口;并且应用Socket技术,通过本地计算机模拟和指定端口与服务器端程序通信,发送请求和得到回答。代码中利用自定义方法connectToServer(),应用Socket进行与服务器的连接以及数据通信操作。具体代码如下:

...
private void connectToServer() {
try {
  clientSocket = new Socket("localhost", 1688); //用户Socket指定本地计算机和端口
  textArea.setText("Conected to the server and database...");
                  //将连接信息显示在文本窗口
  inData = clientSocket.getInputStream(); //创建从服务器得到的输入数据流
  outData = clientSocket.getOutputStream(); //创建输出到服务器的数据流
  toServer = new PrintWriter(outData, true); //创建输出到服务器数据对象
  data = new Scanner(inData); //创建从扫描器中得到的输入数据
  }
catch (IOException e) {
e.printStackTrace();
System.out.println("Check your server before running client...");
System.exit(0);
  }
}
...

可以看到,代码中利用本地计算机进行多用户-服务器-数据库的模拟运行。感兴趣的朋友可以选择几台联网计算机,将localhost修改为作为服务器的IP地址,则可进行远程多用户-服务器-数据库模拟运行这个实例。也可利用安装有MySQL和ProductDB的计算机作为数据库服务器,将服务器端程序在另外一台计算机上运行,然后利用其他联网计算机作为用户,运行这个例子。这时还必须将connectToServer()方法中的localhost修改为作为数据库服务器的计算机的IP地址。


注意出于网络安全考虑,对数据库进行远程访问时,可能受到防火墙以及区域网安全限制,不允许远程连接。


如下是用户端程序中进行事件处理的代码部分:

public void actionPerformed(ActionEvent e) { //完善事件处理接口方法
  Object source = e.getSource(); //得到事件发生源
  if (source == okButton) { //如果用户按下提交按钮
    connectToServer(); //调用自定义方法连接到服务器并进行数据通信
    String requestedDb = productComboBox.getSelectedItem().toString(); //得到数据表选项
    int requestedPrice = 0; //初始化(预设价格为0,即全部记录)
  if (lessRadio.isSelected()) //如果是小于100元按钮
    requestedPrice = 1; //设置为1
    toServer.println(requestedDb); //发送数据表请求至服务器
    toServer.println(requestedPrice); //发送价格请求至服务器
  textArea.setText(""); //清除文本框
    while (data.hasNextLine()) { //循环得到所有服务器发来的信息
        String fromServer = data.nextLine();
      textArea.append(fromServer + "\n"); //将结果添加到文本框
      }
    }
  else if (source == exitButton) { //如果按下退出按钮
    try {
      toServer.close(); //关闭
    clientSocket.close();
    }
    catch (IOException ex) {
    ex.printStackTrace();
      }
  System.exit(0); //停止程序运行
  }
}
...

实例中利用JPanel创建、注册GUI组件,以及对组建进行布局管理。这个部分的代码如下:

class MultiTierClientPanel extends JPanel implements ActionListener{
   JComboBox productComboBox; //, sizeComboBox, colorComboBox;
   JRadioButton allRadio, lessRadio, moreRadio;
   JTextArea textArea;
   JButton okButton, exitButton;
   Socket clientSocket;
   InputStream inData;
   OutputStream outData;
   PrintWriter toServer;
   Scanner data;
public MultiTierClientPanel(){ //构造方法
String[] items = {"Products", "Books"}; //选项
productComboBox = new JComboBox(items); //创建具有这两个选项的下拉选项框
productComboBox.setSelectedItem("Products"); //预设为Products
  allRadio = new JRadioButton("所有记录", true);//创建单选按钮并设预选为所有记录
  lessRadio = new JRadioButton(">=50元"); //第二个单选按钮
    ButtonGroup priceGroup = new ButtonGroup(); //创建按钮组
  priceGroup.add(allRadio); //注册到按钮组
  priceGroup.add(lessRadio);
      JPanel northPanel = new JPanel(); //创建显示下拉选项框和单选按钮的控制板
northPanel.add(productComboBox); //将组建注册到控制板
northPanel.add(allRadio);
northPanel.add(lessRadio);
setLayout(new BorderLayout()); //创建围界布局管理
add(northPanel, BorderLayout.NORTH); //将这个控制板注册到窗口上部显示
textArea = new JTextArea(10, 30); //创建文本框
      JPanel centerPanel = new JPanel(); //创建显示文本框的控制板
  centerPanel.add(textArea); //注册到控制板
  add(centerPanel, BorderLayout.CENTER); //将这个控制板注册到窗口中部显示
okButton = new JButton("OK"); //创建按钮
exitButton = new JButton("Exit");
okButton.addActionListener(this); //注册到事件处理接口
  exitButton.addActionListener(this);
    JPanel southPanel = new JPanel(); //创建显示按钮的控制板
  southPanel.add(okButton); //注册到控制板
southPanel.add(exitButton);
add(southPanel, BorderLayout.SOUTH); //将这个控制板注册到窗口下部显示
  connectToServer(); //调用自定义方法连接服务器
  }

在MultiTierClientFrame中创建MultiTierClientPanel对象,提供窗口显示位置、大小代码、关闭窗口的事件处理,以及main()方法创建窗口对象,执行这个程序。具体代码如下:

...
public class MultiTierClientFrame extends JFrame{
public MultiTierClientFrame(){ //构造方法
setTitle("Request to Server and Database"); //在窗口中显示标题
      Toolkit tk = Toolkit.getDefaultToolkit(); //得到包含系统信息的对象
      Dimension d = tk.getScreenSize(); //得到屏幕信息
int width = 500; //设置窗口显示尺寸
int height = 245;
setBounds((int) (d.width-width)/2, //将窗口显示在屏幕中心位置
                (int) (d.height-height)/2, width, height);
addWindowListener(new WindowAdapter(){ //处理关闭窗口事件
public void windowClosing(WindowEvent e){
System.exit(0);
         }
      });
      JPanel panel = new MultiTierClientPanel(); //创建GUI组件控制板
add(panel); //注册显示
   }
public static void main(String[] args){
      JFrame frame = new MultiTierClientFrame(); //创建窗口
frame.setVisible(true); //显示
   }
}


实战项目运行步骤:建议你先在本机进行测试运行,其步骤如下:

1.检查数据库服务器是否运行正常,JDBC驱动软件是否安装和设置正确。

2.运行服务端程序MultiTierSocketServer,并连接数据库。可在Eclipse中直接运行。

3.运行用户端程序MultiTierClientFrame。首先将这个用户端程序拷贝到一个本机文件夹(注意不包括包名ch23),如C:\Temp,然后打入如下编译指令:

javac MultiTierClientFrame.java

4.分别打开2个本机操作系统窗口,进入C:\Temp目录,打入如下指令运行用户端程序:

java MultiTierClientFrame


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
46 2
|
21天前
|
PHP 数据库 数据安全/隐私保护
布谷直播源码部署服务器关于数据库配置的详细说明
布谷直播系统源码搭建部署时数据库配置明细!
|
23天前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
198 2
|
1月前
|
应用服务中间件 PHP Apache
PbootCMS提示错误信息“未检测到您服务器环境的sqlite3数据库扩展...”
PbootCMS提示错误信息“未检测到您服务器环境的sqlite3数据库扩展...”
|
2月前
|
存储 数据挖掘 数据库
服务器数据恢复—raid磁盘故障导致数据库数据损坏的数据恢复案例
存储中有一组由3块SAS硬盘组建的raid。上层win server操作系统层面划分了3个分区,数据库存放在D分区,备份存放在E分区。 RAID中一块硬盘的指示灯亮红色,D分区无法识别;E分区可识别,但是拷贝文件报错。管理员重启服务器,导致离线的硬盘上线开始同步数据,同步还没有完成就直接强制关机了,之后就没有动过服务器。
|
2月前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
149 5
|
1月前
|
SQL 数据库
SQL-serve数据库不能连接本地服务器的解决方案
SQL-serve数据库不能连接本地服务器的解决方案
185 0
|
2月前
|
存储 Oracle 关系型数据库
Oracle同一台服务器创建多个数据库
【8月更文挑战第30天】在 Oracle 中,可在同一服务器上创建多个数据库。首先确保已安装 Oracle 软件并具有足够资源,然后使用 DBCA 工具按步骤创建,包括选择模板、配置存储及字符集等。重复此过程可创建多个数据库,需确保名称、SID 和存储位置唯一。创建后,可通过 Oracle Enterprise Manager 进行管理,注意服务器资源分配与规划。
132 10
|
2月前
|
存储 数据库 Python
python的对象数据库ZODB的使用(python3经典编程案例)
该文章介绍了如何使用Python的对象数据库ZODB来进行数据存储,包括ZODB的基本操作如创建数据库、存储和检索对象等,并提供了示例代码。
45 0
|
2月前
|
JSON NoSQL 数据库
和SQLite数据库对应的NoSQL数据库:TinyDB的详细使用(python3经典编程案例)
该文章详细介绍了TinyDB这一轻量级NoSQL数据库的使用方法,包括如何在Python3环境中安装、创建数据库、插入数据、查询、更新以及删除记录等操作,并提供了多个编程案例。
119 0
下一篇
无影云桌面