一个基于Socket的http请求监听程序实现
首先来看以下我们的需求:
用java编写一个监听程序,监听指定的端口,通过浏览器如http://localhost:7777来访问时,可以把请求到的内容记录下来,记录可以存文件,sqlit,mysql数据库,然后把接受到的信息在浏览器中显示出来
要点:
Socket,线程,数据库,IO操作,观察者模式
来看下我们如何来设计这个小系统,这个系统包含三部分的内容,一个是监听端口,二是记录日志,三是数据回显,端口监听第一想到的就是Socket编程了,数据回显也是一样的,无非是把当前请求客户端的socket获取到,然后把消息通过流输出出去,日志的记录因为是要多种实现策略,这里我们使用了一个观察者模式来实现,服务器可以添加任意多个观察着,因此有着很灵活的扩展性,在实例程序中我们分别提供了ConsoleRecordHandler--直接把获取到的信息打印到控制台,和存放数据库的方式-MysqlRecordHandler,当然你也可以分别提供基于文件的实现。
首先来看我们系统的类图
HttpServer类是我们的核心类,他实现了Runnable接口,因此有着更高的性能,在循环中不断的去轮询指定端口,构造方法比较简单,只需要一个要监听的端口号即可,还有两个用于触发监听和停止程序运行的方法stop()&start(),这两个方法也比较简单,只是简单的给标志位赋值即可,我们这个程序是基于Oserver模式的简化版本,HttpServer本身是一个被观察的对象(Subject),当这个Subject有变化时(获取到客户端请求时)要通知监听器(我们的RecordHandler)去作操作(写数据库还是写文件或是直接控制台输出),极大的增加了系统的灵活性和易测试性
HttpServer类代码
- package com.crazycoder2010.socket;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.sql.Date;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * 服务器监听对象,对某个端口进行监听,基于线程的实现
- *
- * @author Kevin
- *
- */
- public class HttpServer implements Runnable {
- /**
- * 服务器监听
- */
- private ServerSocket serverSocket;
- /**
- * 标志位,表示当前服务器是否正在运行
- */
- private boolean isRunning;
- /**
- * 观察者
- */
- private List<RecordHandler> recordHandlers = new ArrayList<RecordHandler>();
- public HttpServer(int port) {
- try {
- serverSocket = new ServerSocket(port);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public void stop() {
- this.isRunning = false;
- }
- public void start() {
- this.isRunning = true;
- new Thread(this).start();
- }
- @Override
- public void run() {
- while (isRunning) {//一直监听,直到受到停止的命令
- Socket socket = null;
- try {
- socket = serverSocket.accept();//如果没有请求,会一直hold在这里等待,有客户端请求的时候才会继续往下执行
- // log
- BufferedReader bufferedReader = new BufferedReader(
- new InputStreamReader(socket.getInputStream()));//获取输入流(请求)
- StringBuilder stringBuilder = new StringBuilder();
- String line = null;
- while ((line = bufferedReader.readLine()) != null
- && !line.equals("")) {//得到请求的内容,注意这里作两个判断非空和""都要,只判断null会有问题
- stringBuilder.append(line).append("/n");
- }
- Record record = new Record();
- record.setRecord(stringBuilder.toString());
- record.setVisitDate(new Date(System.currentTimeMillis()));
- notifyRecordHandlers(record);//通知日志记录者对日志作操作
- // echo
- PrintWriter printWriter = new PrintWriter(
- socket.getOutputStream(), true);//这里第二个参数表示自动刷新缓存
- doEcho(printWriter, record);//将日志输出到浏览器
- // release
- printWriter.close();
- bufferedReader.close();
- socket.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 将得到的信写回客户端
- *
- * @param printWriter
- * @param record
- */
- private void doEcho(PrintWriter printWriter, Record record) {
- printWriter.write(record.getRecord());
- }
- /**
- * 通知已经注册的监听者做处理
- *
- * @param record
- */
- private void notifyRecordHandlers(Record record) {
- for (RecordHandler recordHandler : this.recordHandlers) {
- recordHandler.handleRecord(record);
- }
- }
- /**
- * 添加一个监听器
- *
- * @param recordHandler
- */
- public void addRecordHandler(RecordHandler recordHandler) {
- this.recordHandlers.add(recordHandler);
- }
- }
Record类非常简单,只是作为参数传递的对象来用
- package com.crazycoder2010.socket;
- import java.sql.Date;
- public class Record {
- private int id;
- private String record;
- private Date visitDate;
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getRecord() {
- return record;
- }
- public void setRecord(String record) {
- this.record = record;
- }
- public Date getVisitDate() {
- return visitDate;
- }
- public void setVisitDate(Date visitDate) {
- this.visitDate = visitDate;
- }
- }
RecordHandler接口,统一监听接口,非常简单
- package com.crazycoder2010.socket;
- /**
- * 获取到访问信息后的处理接口
- * @author Kevin
- *
- */
- public interface RecordHandler {
- public void handleRecord(Record record);
- }
ConsoleRecordHandler实现,直接System打印输出,在我们作测试时非常有用
- package com.crazycoder2010.socket;
- public class ConsoleRecordHandler implements RecordHandler {
- @Override
- public void handleRecord(Record record) {
- System.out.println("@@@@@@@");
- System.out.println(record.getRecord());
- }
- }
MysqlRecordHandler,数据库实现,定义了要对数据库操作所需要的几个基本属性url,username,password,加载驱动的程序我们放在了静态代码短中,这个东东嘛,只要加载一次就ok了
- package com.crazycoder2010.socket;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.SQLException;
- public class MysqlRecordHandler implements RecordHandler {
- static {
- try {
- Class.forName("com.mysql.jdbc.Driver");
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- private static final String NEW_RECORD = "insert into log(record,visit_date) values (?,?)";
- /**
- * 数据库访问url
- */
- private String url;
- /**
- * 数据库用户名
- */
- private String username;
- /**
- * 数据库密码
- */
- private String password;
- public void setUrl(String url) {
- this.url = url;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public void setPassword(String password) {
- this.password = password;
- }
- @Override
- public void handleRecord(Record record) {
- Connection connection = ConnectionFactory.getConnection(url, username,
- password);
- PreparedStatement preparedStatement = null;
- try {
- preparedStatement = connection.prepareStatement(NEW_RECORD);
- preparedStatement.setString(1, record.getRecord());
- preparedStatement.setDate(2, record.getVisitDate());
- preparedStatement.executeUpdate();
- } catch (SQLException e) {
- e.printStackTrace();
- } finally {
- ConnectionFactory.release(preparedStatement);
- ConnectionFactory.release(connection);
- }
- }
- }
ConnectionFactory类,我们的数据库连接工厂类,定义了几个常用的方法,把数据库连接独立到外部单独类的好处在于,我们可以很灵活的替换连接的生成方式--如我们可以从连接池中获取一个,而我们的数据库操作却只关注Connection本身,从而达到动静分离的效果
- package com.crazycoder2010.socket;
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.PreparedStatement;
- import java.sql.SQLException;
- /**
- * 创建数据库连接的工厂类
- *
- * @author Kevin
- *
- */
- public class ConnectionFactory {
- public static Connection getConnection(String url, String username,
- String password) {
- try {
- return DriverManager.getConnection(url, username, password);
- } catch (SQLException e) {
- e.printStackTrace();
- }
- return null;
- }
- /**
- * 释放连接
- *
- * @param connection
- */
- public static void release(Connection connection) {
- if (connection != null) {
- try {
- connection.close();
- connection = null;
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 关闭查询语句
- *
- * @param preparedStatement
- */
- public static void release(PreparedStatement preparedStatement) {
- if (preparedStatement != null) {
- try {
- preparedStatement.close();
- preparedStatement = null;
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
- }
init.sql我们的数据库建表脚本,只是为了演示,一个表就好
- CREATE TABLE `logs`.`log` (
- `id` INT(10) NOT NULL AUTO_INCREMENT,
- `record` VARCHAR(1024) NOT NULL,
- `visit_date` DATETIME NOT NULL,
- PRIMARY KEY (`id`)
- )
- ENGINE = MyISAM;
AppLuancher类,是时候把这几个模块高到一起跑起来的时候了,我们首先创建了一个服务器端,然后给服务器创建了两个监听器,然后启动服务器,这个时候我们的HttpServer已经开始监听7777端口了!
- package com.crazycoder2010.socket;
- public class AppLauncher {
- /**
- * @param args
- */
- public static void main(String[] args) {
- HttpServer httpServer = new HttpServer(7777);
- httpServer.addRecordHandler(new ConsoleRecordHandler());
- httpServer.addRecordHandler(createMysqlHandler());
- httpServer.start();
- }
- private static RecordHandler createMysqlHandler(){
- MysqlRecordHandler handler = new MysqlRecordHandler();
- handler.setUrl("jdbc:mysql://localhost:3306/logs");
- handler.setUsername("root");
- handler.setPassword("");
- return handler;
- }
- }
打开浏览器,输入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请求监听程序实现的更多相关文章
- Python:基于MD5的文件监听程序
前述 写了一个基于MD5算法的文件监听程序,通过不同的文件能够生成不同的哈希函数,来实现实现判断文件夹中的文件的增加.修改.删除和过滤含有特定字符的文件名的文件. 需求说明 需要实现对一个文件夹下的文 ...
- 安装完oracle重新启动后报ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务(重启前正常)
安装完oracle重新启动后报ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务(重启前正常) 刚安装完后用plSql登录正常. 在dos命令行下 输入 sqlplus 用户 ...
- Java:基于MD5的文件监听程序
前述和需求说明 和之前写的 Python:基于MD5的文件监听程序 是同样的功能,就不啰嗦了,就是又写了一个java版本的,可以移步 python 版本去看一下,整个的核心思路是一样的.代码已上传Gi ...
- 基于socket实现http请求
异步非阻塞模块原理 # 基于socket实现http请求 import socket # 多路IO复用模块 import select socket_list= [] url_list = [&quo ...
- 用nodejs搭建一个简单的服务监听程序
作为一个从业三年左右的,并且从事过半年左右PHP开发工作的前端,对于后台,尤其是对以js语言进行开发的nodejs,那是比较有兴趣的,虽然本身并没有接触过相关的工作,只是自己私下做的一下小实验,但是还 ...
- ORACLE telnet 1521 不通及ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务的解决
服务器上安装了oracle11g , 防火墙上已经增加1521 入站规则.但是内网客户端配置好了TNS无法连接.telnet 1521 不通. 需要在服务器上\product\10.2.0\db_1\ ...
- Oracle ORA12514 监听程序当前无法识别连接描述符中请求的服务
在连接数据库的时候,有时会遇到一个“ORA12514:监听程序当前无法识别连接描述符中请求的服务”的错误,这个错误其实就是数据库动态注册(关于动态注册会在稍后讲解)不生效,导致监听器无法识别客户端连接 ...
- 简述Java中Http/Https请求监听方法
一.工欲善其事必先利其器 做Web开发的人总免不了与Http/Https请求打交道,很多时候我们都希望能够直观的的看到我们发送的请求参数和服务器返回的响应信息,这个时候就需要借助于某些工具啦.本文将采 ...
- Oracle登录失败:监听程序当前无法识别连接描述符中请求的服务
Oracle11g下载地址:https://pan.baidu.com/s/1p3RwLUTAl1Ys4yXmXJ3OVQ 安装步骤视频链接:https://pan.baidu.com/s/1c0FC ...
随机推荐
- 今天犯了一个StringBuilder构造函数引起的二逼问题。
在.Net里,StringBuilder的构造函数有很多,最常用的是无参的构造函数,默认分配16个字符的空间.其次就是填写StringBuilder空间的带一个Int32的构造函数,这个在优化代码的时 ...
- web api 多版本控制重要的两个类
1.版本路径替换 public class ReplaceVersionWithExactValueInPath : IDocumentFilter { public void ...
- jvm lock低性能分析
日志平台client面临着输出日志的问题.为了避免干扰业务系统,我们采用异步输出的方式.这实际上相当于一个多生产者-单消费者的多线程模型.传统的方式是使用同步加锁的方式,但是这种方式不够高效.之前 钟 ...
- 学习Spring Data JPA
简介 Spring Data 是spring的一个子项目,在官网上是这样解释的: Spring Data 是为数据访问提供一种熟悉且一致的基于Spring的编程模型,同时仍然保留底层数据存储的特殊 ...
- C#获取微信二维码显示到wpf
微信的api开放的二维码是一个链接地址,而我们要将这个二维码显示到客户端.方式很多,今天我们讲其中一种. /// <summary> /// 获取图片路径 /// </summary ...
- Day 28面向对象的进阶-内置函数(__new__,__del__)
元类 创造 类 所有类的type 都是他的元类 类创造 对象 具体创造对象的方法 __new__方法 class 类名(classmata = type)#默认是 class 类名(class ...
- 因子和(luoguP1593)(等比数列求和+逆元)
输入两个正整数\(a\)和\(b\),求\(a\cdot b\)的因子和.结果太大,只要输出它对9901的余数. Input 仅一行,为两个正整数\(a\)和\(b\)(\(0≤a,b≤5000000 ...
- Sansa组件
诉求 仿照admin组件,实现对表的URL分配管理. 实现思路 1.在settings.py文件中注册APP,注册示例为: 'app01.apps.App01Config', 'app02.apps. ...
- kali Linux 上编译并使用RFID核弹——proxmark3
你还在在Windows下使用proxmark3?弱爆了! 本文作者:i春秋签约作家——冰尘 作为一个标准的日天日地日空气的(单身贵族泰迪)物理黑客Proxmark3这么高大上的东西应该是在键盘敲打声中 ...
- PHP开发接口,封装方法
接口的主要功能是从服务器端获取数据,然后渲染到客户端 其主要的实现流程一般会经历这样的几个阶段服务器端----> 数据库|缓存 ----> 调用接口 ---->客户端 在接口数据传输 ...