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

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

要点:

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

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

首先来看我们系统的类图

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

HttpServer类代码

  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类非常简单,只是作为参数传递的对象来用

  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接口,统一监听接口,非常简单

  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打印输出,在我们作测试时非常有用

  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了

  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本身,从而达到动静分离的效果

  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我们的数据库建表脚本,只是为了演示,一个表就好

  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端口了!

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

一个基于Socket的http请求监听程序实现的更多相关文章

  1. Python:基于MD5的文件监听程序

    前述 写了一个基于MD5算法的文件监听程序,通过不同的文件能够生成不同的哈希函数,来实现实现判断文件夹中的文件的增加.修改.删除和过滤含有特定字符的文件名的文件. 需求说明 需要实现对一个文件夹下的文 ...

  2. 安装完oracle重新启动后报ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务(重启前正常)

    安装完oracle重新启动后报ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务(重启前正常) 刚安装完后用plSql登录正常. 在dos命令行下 输入  sqlplus 用户 ...

  3. Java:基于MD5的文件监听程序

    前述和需求说明 和之前写的 Python:基于MD5的文件监听程序 是同样的功能,就不啰嗦了,就是又写了一个java版本的,可以移步 python 版本去看一下,整个的核心思路是一样的.代码已上传Gi ...

  4. 基于socket实现http请求

    异步非阻塞模块原理 # 基于socket实现http请求 import socket # 多路IO复用模块 import select socket_list= [] url_list = [&quo ...

  5. 用nodejs搭建一个简单的服务监听程序

    作为一个从业三年左右的,并且从事过半年左右PHP开发工作的前端,对于后台,尤其是对以js语言进行开发的nodejs,那是比较有兴趣的,虽然本身并没有接触过相关的工作,只是自己私下做的一下小实验,但是还 ...

  6. ORACLE telnet 1521 不通及ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务的解决

    服务器上安装了oracle11g , 防火墙上已经增加1521 入站规则.但是内网客户端配置好了TNS无法连接.telnet 1521 不通. 需要在服务器上\product\10.2.0\db_1\ ...

  7. Oracle ORA12514 监听程序当前无法识别连接描述符中请求的服务

    在连接数据库的时候,有时会遇到一个“ORA12514:监听程序当前无法识别连接描述符中请求的服务”的错误,这个错误其实就是数据库动态注册(关于动态注册会在稍后讲解)不生效,导致监听器无法识别客户端连接 ...

  8. 简述Java中Http/Https请求监听方法

    一.工欲善其事必先利其器 做Web开发的人总免不了与Http/Https请求打交道,很多时候我们都希望能够直观的的看到我们发送的请求参数和服务器返回的响应信息,这个时候就需要借助于某些工具啦.本文将采 ...

  9. Oracle登录失败:监听程序当前无法识别连接描述符中请求的服务

    Oracle11g下载地址:https://pan.baidu.com/s/1p3RwLUTAl1Ys4yXmXJ3OVQ 安装步骤视频链接:https://pan.baidu.com/s/1c0FC ...

随机推荐

  1. C# 如何防止重放攻击(转载)

    转载地址:http://www.cnblogs.com/similar/p/6776921.html 重放攻击 重放攻击是指黑客通过抓包的方式,得到客户端的请求数据及请求连接,重复的向服务器发送请求的 ...

  2. NET 时间字符串转换

    // 把指定格式的日期字符串转换为时间:2018/11/1 0:00:00 DateTime.ParseExact("2018a11","yyyyaMM",Sy ...

  3. Linq to SQL 练习

    public class HomeController : Controller { // // GET: /Home/ empentity entity = new empentity(); pub ...

  4. 欢迎使用CSDN-markdown编辑器u

    这里写自定义目录标题 欢迎使用Markdown编辑器 新的改变 功能快捷键 合理的创建标题,有助于目录的生成 如何改变文本的样式 插入链接与图片 如何插入一段漂亮的代码片 生成一个适合你的列表 创建一 ...

  5. HDU 1024 最大M字段和

    一道关于求最大M字段和的问题,翻译完题之后感觉很简单但就是写不来,后来仿佛推到一个dp式子了,对,仿佛...然后抄袭了个式子,嘿,和我的式子大体相似,然后就是很玄学的优化了...不多瞎bb了 1.首先 ...

  6. Slope one—个性化推荐中最简洁的协同过滤算法

    Slope One 是一系列应用于 协同过滤的算法的统称.由 Daniel Lemire和Anna Maclachlan于2005年发表的论文中提出. [1]有争议的是,该算法堪称基于项目评价的non ...

  7. Python 去除列表中重复的元素

    Python 去除列表中重复的元素 来自比较容易记忆的是用内置的set l1 = ['b','c','d','b','c','a','a'] l2 = list(set(l1)) print l2 还 ...

  8. grub覆盖mbr引导系统

    grub覆盖mbr引导系统 0.个人PC,WIN 7 + Kali,easybcd 不起作用,需要制作 kali 安装盘 PS:推荐使用 universal usb installer 制作. 方案一 ...

  9. 《JAVA与模式》之不变模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述不变(Immutable)模式的: 一个对象的状态在对象被创建之后就不再变化,这就是所谓的不变模式. 不变模式的结构 不变模式可增强对象的 ...

  10. SVN图形客户端上传静态库.a文件失败

    1.原因客户端未添加静态库 2.解决办法 到项目静态库所在目录用命令行添加静态库文件 svn add ****.a 3.可能存在问题 Mac OS的自带SVN版本过低不能添加,报错如下: svn: E ...