零国拇、總體介紹
項目背景
該項目是通信軟件開發(fā)實訓(xùn)課程的一門作業(yè)考廉,要求完成一個聊天系統(tǒng)昙篙,但要求有至少一個亮點腊状,無論是功能上的還是技術(shù)上的。
我們小組比較擅長Java Web開發(fā)苔可,因此決定在技術(shù)上做一個亮點缴挖,使用WebSocket
文檔說明
該文檔按照如下順序進行介紹
- 技術(shù)背景
介紹該項目要用到的一些背景知識 - 項目目錄結(jié)構(gòu)
該項目使用myeclipse的Java EE項目,大體上可分為前臺與后臺 - 項目框架搭建
介紹前臺用到的技術(shù)焚辅,以及后臺框架的搭建 - 項目成果展示
- 代碼分享
一映屋、技術(shù)背景
1.WebSocket
WebSocket是HTML5開始提供的一種在單個 TCP 連接上進行全雙工通訊的協(xié)議。WebSocket通訊協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455法焰,WebSocketAPI被W3C定為標(biāo)準(zhǔn)秧荆。
在WebSocket API中倔毙,瀏覽器和服務(wù)器只需要做一個握手的動作埃仪,然后,瀏覽器和服務(wù)器之間就形成了一條快速通道陕赃。兩者之間就直接可以數(shù)據(jù)互相傳送卵蛉。
2.Spring
Spring是一個開源輕量級的框架,使用控制反轉(zhuǎn)(IoC)和面向切面(AOP)么库,它可以讓開發(fā)變得簡單傻丝、輕便、快速與更加靈活诉儒。
3.Spring WebSocket
Spring從4.0開始加入了spring-websocket這個模塊葡缰,并能夠全面支持WebSocket,它與Java WebSocket API標(biāo)準(zhǔn)(JSR-356)保持一致,同時提供了額外的服務(wù)泛释。
4.Spring MVC
Spring MVC是一個model-view-controller(MVC)框架滤愕,能很好地將數(shù)據(jù)、業(yè)務(wù)與展現(xiàn)進行分離怜校。Spring MVC的設(shè)計是圍繞DispatcherServlet展開的间影,DispatcherServlet負(fù)責(zé)將請求派發(fā)到特定的handler。
5.MyBatis
MyBatis 是支持定制化 SQL茄茁、存儲過程以及高級映射的優(yōu)秀的持久層框架魂贬。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或注解裙顽,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數(shù)據(jù)庫中的記錄付燥。
在本項目中,我們使用了以上幾個框架進行搭建愈犹。
二机蔗、目錄結(jié)構(gòu)
Com.tx.
Config websocket配置文件
Controller spring mvc 控制層
DAO 數(shù)據(jù)庫連接操作
Handler websocket操作
Model 數(shù)據(jù)實體
Service 服務(wù)器處理
Tool 工具類Webroot
前臺代碼目錄
Js javascript代碼
Style css代碼
Test 前臺測試代碼WEN-INF 配置文件夾
Jsp jsp文件夾
Lib 項目所需的外部jar包
As-servlet.xml spring mvc 配置文件
Web.xml web項目配置文件
三、實驗步驟
web服務(wù)前端
1甘萧、socket相關(guān)代碼結(jié)構(gòu)
var socket = new Socket(url);//url必須為ws://開頭的相關(guān)協(xié)議
socket.onopen = function(event){
//連接初始化代碼
};
sokcet.onmessage = function(event){
var text = event.data;
//處理接受到的消息
};
socket.onclose = function(event){
//連接關(guān)閉時觸發(fā)
};
socket.onerror = function(event){
//連接過程中出錯時觸發(fā)
}
2.本次實驗所封裝的一些函數(shù)的解析
- 消息封裝函數(shù)萝嘁,用于生成能直接添加到html的dom節(jié)點的文檔類型
function messagePackage(message) {
/*
message{
userName : xx,
timeSign : 22:12:44,
content : abc
}
*/
var element_section = $("<section></section>");
var element_section_p1 = $("<p></p>");
var element_section_p1_user = $("<span></span>");
var element_section_p1_time = $("<time></time>");
var element_section_p2_content = $("<p></p>");
element_section.addClass("message");
element_section_p1.addClass("header");
element_section_p2_content.addClass("content");
element_section_p1_user.text(message.username);
element_section_p1_time.text(message.timeSign);
element_section_p2_content.text(message.content);
element_section_p1.append(element_section_p1_user);
element_section_p1.append(element_section_p1_time);
element_section.append(element_section_p1);
element_section.append(element_section_p2_content);
return element_section;
}
- 消息處理函數(shù),用于處理onmessage中由服務(wù)器發(fā)送到客戶端的消息扬卷,并規(guī)定了消息類型與相關(guān)的處理方式
/*
jsonData : {
type:1(聊天信息)||2(用戶列表更新信息),
username(1,2):xx,
timeSign(1):xx:xx:xx,
content(1):xxxxxxxxxxxxx,
}
*/
function messageHandle(event) {
var jsonStr = event.data;
var data = JSON.parse(jsonStr);
var $message = null;
switch(data.type) {
//更新聊天顯示框
case 1:
if(data.username == currentUser) return;
$message = messagePackage({
username : data.username,
timeSign : data.timeSign,
content : data.content
});
$show.append($message);
//讓滾動條自動滾到底
$show.get(0).scrollTop = $show.get(0).scrollHeight;
break;
//向已經(jīng)在線的用戶發(fā)送用戶列表更新信息
case 2:
var $userName = $("<p></p>");
$userName.text(data.username);
$("#usersInfo").append($userName);
break;
//將所有已經(jīng)在線的用戶信息發(fā)送給剛加入的用戶
case 3:
var usernames = data.usernames;
var $usersInfo = $("#usersInfo");
$usersInfo.empty();
for(var i= 0,len=usernames.length;i<len;i++) {
var $userName = $("<p></p>");
$userName.text(usernames[i]);
$usersInfo.append($userName);
}
break;
//刪除用戶信息
case 4:
var $usersInfo = $("#usersInfo");
$usersInfo.find(":contains("+data.username+")").remove();
}
}
Spring & Spring MVC配置
1牙言、Web.xml 配置
配置spring 過濾器 并設(shè)置UTF-8編碼
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
配置servlet spring mvc攔截器 設(shè)置匹配后綴名為.do
<display-name>as</display-name>
<servlet>
<servlet-name>as</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>as</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
2.as-servlet.xml 配置Spring MVC
使用Spring注解的方式
<mvc:annotation-driven />
<context:annotation-config />
<context:component-scan base-package="com.tx.*" />
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />
返回json模板
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
</list>
</property>
</bean>
設(shè)置spring mvc視圖
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
3.編寫Spring MVC controller
controller用于接收和發(fā)送前后臺的請求與響應(yīng)
前后臺定義用來傳送的json字符串
聊天消息:
'{"type":1,"username":"xxxx","timeSign":"15:21:02","content":"消息內(nèi)容"}'
用戶列表更新消息(已加入):
'{"type":2,"username":"xxxx"}'
用戶列表全部消息(剛加入):
'{"type":3,"usernames":["a","b","c"]}'
用戶列表某用戶信息刪除 :
{"type":4,"username":"xx"}
本例以用戶登錄、注冊為例怪得,其余部分請參考頁面最下方提供的源代碼
這里使用了Spring MVC注解來進行請求的處理
例:
@RequestMapping里面填寫的是請求的地址
return 根據(jù)返回值的不同進行頁面跳轉(zhuǎn)等操作咱枉,這里返回 login 表示跳轉(zhuǎn)到 login.jsp 這個頁面
@RequestMapping("login.do")
public String gtLogin() {
return "login";
}
```java
@Controller
public class MainController {
@RequestMapping("register.do")
public String gtRegister() {
return "register";
}
@RequestMapping("login.do")
public String gtLogin() {
return "login";
}
@RequestMapping("loginServer.do")
public ModelAndView login(@RequestParam("username") String username,
@RequestParam("password") String password) {
ModelAndView modelAndView = new ModelAndView();
StudentService ss = new StudentService();
boolean r = ss.login(username, password);
if (r) {
modelAndView.addObject("name", username);
modelAndView.setViewName("chat");
} else {
modelAndView.setViewName("login");
}
return modelAndView;
}
@RequestMapping(value = "registerServer.do", method = RequestMethod.POST)
public ModelAndView gtRegister(@RequestParam("username") String username,
@RequestParam("password") String password) {
StudentService ss = new StudentService();
ModelAndView modelAndView = new ModelAndView();
boolean r = ss.register(username, password);
if (r) {
modelAndView.addObject("name", username);
modelAndView.setViewName("chat");
} else {
modelAndView.setViewName("login");
}
return modelAndView;
}
}
Mybatis配置
有關(guān)mybatis的具體使用說明可以參考 mybatis文檔
1.建立與數(shù)據(jù)庫表對應(yīng)的model,POJO類
這里僅以Student.java類來說明:
類名同數(shù)據(jù)庫中的表名一致徒恋,變量名同同數(shù)據(jù)庫中的字段名一致蚕断,并編寫get()和set()方法
package com.tx.model;
public class Student {
private int id;
private String name;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2.mybatis-config.xml設(shè)置
我們使用xml來對mybatis數(shù)據(jù)庫進行配置,里面包括:
- driver: 數(shù)據(jù)庫驅(qū)動程序
- url: 數(shù)據(jù)庫連接地址及數(shù)據(jù)庫名
- username:數(shù)據(jù)庫連接用戶名
- password:數(shù)據(jù)庫連接密碼
- xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/chat"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/tx/model/mapping.xml"/>
</mappers>
</configuration>
3.mapping.xml設(shè)置
mybatis的映射表入挣,通過寫sql語句來進行查詢
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tx.model.Mapper">
<!-- 通過username獲得密碼 -->
<select id="getpwd" parameterType="String" resultType="String">
select stuPassword from student where stuName=#{stuName}
</select>
<insert id="register" parameterType="String">
insert into student (stuName, stuPassword) values (#{stuName}, #{stuPassword})
</insert>
</mapper>
DAO層代碼
mybatis的映射文件亿乳,方法名與xml中的id相同
package com.tx.model;
import org.apache.ibatis.annotations.Param;
public interface Mapper {
public String getpwd(String stuName);
public int register(@Param("stuName")String stuName, @Param("stuPassword")String stuPassword);
}
DAO層,調(diào)用Mybatis的Mapper接口
package com.tx.DAO;
import org.apache.ibatis.session.SqlSession;
import com.tx.model.Mapper;
import com.tx.tools.Helper;
public class StudentDAO {
SqlSession session = Helper.getSessionFactory().openSession();
Mapper mapper = session.getMapper(Mapper.class);
public String getpwd(String username){
return mapper.getpwd(username);
}
public int register(String name, String password) {
int result = 0;
SqlSession session = Helper.getSessionFactory().openSession();
try {
Mapper mapper = session.getMapper(Mapper.class);
result = mapper.register(name, password);
session.commit();
} finally {
session.close();
}
return result;
}
}
service層代碼
service層調(diào)用DAO層
package com.tx.service;
import com.tx.DAO.StudentDAO;
public class StudentService {
StudentDAO sdao = new StudentDAO();
public boolean login(String name, String password) {
boolean result = false;
if (password.equals(sdao.getpwd(name))) {
result = true;
}
return result;
}
public boolean register(String name, String password) {
if(sdao.register(name, password) == 1) {
return true;
} else {
return false;
}
}
}
Spring websocket配置
1.WebSocketConfigurer設(shè)置
注冊Spring WebSocket服務(wù)
其中registerWebSocketHandlers方法:
registry.addHandler(systemWebSocketHandler(),"xxx");
xxx填寫準(zhǔn)備使用的WebSocket服務(wù)器請求地址径筏,本項目中以 webSocketServer.do 為例
package com.tx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.client.standard.WebSocketContainerFactoryBean;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import com.tx.handler.SystemWebSocketHandler;
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(systemWebSocketHandler(),"webSocketServer.do");
}
@Bean
public WebSocketHandler systemWebSocketHandler(){
return new SystemWebSocketHandler();
}
@Bean
public WebSocketContainerFactoryBean createWebSocketContainer() {
WebSocketContainerFactoryBean container = new WebSocketContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
}
2.WebSocketHandler設(shè)置
服務(wù)器如何處理WebSocket請求在這里面填寫
成功建立連接
接收到消息處理
處理異常
連接關(guān)閉后
public class SystemWebSocketHandler implements WebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
System.out.println("ConnectionEstablished");
//TODO
}
@Override
public void handleMessage(WebSocketSession session,
WebSocketMessage<?> message) throws Exception {
//TODO
}
@Override
public void handleTransportError(WebSocketSession session,
Throwable exception) throws Exception {
//TODO
}
@Override
public void afterConnectionClosed(WebSocketSession session,
CloseStatus closeStatus) throws Exception {
System.out.println("ConnectionClosed");
//TODO
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
四葛假、運行結(jié)果
用戶注冊
在瀏覽器中輸入 http://localhost:8080/RealTimeChat/register.do
填寫用戶名及密碼后,點擊注冊按鈕
注冊成功后滋恬,自動跳轉(zhuǎn)至聊天頁面
聊天界面
頂部為當(dāng)前登錄用戶
左上方為聊天室的聊天窗口
右上方為當(dāng)前聊天室所有的在線用戶
下方為用戶的輸入窗口
登錄界面
新用戶登錄 http://localhost:8080/RealTimeChat/login.do
多人聊天
新用戶加入
用戶退出
kyle用戶退出