首先來看以下我們的需求:
用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
再去查以下我們的資料庫,呵呵,也有了,再看看我們的浏覽器上是否也把這些資訊同樣顯示出來了~~