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

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

要点:

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

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

首先来看我们系统的类图

8467571_1299858521AIOo.png

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 recordHandlers = new ArrayList();

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

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐