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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: 手把手教会你|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


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
7天前
|
缓存 监控 Java
Java Socket编程最佳实践:优化客户端-服务器通信性能
【6月更文挑战第21天】Java Socket编程优化涉及识别性能瓶颈,如网络延迟和CPU计算。使用非阻塞I/O(NIO)和多路复用技术提升并发处理能力,减少线程上下文切换。缓存利用可减少I/O操作,异步I/O(AIO)进一步提高效率。持续监控系统性能是关键。通过实践这些策略,开发者能构建高效稳定的通信系统。
|
7天前
|
Java 应用服务中间件 开发者
【实战指南】Java Socket编程:构建高效的客户端-服务器通信
【6月更文挑战第21天】Java Socket编程用于构建客户端-服务器通信。`Socket`和`ServerSocket`类分别处理两端的连接。实战案例展示了一个简单的聊天应用,服务器监听端口,接收客户端连接,并使用多线程处理每个客户端消息。客户端连接服务器,发送并接收消息。了解这些基础,加上错误处理和优化,能帮你开始构建高效网络应用。
|
7天前
|
IDE Java 开发工具
从零开始学Java Socket编程:客户端与服务器通信实战
【6月更文挑战第21天】Java Socket编程教程带你从零开始构建简单的客户端-服务器通信。安装JDK后,在命令行分别运行`SimpleServer`和`SimpleClient`。服务器监听端口,接收并回显客户端消息;客户端连接服务器,发送“Hello, Server!”并显示服务器响应。这是网络通信基础,为更复杂的网络应用打下基础。开始你的Socket编程之旅吧!
|
16天前
|
监控 关系型数据库 MySQL
|
7天前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
|
7天前
|
安全 Java 网络安全
Java Socket编程教程:构建安全可靠的客户端-服务器通信
【6月更文挑战第21天】构建安全的Java Socket通信涉及SSL/TLS加密、异常处理和重连策略。示例中,`SecureServer`使用SSLServerSocketFactory创建加密连接,而`ReliableClient`展示异常捕获与自动重连。理解安全意识,如防数据截获和中间人攻击,是首要步骤。通过良好的编程实践,确保网络应用在复杂环境中稳定且安全。
|
7天前
|
Java 数据安全/隐私保护
深入剖析:Java Socket编程原理及客户端-服务器通信机制
【6月更文挑战第21天】Java Socket编程用于构建网络通信,如在线聊天室。服务器通过`ServerSocket`监听,接收客户端`Socket`连接请求。客户端使用`Socket`连接服务器,双方通过`PrintWriter`和`BufferedReader`交换数据。案例展示了服务器如何处理每个新连接并广播消息,以及客户端如何发送和接收消息。此基础为理解更复杂的网络应用奠定了基础。
|
4天前
|
SQL XML Java
后端数据库开发JDBC编程Mybatis之用基于XML文件的方式映射SQL语句实操
后端数据库开发JDBC编程Mybatis之用基于XML文件的方式映射SQL语句实操
23 3
|
9天前
|
SQL Java 关系型数据库
Java数据库编程的详细介绍
Java数据库编程的详细介绍
11 1
|
24天前
|
关系型数据库 应用服务中间件 数据库
编程入门(一)【Web服务器环境的部署】
编程入门(一)【Web服务器环境的部署】
42 1