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

举报
TiAmoZhang 发表于 2023/03/28 13:30:04 2023/03/28
【摘要】 手把手教会你|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

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。