前言:
最近学了一堆网页相关的内容,html,js,jquery,javaweb,servlet,websocket等东西,所以就试着集合起来写个简单的网页,于是就决定就是你了:在线聊天网站。
而且还写了3个版本。。。
版本1:全是用的jsp写,不过发现jsp把网页代码和连接后台的代码都混在一起,感觉不好(可能是自己写得渣。。。),所以后面都没用jsp
版本2:写的时候不知道websocket,聊天信息更新是用的长轮询
版本3:版本2后面的时候查到有websocket,就有了下面这个
ps.版本2、3都是html+css+js+servlet
项目名称:在线聊天网站
项目代码:http://download.csdn.net/detail/name_z/9396909
(上面资源的资源简介中的创建数据库的代码有点错误,上传了没法修改…,以下面创建数据库代码为准,就是把tbUser中的keyword改为password就好了)
里面是整个eclipse创建的项目,直接导入eclipse可以了
使用该项目需要的数据库(MySQL)创建代码:
create table tbUser
(name varchar(10) not null,password char(20) not null,primary key(name)
)CHARACTER SET 'utf8'
COLLATE 'utf8_general_ci';
#其实还有另外一个表(存储聊天信息),不过后面写的时候忘记把聊天信息保存进数据库,所以其实可以不用管(程序里没用到),另一个表信息是:
create table tbConnection
(id int not null,orignName varchar(10) not null,targetName varchar(10) not null,content varchar(255) not null,date datetime not null,primary key(id),foreign key(orignName) references tbUser(name),foreign key(targetName) references tbUser(name)
)CHARACTER SET 'utf8'
COLLATE 'utf8_general_ci';
用到的知识和工具
工具:
1.tomcat8.0
2.eclipse for javaee
3.jdk1.8
4.mysql
涉及知识:
1.html
2.javascript+jquery
3.css
4.java
5.servlet+listener
6.websocket
7.mysql+sql
网页功能
1.提供登陆和注册功能
2.提供已登陆用户注销功能(退出当前帐户)
3.显示在线账户的数量及名称(通过手动点击刷新按钮刷新信息)
4.与在线用户聊天
4.1.点击显示的在线账户名后,可向该用户发送信息(该用户名将显示在聊天区域)
4.2.若接收信息的用户也已经点击发送人(即发送人的名字已显示在聊天区域)时,发送的信息将直接显示在聊天区域
4.3.若没有,则将会在网页右下角有提示信息,点击提示信息或者点击该用户名字均可将其显示在聊天区域
5.用户登陆到退出(注销或长时间关闭网页)之间的所有回话信息均有保存,即你与A在进行对话,此时切换到与B进行对话,再切回A对话时,此前与A的对话依然存在
6.查询历史记录(忘记实现了,后面就没弄了。。。)
网页结构
结构:
1.网页显示:html+css+jquery
2.向服务器发送数据以及处理服务器发送的数据:jquery
3.服务器:servlet
网页:
login.html
1.点击确定登陆
2.点击注册进入注册页面
signup.html
1.点击注册进行注册(注册成功后自动跳转到登陆页面)
2.点击登陆进入登陆页面
main.html
1.注销按钮退出当前账号,并跳转到登陆页面
2.刷新按钮刷新在线账户
3.在线账户下方为在线的账户名(按钮),点击可向其发送信息(名字显示在聊天区域–蓝色区域顶部)
4.右下角为提示信息(图中:用户dog向用户cat我发送了信息,因为此时我没有选定dog用户,所有没有直接显示在聊天区域而是变为提示信息)
js+jquery:
这次基本都是用的jquery,使用jquery要先把jquery库(也就是一个叫jquery.js的文件)下载下来,然后和其他js文件一样在html页面引用即可。
jquery这里用到比较重要的东西也就两个:
1.jquery get/post:从服务器请求数据
参考资料:http://www.w3school.com.cn/jquery/jquery_ajax_get_post.asp
用法:
//get方法
$.get(//servlet名称"servlet",//请求成功后执行的函数,result为服务器返回的数据function(result){}
);
//post方法
$.post("servlet",//传给服务器的数据{数据名称:数据},function(result){}
);
在注册页面中的使用:
2.客户端websocket
参考资料:http://wenku.baidu.com/view/4e3d2d34915f804d2a16c119.html(这个真的是通俗易懂)
简单来说,就是在没有websocket以及html5的时候,服务器和客户端间,只能由客户端先发送信息给服务器,服务器再回应客户端,而不能由服务器给客户端先发送信息。
而websocket和html5可以实现这个功能,由服务器首先发送信息给客户端,html5的资料:http://www.w3school.com.cn/html5/html_5_serversentevents.asp。
websocket的原理:
大概就是服务器和客户端通过3次握手建立一条tcp连接,所以之后双方都可以通过这条通道发送信息;
通过这个容易了解到以前服务器和客户端连接是:客户端和服务器之间由于信息不对等,客户端知道服务器地址,而服务器不知道客户端地址,所以每次都只能由客户端先发送信息,服务器根据信息报中的客户端地址向其回传信息。
使用:
服务器和客户端一样都是定义了4个方法:onopen,opclose,onmessage,onerror
客户端:
//建立与服务器的连接
var webSocket = null;
if ('WebSocket' in window)//传入的地址即为需要连接的websocket地址:ws://服务器地址:端口/程序地址/实现websocket的类名webSocket = new WebSocket('ws://192.168.1.101:8080/ConnectionWeb3.0/ConnectionWebSocket');
else if ('MozWebSocket' in window) webSocket = new MozWebSocket(wsuri);
else alert("not support");
//建立连接的事件
webSocket.onopen = function(event) {onOpen(event);
};
//接受到服务器传来的数据事件
webSocket.onmessage = function(event) {onMessage(event);
};
//event.data为服务器传来的数据
function onMessage(event)
{processConnectionMessage(event.data);
}
服务器的放到下面服务器说。。。
服务器
调用的基础类的关系图:
类名 | 介绍 | 存储 |
---|---|---|
User | 用户,存储用户姓名 | 存储在session中,即每个登陆的用户都有一个对应的User对象 |
UserManager | 在线用户管理,存储在线用户的User对象 | 存储于ServletContext中,整个网页程序只有一个UserManager |
Word | 一条通话信息,里面有发送人、接受人、信息内容、时间 | 存储于session中,且会被存储在数据库中 |
ConnectionManager | 管理通话信息 | 存储在session中,每个用户都有一个ConnectionManager对象,用于存储Word |
Database | 最底层与数据库连接的类,仅提供返回数据库连接、返回PreparedStatement两个功能 | 存储与ServletContext的DatabaseManager中 |
DatabaseManager | 针对数据优化Database的接口,提供更方便的操作数据的方法 | 存储在ServletContext中,整个网页程序只有一个 |
Servlet:
简单概括来说,servlet类就是负责处理用户传递给服务器的信息然后返回信息给用户的程序,某个角度来说,servlet就是服务器。
使用方法,就是通过继承Servlet类(目前基本是继承HttpServlet类),并重写里面的方法:
下面已SignupServlet来介绍
public class SignupServlet extends HttpServlet {public SignupServlet() {super();// TODO Auto-generated constructor stub
}
//客户端以get的方式发送请求时调用的函数protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {}
//客户端以post的方式发送请求时调用的函数protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {ServletContext context = request.getServletContext();//可通过ServletContext获得存储在里面的对象DatabaseManager dm = (DatabaseManager)context.getAttribute("DatabaseManager");PrintWriter out = response.getWriter();String name = request.getParameter("name");String password = request.getParameter("password");if(dm.addUser(name, password))//向客户端发送文本信息out.write("true");elseout.write("false");out.close();
}
//还有其他方式的请求的处理函数。。。
}
网页中的其它servlet:
Servlet | 介绍 |
---|---|
LoginServlet | 检查该session是否已经登陆过,以及检查传过来的登陆信息是否正确 |
SignupServlet | 检查传过来的注册信息,以及存储进数据库 |
DestroyServlet | 销毁当前发出请求的session的所有内容、信息 |
UserMessageServlet | 返回用户信息给用户 |
ConnectionMessageServlet | 返回用户聊天信息给用户(用户session中ConnectionManager存储的Word),以及保存聊天信息 |
Servlet的配置:
servlet的生效必须在web.xml中进行配置:
<servlet><servlet-name>SignupServlet</servlet-name><servlet-class>servlet.SignupServlet</servlet-class></servlet><servlet-mapping><servlet-name>SignupServlet</servlet-name><url-pattern>/signup_servlet</url-pattern></servlet-mapping>
servlet地址问题,前面的jquery get/post里面提到servlet名称,就是上面配置中的servlet-name里的内容
Listener:
参考资料:http://www.cnblogs.com/hellojava/archive/2012/12/26/2833840.html
参考资料中对Listener说的比较详细,就不多说了。
这个程序中只用到了ServletContextListener
public class MyServerletContextListener implements ServletContextListener {@Override//context销毁时调用的函数,也就是关闭该程序时候public void contextDestroyed(ServletContextEvent arg0) {// TODO Auto-generated method stub}@Override//context创建时候,也就是程序启动的时候public void contextInitialized(ServletContextEvent arg0) {// TODO Auto-generated method stub ServletContext context = arg0.getServletContext();//UsersManger在线用户管理器、DatabaseManager数据库都是整个应用//程序只有一个,并且所有用户都需共享一个,因此在context创建时设置//好context.setAttribute("usersManager", new UsersManager());context.setAttribute("DatabaseManager", new DatabaseManager());}
}
在web.xml的配置:
<listener><listener-class>listener.MyServerletContextListener</listener-class></listener>
服务器端websocket:
使用
上面客户端websocket处大概讲了websocket的原理以及客户端websocket的应用,现在来说服务器的websocket使用:
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;//这是指定websocket访问名字,不可漏
@ServerEndpoint("/ConnectionWebSocket")
public class ConnectionWebSocket{private static ArrayList<Session> sessions = new ArrayList<Session>();@SuppressWarnings("static-access")@OnMessage//收到信息时,把该信息向所有已经保存了session的用户发送信息public void onMessage(String message,Session session) throws IOException{for(Session temp:this.sessions){temp.getBasicRemote().sendText(message);}}@SuppressWarnings("static-access")@OnOpen//用户创建websocket连接时,保存用户的sessionpublic void onOpen(Session session) {// TODO Auto-generated method stubthis.sessions.add(session);}@SuppressWarnings("static-access")@OnClose//用户关闭websocket连接时,删除用户的sessionpublic void onClose(Session session) { this.sessions.remove(session);}
}
注意:
1.上面的Session和Servlet中的Session是不同的类
websocket注意点:
背景:一开始我写的时候私有变量sessions没有设置为static,然后发现在创建多个用户后,里面存储的session也依然只有一个。。。
所以,可以知道websocket不是唯一的,它和java的socket其实原理差不多,每有一个连接就创建一个websocket,所以想要所有socket共享数据,就必须设置为static。(现在突然想起这会引起并发的竞争问题,ArrayList不支持并发操作。。。当时写额时候没想起)
所以,websocket一定要注意共享和并发的问题。
聊天机制:
每当用户创建websocket连接时,保存用户的session用于发送信息,当然用户关闭连接时,就从中删除该用户的session;
聊天时:
假设现在存储了A、B、C、D四个用户的session,这时A向D发送信息a,ConnectionWebSocket接受到信息调用onMessage方法,把信息a发送给所有存储了session的用户也就是A、B、C、D四个用户,然后各个用户接受到信息后,检查是不是自己的信息。
其他遇到的问题
中文乱码问题
如果直接传输中文的话,特别是由服务器端传输到客户端(客户端传输到服务器暂未发现问题),通常会变成乱码,因此传输前先编码,接收后再解码
客户端:
//解码
decodeURI(data)
//编码
encodeURI(data)
服务器端:
//编码
URLEncoder.encode(data, "utf-8")
//解码
URLDecoder.decode(data, "utf-8")