分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 前端开发

一个基于Socket的http请求监听程序实现

发布时间:2023-09-06 01:12责任编辑:郭大石关键词:http

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

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

要点:

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

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

首先来看我们系统的类图

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

HttpServer类代码

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

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

[java]view plaincopyprint?
  1. packagecom.crazycoder2010.socket;
  2. importjava.sql.Date;
  3. publicclassRecord{
  4. privateintid;
  5. privateStringrecord;
  6. privateDatevisitDate;
  7. publicintgetId(){
  8. returnid;
  9. }
  10. publicvoidsetId(intid){
  11. this.id=id;
  12. }
  13. publicStringgetRecord(){
  14. returnrecord;
  15. }
  16. publicvoidsetRecord(Stringrecord){
  17. this.record=record;
  18. }
  19. publicDategetVisitDate(){
  20. returnvisitDate;
  21. }
  22. publicvoidsetVisitDate(DatevisitDate){
  23. this.visitDate=visitDate;
  24. }
  25. }

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

[java]view plaincopyprint?
  1. packagecom.crazycoder2010.socket;
  2. /**
  3. *获取到访问信息后的处理接口
  4. *@authorKevin
  5. *
  6. */
  7. publicinterfaceRecordHandler{
  8. publicvoidhandleRecord(Recordrecord);
  9. }

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

[java]view plaincopyprint?
  1. packagecom.crazycoder2010.socket;
  2. publicclassConsoleRecordHandlerimplementsRecordHandler{
  3. @Override
  4. publicvoidhandleRecord(Recordrecord){
  5. System.out.println("@@@@@@@");
  6. System.out.println(record.getRecord());
  7. }
  8. }

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

[java]view plaincopyprint?
  1. packagecom.crazycoder2010.socket;
  2. importjava.sql.Connection;
  3. importjava.sql.PreparedStatement;
  4. importjava.sql.SQLException;
  5. publicclassMysqlRecordHandlerimplementsRecordHandler{
  6. static{
  7. try{
  8. Class.forName("com.mysql.jdbc.Driver");
  9. }catch(ClassNotFoundExceptione){
  10. e.printStackTrace();
  11. }
  12. }
  13. privatestaticfinalStringNEW_RECORD="insertintolog(record,visit_date)values(?,?)";
  14. /**
  15. *数据库访问url
  16. */
  17. privateStringurl;
  18. /**
  19. *数据库用户名
  20. */
  21. privateStringusername;
  22. /**
  23. *数据库密码
  24. */
  25. privateStringpassword;
  26. publicvoidsetUrl(Stringurl){
  27. this.url=url;
  28. }
  29. publicvoidsetUsername(Stringusername){
  30. this.username=username;
  31. }
  32. publicvoidsetPassword(Stringpassword){
  33. this.password=password;
  34. }
  35. @Override
  36. publicvoidhandleRecord(Recordrecord){
  37. Connectionconnection=ConnectionFactory.getConnection(url,username,
  38. password);
  39. PreparedStatementpreparedStatement=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(SQLExceptione){
  46. e.printStackTrace();
  47. }finally{
  48. ConnectionFactory.release(preparedStatement);
  49. ConnectionFactory.release(connection);
  50. }
  51. }
  52. }

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

[java]view plaincopyprint?
  1. packagecom.crazycoder2010.socket;
  2. importjava.sql.Connection;
  3. importjava.sql.DriverManager;
  4. importjava.sql.PreparedStatement;
  5. importjava.sql.SQLException;
  6. /**
  7. *创建数据库连接的工厂类
  8. *
  9. *@authorKevin
  10. *
  11. */
  12. publicclassConnectionFactory{
  13. publicstaticConnectiongetConnection(Stringurl,Stringusername,
  14. Stringpassword){
  15. try{
  16. returnDriverManager.getConnection(url,username,password);
  17. }catch(SQLExceptione){
  18. e.printStackTrace();
  19. }
  20. returnnull;
  21. }
  22. /**
  23. *释放连接
  24. *
  25. *@paramconnection
  26. */
  27. publicstaticvoidrelease(Connectionconnection){
  28. if(connection!=null){
  29. try{
  30. connection.close();
  31. connection=null;
  32. }catch(SQLExceptione){
  33. e.printStackTrace();
  34. }
  35. }
  36. }
  37. /**
  38. *关闭查询语句
  39. *
  40. *@parampreparedStatement
  41. */
  42. publicstaticvoidrelease(PreparedStatementpreparedStatement){
  43. if(preparedStatement!=null){
  44. try{
  45. preparedStatement.close();
  46. preparedStatement=null;
  47. }catch(SQLExceptione){
  48. e.printStackTrace();
  49. }
  50. }
  51. }
  52. }

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

[java]view plaincopyprint?
  1. CREATETABLE`logs`.`log`(
  2. `id`INT(10)NOTNULLAUTO_INCREMENT,
  3. `record`VARCHAR(1024)NOTNULL,
  4. `visit_date`DATETIMENOTNULL,
  5. PRIMARYKEY(`id`)
  6. )
  7. ENGINE=MyISAM;

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

[java]view plaincopyprint?
  1. packagecom.crazycoder2010.socket;
  2. publicclassAppLauncher{
  3. /**
  4. *@paramargs
  5. */
  6. publicstaticvoidmain(String[]args){
  7. HttpServerhttpServer=newHttpServer(7777);
  8. httpServer.addRecordHandler(newConsoleRecordHandler());
  9. httpServer.addRecordHandler(createMysqlHandler());
  10. httpServer.start();
  11. }
  12. privatestaticRecordHandlercreateMysqlHandler(){
  13. MysqlRecordHandlerhandler=newMysqlRecordHandler();
  14. handler.setUrl("jdbc:mysql://localhost:3306/logs");
  15. handler.setUsername("root");
  16. handler.setPassword("");
  17. returnhandler;
  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请求监听程序实现

原文地址:http://www.cnblogs.com/csguo/p/7542374.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved