即时通讯与数据库编辑应用开发技术解析
1. 即时通讯应用基础实现
在即时通讯应用开发中,首先要实现用户登录及事件广播功能。以下是相关代码:
synchronized( this ){
users.put( id, user );
}
//create sign-on event
SignOnEvent event = new SignOnEvent();
event.contact = user.contact;
broadcastEvent( event, user );
//add sign-on events for current contact list
synchronized( this ){
Set entrySet = users.entrySet();
for( Iterator it = entrySet.iterator(); it.hasNext(); ){
Map.Entry entry = (Map.Entry)it.next();
UserInfo userTemp = (UserInfo)entry.getValue();
if( userTemp != user ){
SignOnEvent eventTemp = new SignOnEvent();
eventTemp.contact = userTemp.contact;
user.events.add( eventTemp );
}
}
}
上述代码实现了用户登录时将用户信息存入
users
集合,创建登录事件并广播,同时为当前联系人列表添加登录事件。
2. 传统服务器 IO 问题分析
在传统的服务器 IO 模式下,每个客户端连接对应一个线程,存在严重的性能问题。具体表现如下:
-
线程资源限制
:Servlet 容器有最大并发线程数限制,这决定了同时使用应用的最大客户端数量。例如,默认线程最大数量通常在 100 - 200 之间,若有 1000 甚至 10000 个并发用户,服务器将面临巨大压力。
-
内存与性能开销
:同时运行大量线程会消耗大量内存,还可能导致处理峰值,使服务器陷入停顿。此外,线程切换也会导致性能显著下降。
传统服务器 IO 模式下连接与线程的关系如下表所示:
| HTTP 请求 | 连接 | 线程 |
| — | — | — |
| 请求 1 | 连接 1 | 线程 1 |
| 请求 2 | 连接 2 | 线程 2 |
| 请求 3 | 连接 3 | 线程 3 |
3. 高级服务器 IO 解决方案
为解决传统服务器 IO 的问题,出现了基于非阻塞调用的高级 IO 解决方案,使得服务器在连接挂起时也能释放线程。以下是两种具体实现:
3.1 Tomcat 的 Comet 实现
Apache Tomcat 6 及更高版本支持 CometProcessor 接口,通过实现该接口的
event
方法处理异步事件。具体步骤如下:
1.
定义
PendingRequest
类
:用于保存 RPC 请求和 Comet 事件。
class PendingRequest{
RPCRequest rpcRequest;
CometEvent event;
public PendingRequest(RPCRequest rpcRequest, CometEvent event) {
this.rpcRequest = rpcRequest;
this.event = event;
}
}
-
实现
event方法 :处理 READ 事件,根据请求方法决定是保存请求还是直接处理。
Map pendingRequests = new HashMap();
CometMessengerService messengerService = new CometMessengerService();
public void event(CometEvent event) throws IOException, ServletException {
if (event.getEventType() == CometEvent.EventType.READ) {
//get the RPC request
RPCRequest rpcRequest = RPC.decodeRequest( readRequest( event ) );
Method targetMethod = rpcRequest.getMethod();
//if it’s the event request, then wait for events
synchronized(pendingRequests){
messengerService.perThreadRequest.set( event.getHttpServletRequest() );
if( targetMethod.getName().equals("getEvents") &&
!messengerService.hasEvents() ){
//save this request for processing later
pendingRequests.put( messengerService.getCurrentId(),
new PendingRequest( rpcRequest, event ) );
}
else{
//otherwise process the RPC call as usual
sendResponse( event, rpcRequest );
}
}
}
}
-
实现
sendResponse方法 :执行 RPC 调用并发送响应。
public void sendResponse( CometEvent event, RPCRequest rpcRequest ) {
try{
try{
messengerService.perThreadRequest.set(
event.getHttpServletRequest() );
String result = RPC.invokeAndEncodeResponse( messengerService,
rpcRequest.getMethod(), rpcRequest.getParameters());
writeResponse(event.getHttpServletResponse(), result);
event.close();
}
catch (IncompatibleRemoteServiceException e) {
writeResponse( event.getHttpServletResponse(),
RPC.encodeResponseForFailure(null, e) );
}
}
catch (Throwable e) {
writeResponse( event.getHttpServletResponse(), "Server Error" );
}
}
-
实现
CometMessengerService类 :处理事件通知。
class CometMessengerService extends AbstractMessengerService{
final ThreadLocal perThreadRequest = new ThreadLocal();
public String getCurrentId() {
return
((HttpServletRequest)perThreadRequest.get()).getSession(true).getId();
}
public void onEvents(String id) {
synchronized(pendingRequests){
PendingRequest pr = (PendingRequest)pendingRequests.get( id );
if( pr != null ){
pendingRequests.remove(id);
sendResponse( pr.event, pr.rpcRequest );
}
}
}
}
3.2 Jetty 的 Continuations 实现
Jetty 通过 Continuations 技术支持 Ajax 事件。使用时,创建
Continuation
对象并调用
suspend
方法释放线程,有响应时调用
resume
方法恢复处理。具体步骤如下:
1.
实现
doPost
方法
:读取请求负载并执行 RPC 调用,处理异常并重新抛出给 Jetty。
public void doPost(HttpServletRequest httpRequest,
HttpServletResponse httpResponse) throws ServletException, IOException {
//get the payload
String payload=(String)httpRequest.getAttribute("payload");
if (payload==null){
payload = readPayloadAsUtf8(httpRequest);
httpRequest.setAttribute("payload",payload);
}
try {
try {
//make the RPC call
RPCRequest rpcRequest = RPC.decodeRequest(payload);
messengerService.perThreadRequest.set( httpRequest );
String result = RPC.invokeAndEncodeResponse(messengerService,
rpcRequest.getMethod(), rpcRequest.getParameters());
writeResponse(httpResponse, result);
}
catch (IncompatibleRemoteServiceException e) {
writeResponse(httpResponse, RPC.encodeResponseForFailure(null, e) );
}
}
catch (Throwable e) {
if (e instanceof RuntimeException &&
"org.mortbay.jetty.RetryRequest".equals(e.getClass().getName())){
throw (RuntimeException) e;
}
}
}
-
实现
ContinuationsMessengerService类 :处理事件获取和通知。
class ContinuationsMessengerService extends AbstractMessengerService{
final ThreadLocal perThreadRequest = new ThreadLocal();
public String getCurrentId() {
return
((HttpServletRequest)perThreadRequest.get()).getSession(true).getId();
}
public void onEvents(String id) {
synchronized(pendingRequests){
Continuation c = (Continuation)pendingRequests.get( id );
if( c != null ){
pendingRequests.remove(id);
c.resume();
}
}
}
public ArrayList getEvents(){
ArrayList events = null;
UserInfo user = getCurrentUser();
if( user != null ){
if( user.events.size() == 0){
Continuation continuation = ContinuationSupport.getContinuation(
((HttpServletRequest)perThreadRequest.get()),this);
pendingRequests.put(user.id,continuation);
continuation.suspend(30000);
}
synchronized( user ){
events = user.events;
user.events = new ArrayList();
}
}
return events;
}
}
4. 数据库编辑应用概述
在开发新的 Web 应用时,并非总是适合采用最新的 Ajax 技术。传统 Web 页面开发具有简单和基础设施成熟的优势,但某些任务可能需要 Ajax 工具的支持。数据库编辑应用就是这样一个例子,它基于类似 Digg 的社交新闻网站,为管理员提供快速访问和管理应用数据的功能。
5. 数据库编辑应用特点
该应用具有以下特点:
-
采用 MVC 架构
:与其他示例应用类似,使用 MVC 架构组织代码,使代码结构清晰,易于维护。
-
使用 DAO 访问数据
:通过 Data Access Objects (DAOs) 访问服务器上的复杂数据结构,提供对数据格式、通信方法和服务器实现的抽象。
-
支持多种数据格式转换
:展示了如何将对象和对象集合转换为 XML 和 JSON 格式,还使用代码生成器减轻 Java 对象转换的繁琐工作。
-
支持多种通信方式
:提供三种与服务器通信的方法,分别是通过 GWT - RPC 的 RPC 调用、传统动作和 REST。在 http://databaseeditor.gwtapps.com 上可以找到 Java 实现的 RPC、PHP 实现的动作和 Ruby on Rails 实现的 REST 示例。
6. 管理器应用模式
数据库编辑应用遵循管理器应用模式,该模式用于管理大量数据,提供简单的界面来定位和操作数据。常见的管理器应用包括文件系统浏览器、电子邮件客户端和媒体库等。
该应用作为传统 Web 应用的管理员工具,通过简单的界面动态加载服务器数据,操作应用的数据模型。例如,在社交新闻网站中,用户提交新闻故事并投票,得票最多的故事显示在首页。数据库编辑应用不构建传统的 Web 前端,而是提供服务器端实现和数据库支持,与客户端管理器应用进行通信。
以下是数据库编辑应用的功能流程 mermaid 图:
graph LR
A[客户端请求] --> B[服务器接收请求]
B --> C{请求类型}
C -->|RPC| D[执行 RPC 调用]
C -->|传统动作| E[执行传统动作]
C -->|REST| F[执行 REST 请求]
D --> G[返回响应]
E --> G
F --> G
G --> A
综上所述,即时通讯应用和数据库编辑应用在开发过程中都涉及到多种技术和模式的应用。即时通讯应用通过高级服务器 IO 解决方案解决了传统 IO 模式的性能问题,而数据库编辑应用则展示了如何结合多种技术构建一个功能强大的 Web 应用管理工具。这些技术和模式的应用为 Web 应用开发提供了宝贵的经验和参考。
即时通讯与数据库编辑应用开发技术解析
7. 数据库编辑应用的详细实现步骤
7.1 数据访问对象(DAO)的实现
数据访问对象(DAO)在数据库编辑应用中起着关键作用,它负责与数据库进行交互,提供对数据的增删改查操作。以下是一个简单的 DAO 示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
// 假设这是一个新闻故事的数据访问对象
public class NewsStoryDAO {
private static final String DB_URL = "jdbc:mysql://localhost:3306/social_news";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
public List<NewsStory> getAllNewsStories() {
List<NewsStory> newsStories = new ArrayList<>();
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM news_stories");
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
NewsStory story = new NewsStory();
story.setId(rs.getInt("id"));
story.setTitle(rs.getString("title"));
story.setDescription(rs.getString("description"));
story.setLink(rs.getString("link"));
newsStories.add(story);
}
} catch (SQLException e) {
e.printStackTrace();
}
return newsStories;
}
// 其他增删改查方法可以类似实现
}
// 新闻故事类
class NewsStory {
private int id;
private String title;
private String description;
private String link;
// Getters and Setters
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
}
上述代码展示了一个简单的新闻故事 DAO,通过 JDBC 连接数据库并获取所有新闻故事。
7.2 数据格式转换
数据库编辑应用需要将对象和对象集合转换为 XML 和 JSON 格式,以便在客户端和服务器之间进行传输。以下是一个将新闻故事列表转换为 JSON 格式的示例:
import com.google.gson.Gson;
import java.util.List;
public class DataFormatConverter {
public static String convertToJSON(List<NewsStory> newsStories) {
Gson gson = new Gson();
return gson.toJson(newsStories);
}
}
使用 Google Gson 库可以方便地将 Java 对象转换为 JSON 字符串。
7.3 不同通信方式的实现
7.3.1 GWT - RPC 通信
GWT - RPC 是一种基于 Java 接口的远程过程调用机制。以下是一个简单的 GWT - RPC 服务接口和实现:
// 服务接口
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
@RemoteServiceRelativePath("newsService")
public interface NewsService extends RemoteService {
List<NewsStory> getAllNewsStories();
}
// 服务实现
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import java.util.List;
public class NewsServiceImpl extends RemoteServiceServlet implements NewsService {
private NewsStoryDAO newsStoryDAO = new NewsStoryDAO();
@Override
public List<NewsStory> getAllNewsStories() {
return newsStoryDAO.getAllNewsStories();
}
}
7.3.2 传统动作通信
传统动作通信通常通过表单提交或链接点击来触发服务器端的处理。以下是一个简单的 Servlet 示例:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
public class NewsServlet extends HttpServlet {
private NewsStoryDAO newsStoryDAO = new NewsStoryDAO();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<NewsStory> newsStories = newsStoryDAO.getAllNewsStories();
resp.setContentType("application/json");
PrintWriter out = resp.getWriter();
out.println(DataFormatConverter.convertToJSON(newsStories));
out.close();
}
}
7.3.3 REST 通信
REST 通信基于 HTTP 协议,通过不同的 HTTP 方法(GET、POST、PUT、DELETE)来实现资源的增删改查。以下是一个简单的 RESTful 服务示例:
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.List;
@Path("/news")
public class NewsResource {
private NewsStoryDAO newsStoryDAO = new NewsStoryDAO();
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<NewsStory> getAllNewsStories() {
return newsStoryDAO.getAllNewsStories();
}
}
8. 应用部署与扩展
即时通讯应用和数据库编辑应用开发完成后,需要进行部署和扩展。
8.1 即时通讯应用部署
即时通讯应用的部署步骤如下:
1. 使用
Messenger - compile
脚本编译应用。
2. 将生成的文件复制到 Web 服务器。
3. 将 Servlet 类复制到 Servlet 容器实现中,并包含 GWT Servlet JAR 文件,或者创建并部署 WAR 文件(通常使用 Ant 或 Maven)。
8.2 数据库编辑应用部署
数据库编辑应用的部署步骤如下:
1. 部署数据库,创建相应的表结构。
2. 部署服务器端代码,如 Servlet、RESTful 服务等。
3. 部署客户端代码,确保客户端能够与服务器进行通信。
8.3 应用扩展
两个应用都可以根据需求进行扩展,例如:
-
即时通讯应用
:可以添加更多的功能,如群聊、文件传输等。
-
数据库编辑应用
:可以增加更多的数据管理功能,如数据备份、恢复等。
9. 总结
即时通讯应用和数据库编辑应用展示了不同类型 Web 应用的开发技术和模式。即时通讯应用通过高级服务器 IO 解决方案解决了传统 IO 模式的性能问题,提高了应用的并发处理能力。数据库编辑应用则通过多种技术的结合,为管理员提供了一个强大的数据管理工具。
在开发过程中,我们学习了如何使用 MVC 架构、DAO 模式、多种数据格式转换和通信方式,以及如何遵循管理器应用模式。这些技术和模式的应用不仅提高了开发效率,还使应用具有更好的可维护性和扩展性。
以下是两种应用的技术对比表格:
| 应用类型 | 主要技术 | 解决问题 | 通信方式 |
| — | — | — | — |
| 即时通讯应用 | 高级服务器 IO(Comet、Continuations) | 传统 IO 模式性能问题 | GWT - RPC |
| 数据库编辑应用 | MVC 架构、DAO 模式、数据格式转换 | 数据管理和操作 | GWT - RPC、传统动作、REST |
通过对这两个应用的学习,我们可以更好地掌握 Web 应用开发的技巧和方法,为开发更复杂的 Web 应用打下坚实的基础。
graph LR
A[即时通讯应用开发] --> B[高级服务器 IO 实现]
B --> C[Comet 实现]
B --> D[Continuations 实现]
E[数据库编辑应用开发] --> F[MVC 架构设计]
E --> G[DAO 模式应用]
E --> H[数据格式转换]
E --> I[多种通信方式实现]
F --> J[代码结构清晰]
G --> K[数据访问抽象]
H --> L[XML 和 JSON 转换]
I --> M[GWT - RPC]
I --> N[传统动作]
I --> O[REST]
以上就是即时通讯应用和数据库编辑应用开发的详细解析,希望对大家的 Web 应用开发有所帮助。
超级会员免费看
1171

被折叠的 条评论
为什么被折叠?



