zoukankan      html  css  js  c++  java
  • 一个基于Socket的http请求监听程序实现

    首先来看以下我们的需求:

    用java编写一个监听程序,监听指定的端口,通过浏览器如http://localhost:7777来访问时,可以把请求到的内容记录下来,记录可以存文件,sqlit,mysql数据库,然后把接受到的信息在浏览器中显示出来

    要点:

    Socket,线程,数据库,IO操作,观察者模式

    来看下我们如何来设计这个小系统,这个系统包含三部分的内容,一个是监听端口,二是记录日志,三是数据回显,端口监听第一想到的就是Socket编程了,数据回显也是一样的,无非是把当前请求客户端的socket获取到,然后把消息通过流输出出去,日志的记录因为是要多种实现策略,这里我们使用了一个观察者模式来实现,服务器可以添加任意多个观察着,因此有着很灵活的扩展性,在实例程序中我们分别提供了ConsoleRecordHandler--直接把获取到的信息打印到控制台,和存放数据库的方式-MysqlRecordHandler,当然你也可以分别提供基于文件的实现。

    首先来看我们系统的类图

    HttpServer系统类图

    HttpServer类是我们的核心类,他实现了Runnable接口,因此有着更高的性能,在循环中不断的去轮询指定端口,构造方法比较简单,只需要一个要监听的端口号即可,还有两个用于触发监听和停止程序运行的方法stop()&start(),这两个方法也比较简单,只是简单的给标志位赋值即可,我们这个程序是基于Oserver模式的简化版本,HttpServer本身是一个被观察的对象(Subject),当这个Subject有变化时(获取到客户端请求时)要通知监听器(我们的RecordHandler)去作操作(写数据库还是写文件或是直接控制台输出),极大的增加了系统的灵活性和易测试性

    HttpServer类代码

    [java] view plain copy
     
     print?
    1. package com.crazycoder2010.socket;  
    2. import java.io.BufferedReader;  
    3. import java.io.IOException;  
    4. import java.io.InputStreamReader;  
    5. import java.io.PrintWriter;  
    6. import java.net.ServerSocket;  
    7. import java.net.Socket;  
    8. import java.sql.Date;  
    9. import java.util.ArrayList;  
    10. import java.util.List;  
    11. /** 
    12.  * 服务器监听对象,对某个端口进行监听,基于线程的实现 
    13.  *  
    14.  * @author Kevin 
    15.  *  
    16.  */  
    17. public class HttpServer implements Runnable {  
    18.     /** 
    19.      * 服务器监听 
    20.      */  
    21.     private ServerSocket serverSocket;  
    22.     /** 
    23.      * 标志位,表示当前服务器是否正在运行 
    24.      */  
    25.     private boolean isRunning;  
    26.     /** 
    27.      * 观察者 
    28.      */  
    29.     private List<RecordHandler> recordHandlers = new ArrayList<RecordHandler>();  
    30.     public HttpServer(int port) {  
    31.         try {  
    32.             serverSocket = new ServerSocket(port);  
    33.         } catch (IOException e) {  
    34.             e.printStackTrace();  
    35.         }  
    36.     }  
    37.     public void stop() {  
    38.         this.isRunning = false;  
    39.     }  
    40.     public void start() {  
    41.         this.isRunning = true;  
    42.         new Thread(this).start();  
    43.     }  
    44.     @Override  
    45.     public void run() {  
    46.         while (isRunning) {//一直监听,直到受到停止的命令  
    47.             Socket socket = null;  
    48.             try {  
    49.                 socket = serverSocket.accept();//如果没有请求,会一直hold在这里等待,有客户端请求的时候才会继续往下执行  
    50.                 // log  
    51.                 BufferedReader bufferedReader = new BufferedReader(  
    52.                         new InputStreamReader(socket.getInputStream()));//获取输入流(请求)  
    53.                 StringBuilder stringBuilder = new StringBuilder();  
    54.                 String line = null;  
    55.                 while ((line = bufferedReader.readLine()) != null  
    56.                         && !line.equals("")) {//得到请求的内容,注意这里作两个判断非空和""都要,只判断null会有问题  
    57.                     stringBuilder.append(line).append("/n");  
    58.                 }  
    59.                 Record record = new Record();  
    60.                 record.setRecord(stringBuilder.toString());  
    61.                 record.setVisitDate(new Date(System.currentTimeMillis()));  
    62.                 notifyRecordHandlers(record);//通知日志记录者对日志作操作  
    63.                 // echo  
    64.                 PrintWriter printWriter = new PrintWriter(  
    65.                         socket.getOutputStream(), true);//这里第二个参数表示自动刷新缓存  
    66.                 doEcho(printWriter, record);//将日志输出到浏览器  
    67.                 // release  
    68.                 printWriter.close();  
    69.                 bufferedReader.close();  
    70.                 socket.close();  
    71.             } catch (IOException e) {  
    72.                 e.printStackTrace();  
    73.             }  
    74.         }  
    75.     }  
    76.     /** 
    77.      * 将得到的信写回客户端 
    78.      *  
    79.      * @param printWriter 
    80.      * @param record 
    81.      */  
    82.     private void doEcho(PrintWriter printWriter, Record record) {  
    83.         printWriter.write(record.getRecord());  
    84.     }  
    85.     /** 
    86.      * 通知已经注册的监听者做处理 
    87.      *  
    88.      * @param record 
    89.      */  
    90.     private void notifyRecordHandlers(Record record) {  
    91.         for (RecordHandler recordHandler : this.recordHandlers) {  
    92.             recordHandler.handleRecord(record);  
    93.         }  
    94.     }  
    95.     /** 
    96.      * 添加一个监听器 
    97.      *  
    98.      * @param recordHandler 
    99.      */  
    100.     public void addRecordHandler(RecordHandler recordHandler) {  
    101.         this.recordHandlers.add(recordHandler);  
    102.     }  
    103. }  

    Record类非常简单,只是作为参数传递的对象来用

    [java] view plain copy
     
     print?
    1. package com.crazycoder2010.socket;  
    2. import java.sql.Date;  
    3. public class Record {  
    4.     private int id;  
    5.     private String record;  
    6.     private Date visitDate;  
    7.     public int getId() {  
    8.         return id;  
    9.     }  
    10.     public void setId(int id) {  
    11.         this.id = id;  
    12.     }  
    13.     public String getRecord() {  
    14.         return record;  
    15.     }  
    16.     public void setRecord(String record) {  
    17.         this.record = record;  
    18.     }  
    19.     public Date getVisitDate() {  
    20.         return visitDate;  
    21.     }  
    22.     public void setVisitDate(Date visitDate) {  
    23.         this.visitDate = visitDate;  
    24.     }  
    25. }  

    RecordHandler接口,统一监听接口,非常简单

    [java] view plain copy
     
     print?
    1. package com.crazycoder2010.socket;  
    2. /** 
    3.  * 获取到访问信息后的处理接口 
    4.  * @author Kevin 
    5.  * 
    6.  */  
    7. public interface RecordHandler {  
    8.     public void handleRecord(Record record);  
    9. }  

    ConsoleRecordHandler实现,直接System打印输出,在我们作测试时非常有用

    [java] view plain copy
     
     print?
    1. package com.crazycoder2010.socket;  
    2. public class ConsoleRecordHandler implements RecordHandler {  
    3.     @Override  
    4.     public void handleRecord(Record record) {  
    5.         System.out.println("@@@@@@@");  
    6.         System.out.println(record.getRecord());  
    7.     }  
    8. }  

    MysqlRecordHandler,数据库实现,定义了要对数据库操作所需要的几个基本属性url,username,password,加载驱动的程序我们放在了静态代码短中,这个东东嘛,只要加载一次就ok了

    [java] view plain copy
     
     print?
    1. package com.crazycoder2010.socket;  
    2. import java.sql.Connection;  
    3. import java.sql.PreparedStatement;  
    4. import java.sql.SQLException;  
    5. public class MysqlRecordHandler implements RecordHandler {  
    6.     static {  
    7.         try {  
    8.             Class.forName("com.mysql.jdbc.Driver");  
    9.         } catch (ClassNotFoundException e) {  
    10.             e.printStackTrace();  
    11.         }  
    12.     }  
    13.     private static final String NEW_RECORD = "insert into log(record,visit_date) values (?,?)";  
    14.     /** 
    15.      * 数据库访问url 
    16.      */  
    17.     private String url;  
    18.     /** 
    19.      * 数据库用户名 
    20.      */  
    21.     private String username;  
    22.     /** 
    23.      * 数据库密码 
    24.      */  
    25.     private String password;  
    26.     public void setUrl(String url) {  
    27.         this.url = url;  
    28.     }  
    29.     public void setUsername(String username) {  
    30.         this.username = username;  
    31.     }  
    32.     public void setPassword(String password) {  
    33.         this.password = password;  
    34.     }  
    35.     @Override  
    36.     public void handleRecord(Record record) {  
    37.         Connection connection = ConnectionFactory.getConnection(url, username,  
    38.                 password);  
    39.         PreparedStatement preparedStatement = null;  
    40.         try {  
    41.             preparedStatement = connection.prepareStatement(NEW_RECORD);  
    42.             preparedStatement.setString(1, record.getRecord());  
    43.             preparedStatement.setDate(2, record.getVisitDate());  
    44.             preparedStatement.executeUpdate();  
    45.         } catch (SQLException e) {  
    46.             e.printStackTrace();  
    47.         } finally {  
    48.             ConnectionFactory.release(preparedStatement);  
    49.             ConnectionFactory.release(connection);  
    50.         }  
    51.     }  
    52. }  

    ConnectionFactory类,我们的数据库连接工厂类,定义了几个常用的方法,把数据库连接独立到外部单独类的好处在于,我们可以很灵活的替换连接的生成方式--如我们可以从连接池中获取一个,而我们的数据库操作却只关注Connection本身,从而达到动静分离的效果

    [java] view plain copy
     
     print?
    1. package com.crazycoder2010.socket;  
    2. import java.sql.Connection;  
    3. import java.sql.DriverManager;  
    4. import java.sql.PreparedStatement;  
    5. import java.sql.SQLException;  
    6. /** 
    7.  * 创建数据库连接的工厂类 
    8.  *  
    9.  * @author Kevin 
    10.  *  
    11.  */  
    12. public class ConnectionFactory {  
    13.     public static Connection getConnection(String url, String username,  
    14.             String password) {  
    15.         try {  
    16.             return DriverManager.getConnection(url, username, password);  
    17.         } catch (SQLException e) {  
    18.             e.printStackTrace();  
    19.         }  
    20.         return null;  
    21.     }  
    22.     /** 
    23.      * 释放连接 
    24.      *  
    25.      * @param connection 
    26.      */  
    27.     public static void release(Connection connection) {  
    28.         if (connection != null) {  
    29.             try {  
    30.                 connection.close();  
    31.                 connection = null;  
    32.             } catch (SQLException e) {  
    33.                 e.printStackTrace();  
    34.             }  
    35.         }  
    36.     }  
    37.     /** 
    38.      * 关闭查询语句 
    39.      *  
    40.      * @param preparedStatement 
    41.      */  
    42.     public static void release(PreparedStatement preparedStatement) {  
    43.         if (preparedStatement != null) {  
    44.             try {  
    45.                 preparedStatement.close();  
    46.                 preparedStatement = null;  
    47.             } catch (SQLException e) {  
    48.                 e.printStackTrace();  
    49.             }  
    50.         }  
    51.     }  
    52. }  

    init.sql我们的数据库建表脚本,只是为了演示,一个表就好

    [java] view plain copy
     
     print?
    1. CREATE TABLE `logs`.`log` (  
    2.   `id` INT(10)  NOT NULL AUTO_INCREMENT,  
    3.   `record` VARCHAR(1024)  NOT NULL,  
    4.   `visit_date` DATETIME  NOT NULL,  
    5.   PRIMARY KEY (`id`)  
    6. )  
    7. ENGINE = MyISAM;  

    AppLuancher类,是时候把这几个模块高到一起跑起来的时候了,我们首先创建了一个服务器端,然后给服务器创建了两个监听器,然后启动服务器,这个时候我们的HttpServer已经开始监听7777端口了!

    [java] view plain copy
     
     print?
    1. package com.crazycoder2010.socket;  
    2. public class AppLauncher {  
    3.     /** 
    4.      * @param args 
    5.      */  
    6.     public static void main(String[] args) {  
    7.         HttpServer httpServer = new HttpServer(7777);  
    8.         httpServer.addRecordHandler(new ConsoleRecordHandler());  
    9.         httpServer.addRecordHandler(createMysqlHandler());  
    10.         httpServer.start();  
    11.     }  
    12.     private static RecordHandler createMysqlHandler(){  
    13.         MysqlRecordHandler handler = new MysqlRecordHandler();  
    14.         handler.setUrl("jdbc:mysql://localhost:3306/logs");  
    15.         handler.setUsername("root");  
    16.         handler.setPassword("");  
    17.         return handler;  
    18.     }  
    19. }  

    打开浏览器,输入http://localhost:7777我们看到控制台输出了一堆的文字

    GET / HTTP/1.1

    Host: localhost:7777

    Connection: keep-alive

    Cache-Control: max-age=0

    User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16

    Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5

    Accept-Encoding: gzip,deflate,sdch

    Accept-Language: zh-CN,zh;q=0.8

    Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3

    再去查以下我们的数据库,呵呵,也有了,再看看我们的浏览器上是否也把这些信息同样显示出来了~~

    总结一下

    麻雀虽小,五脏俱全,可能有人说这个小程序高这么多类干吗,我在main函数里一下子不久写完了吗?的确很多人这么搞,但是我不赞同,一个小东西,如果你是报者学习的姿态,一种不把他当玩具的心态来设计它时,你就会比别人多想一步,设计模式,封装变化,单一职责,这些东东不能让他们一直留在大学的课本里,而是有意识的去在实践中运用--实践是检验真理的唯一标准,经验来源于积累

    转:http://blog.csdn.net/crazycoder2010/article/details/6241552

  • 相关阅读:
    学习进度四
    每日进度二
    每日进度一
    学习进度三
    学习进度二
    安全性战术
    Linux 常用命令
    python测试框架之pytest
    Python 列表生成式和字典生成式
    使用Python在Windows 10上显示通知信息
  • 原文地址:https://www.cnblogs.com/Eilen/p/7661952.html
Copyright © 2011-2022 走看看