手把手教你实现一个网页聊天室

现在写文章都喜欢“手把手”,有一种老爸抱着三岁的儿子在路边撒尿的感觉,所以这篇的题目我也“手把手”一回,费话不说,开始撒尿:

使用到的技术

  • websocket
  • springmvc
  • maven
  • fastjson

这里使用到的主要技术是websocket,后端服务用java来写,其中springmvcmaven只是用于写java时的框架和构建工具,fastjson用于json数据格式的转换。

什么是websocket?

websocket是html5提供的一种在一个(TCP)接口进行全双工通信的技术,所谓全双工通信,简单点说就是通信的双方都可以同时向对方发送数据,类似于打电话时两方可以同时说话。

在没有websocket之前,实现聊天室这种实时的消息响应需求,一般会用长连接轮询的方式,在特定的的时间间隔(如每1秒),由客户端对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端,这种方式有很明显的缺点,客户端需要不断的向服务器发出请求,然而HTTP请求的header是非常长的,而我们需要的有用数据可能只是一个很小的值,这样会占用很多的带宽。而用websocket,浏览器和服务器只需要做一个握手的动作,两者之间就形成了一条快速通道,直接就可以互相传送数据了。

支持websocket的浏览器和服务器

websocket是html5的特性,浏览器至少要支持html5,服务器我这边用tomcat7.0.54(其它较高版本我这边不起作用!?),另外java版本要7以上:

浏览器支持 版本
Chrome 4+
Firefox 4+
Internet Explorer 10+
Opera 10+
Safari 5+
服务器支持 版本
tomcat 7.0.27+
jetty 7.0.1+
Nginx 1.3.13+
resin 4+

开始代码

websocket浏览器实现

浏览器通过javascript向服务器发起websocket请求,获取websocket连接后,浏览器和服务器就可以通过TCP连接直接交换数据了。

首先需要创建一个websocket对象:

var socket = new WebSocket('ws://localhost:8080/chatsocket');

这里的参数指定后台websocket服务的连接地址,不同于http://,websocket请求地址以ws://开头,接下来是主机的地址+端口号+后台服务名,这里的chatsocket是后面java后端所起的服务名字。其实第一次的握手连接,套接字还是以HTTP连接开始的,在HTTP握手之后才升级为TCP套接字,任意一端就都可以发送数据了。如果页面用得是JSP模板,地址和端口可以用JSP的pageContext获取,而不要直接写死:

  • 获取地址:${pageContext.request.serverName}
  • 获取端口:${pageContext.request.serverPort}
  • 获取上下文路径:${pageContext.request.contextPath}

websocket对象的方法

创建websocket对象后,它有一些相关属性、事件和方法,具体可以查看W3C的websocket API

比较重要的事件和方法主要有以下几个:

//连接成功建立的回调方法
socket.onopen = function (event) { console.log("连接服务器成功!"); };
//点击发送消息的方法,这里以json格式传送数据
socket.send(JSON.stringify({
    content: "这里是消息的内容"
    name:"发送人"
}));
//接收到消息的回调方法,一般将返回的数据append到消息页面
socket.onmessage = function (event) { 
    var message = JSON.parse(event.data);
    //dosomething...
    //发送人:message.name ,发送时间: message.date,发送内容:message.content
};

websocket服务器实现

第一步:加入信赖

java要使用websocket要加入相关信赖:

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>4.0.1.RELEASE</version>
</dependency>

注意:

  • tomcat的lib目录下有一个包websocket-api.jar,用这个包就可以了,所以部署的时候将上述的javax.websocket-api.jar删除再重新启动,否则两则重复,启动会报错。
  • spring-websocket-4.0.1.RELEASE.jar这个包的作用只是需要在websocket后端注入service所用的,否则删除这个包不影响websocket服务运行。
  • 具体spring对websocket的支持见这里

第二步:开启后端服务

新建一个java类,在这个类上加上ServerEndpoint注解,它就变成websocket服务了:

@ServerEndpoint(value = "/chatsocket",configurator = SpringConfigurator.class)
public class HudongEndpoint{

}

这里定义了两个参数:

  • value:服务的名字,前面js创建websocket时的url的服务名就是这个
  • configurator:如果要用spring注入servie,就要加上这个参数

第三步:接收消息的方法

和html5的websocket一样,后端也有相关操作方法,加上相关的注解就可以,比如:

  • @OnOpen:连接建立成功时调用的方法
  • @OnMessage:收到客户端消息后调用的方法
  • @OnClose:连接关闭调用的方法
  • @OnError:发生错误时调用的方法

这里主要说明下@OnMessage方法:

@OnMessage
public void getMessage(String message, Session session) {
    JSONObject jsonObject= JSONObject.parseObject(message);

    jsonObject.put("date", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    for (Session openSession : session.getOpenSessions()) {
        jsonObject.put("isSelf", openSession.equals(session));
        openSession.getAsyncRemote().sendText(jsonObject.toString());
        //openSession.getBasicRemote().sendText(message);
    }
}

这个方法有两个参数:

  • message:前端发送过来的消息,json格式
  • session:这个参数可选,为与某个客户端的连接会话,需要通过它给客户端发送数据。如果要将聊天数据保存到数据库中,可以在这个方法中执行相关操作

总结

整个websocket发送消息和接收消息的过程,和http请求在使用上其实没多大区别,后台的一个ServerEndpoint类似于 springmvc的一个controller,相关的业务操作在对应的方法里就是了。这里只是简单的文本聊天,如果要加上表情和图片,可以使用富文本编辑器(Ueditor,kindeditor等),当然还可以加上计算实时在线人数,上线下线通知等功能。

网页布局:CSS实现一列二列三列和混合布局

常见网站布局

网站常见的布局方式主要有一列布局,二列布局,三列布局和混合布局。

一列布局

效果图:

一列布局

代码:

  • CSS:
    body{margin:0;padding: 0;}
    .top{height:100px;background: blue;}
    .main{width:800px;height:300px;background: #ccc;margin:0 auto;}
    .foot{width: 800px;height:100px;background: red;margin: 0 auto;} 
  • HTML:
    <div class="top"></div>
    <div class="main"></div>
    <div class="foot"></div>
  • 说明:

页面中有三块div,头底和中间内容部分,三部分都设置了固定的高度,顶部的宽度不设置,在实际开发中main部分一般不设置固定高度,而是可随着内容的多少而多高,可以参考新浪网

二列布局

效果图:

二列布局

代码:

  • CSS:
body{margin:0;padding: 0;}
.main{width:600px;margin:0 auto;}
.left{width: 200px;height:300px;background: #ccc;float:left;}
.right{width:400px;height:300px;background: #ddd;float:right;}

  • HTML:
<div class="main">
    <div class="left"></div>    
    <div class="right"></div>
</div>

  • 说明:

二列布局的宽高一般是固定的,所以一般外面再套一层main,用这个main来固定宽高。

三列布局

效果图:

三列布局

代码:

  • CSS:
body{margin:0;padding: 0;}
.left{width: 200px;height:500px;background: #ccc;position:absolute;left: 0;top: 0;}
.middle{background: blue; height: 500px;margin:0 200px 0 200px;}
.right{width:200px;height:500px;background: red;position: absolute;right: 0;top: 0;}

  • HTML:
<div class="left"></div>    
<div class="middle">sdfsdfsdfsdfsdf</div>
<div class="right"></div>

  • 说明:

三列布局,让left绝定定位到左侧,right绝定定位到右侧,中间不设宽度,则可以随浏览器自适应,这里要用absolute的方式来定位,用float难以实现。

混合布局

效果图:

混合布局

代码:

  • CSS:
body{margin:0;padding: 0;}
.top{height:100px;background: blue;}
.head{height: 100px;width: 800px;background: #f60;margin: 0 auto;}
.main{width:800px;height:300px;background: #ccc;margin:0 auto;}
.left{width:200px;height: 300px;background: #eee;float: left;}
.right{width:600px;height: 300px;background: #369;float: right;}
.foot{width: 800px;height:100px;background: red;margin: 0 auto;}

  • HTML:
<div class="top">
    <div class="head"></div>
</div>
<div class="main">
    <div class="left"></div>
    <div class="right"></div>
</div>
<div class="foot"></div>

  • 说明:

常见网站的布局都是混合布局,上面的混合布局例子在原先的一列布局上做了修改,在main下划分了左右块,top下加了head块。可以根据需要,在leftright块下再划分为其它块。

ECharts使用总结

我对Echarts的理解

我对技术的理解是,一门技术,先会使用,再去理解,使用才是真正目的,理解只是更上一层楼,但不是强求的,技术只是工具而已,它不是知识。所以用Echarts实现图表的展现,其内部原理,实现机制,程序员根本不需要了解,我们要做的就是把数据扔给它,如何给它数据,一是直接在页面上写死(-_-……),二是访问图表页面的时候,程序从后台返回数据,前台页面取得数据后,在需要的地方填入即可,三是用Ajax的方式来动态加载数据,Ajax取得数据后,在返回后的succss方法里通过js脚本替换Echarts的option里的相关数据,当然还有其它方法,目的只有一个,把数据给它。

Echarts的简单使用

一、准备。

新建一个echarts_demo.html页面,在body中准备一个div,用于放置图表。

<body>
    <div id="main" style="width:800px;height:500px"></div>
</body>

二、引入echarts.js

在echarts_demo.html中,引入echarts,主要有两种方式,一种是模块化包引入:

<script src="http://echarts.baidu.com/build/dist/echarts.js"></script>

一种是单文件引入,在官网下载文件后,放在本地项目下,如下引入:

<script src="/js/echarts.js"></script>

三、配置echarts路径

<script type="text/javascript">
    // 路径配置
    require.config({
        paths: {
            echarts: 'http://echarts.baidu.com/build/dist'
        }
    });
</script>

四、初始化图表

ECharts图表大部分要关心的是option的实现,这里以阅读数为例生成柱状图,代码如下。

<script type="text/javascript">
    var option = {
        tooltip: {
            show: true
        },
        legend: {
            data:['2015年1-9月读书数'],
            backgroundColor:'blue'
        },
        xAxis : [
            {
                type : 'category',
                data : ["1月","2月","...省略","8月","9月"]
            }
        ],
        yAxis : [
            {
                type : 'value'
            }
        ],
        series : [
            {
                "name":"读书数",
                "type":"bar",
                "data":[3, 4, 1,1,1,3,1,2,4],
                itemStyle: {
                    normal: {
                        label : {
                            show: true,
                            position: 'top'
                        }
                    }
                }
            }
        ]
    };
    // 为echarts对象加载数据 
    myChart.setOption(option); 
    }
);
</script>

五、效果图

2015read2015.09.24_11h01m16s_002_

Ajax动态加载数据 [重点]

用Ajax动态加载图表数据,后台以Json方式传过来一个map,需要的是把option里用到的静态数据替换为返回的Json数据,要换的数据主要是

option.legend.data
option.xAxis.data
option.series.data

后台传过来的数据一般如下格式:

 {
    counts={
        食品=[3, 5, 35, 15], 
        药品=[5, 3, 2, 8]
    }, 
    years=[2011, 2012, 2013, 2014, 2015],        
    types=[
        com.demo.bean.CorpType@16d63462,
        com.demo.bean.CorpType@74728371,
        com.demo.bean.CorpType@a13ab71
        ]
    }

Ajax调用的代码如下,目的就是动态给相关属性设置值:

$.ajax({
        url:"/echarts/url", //访问后台取得Json数据路径
        type:"POST",
        dataType:"json",
        async:false,
        success:function(result){
            var types=[];           
            for(var i=0;i<result.types.length;i++){
                types.push(result.types[i].name);
            }
            option.legend.data=types;
            option.xAxis[0].data=result.years;
            option.series=[];
            for(var n in result.counts){
                var new_series={
                    type:'bar',
                    itemStyle: {
                    normal: {
                        label : {
                            show: true,
                            position: 'top'
                        }
                    }
                }};
                new_series.name=n;
                new_series.data=result.counts[n];  
                option.series.push(new_series);
            }
            myChart.hideLoading();
            myChart.setOption(option);
         }
    });

[完]

编程神器VIM安装美化

天下武功,唯快不破。编程器对于程序员,犹如剑对于剑客。什么样的剑配什么样的剑客,什么样的编程器配什么样的程序员。VIM、Emacs、Sublime Text、textmate、notepad等,这里面我最喜欢的是VIM和SublimeText,sublime Text近几年才流行起来,而VIM历史悠久,学习成本高,很多人望而生畏,但一旦上手熟悉后,能极大提高编程效率,VIM可以说是第一编程神器。

效果图

趋势图

安装:

在windows系统下,VIM称为GVIM,在官网下载。安装后,在安装目录下,有三部分:

  • vim74:安装目录,我这里安装的7.4版本。
  • vimfiles:用户自己的一个配置文件夹,vim74中的plugin和vimfiles中的plugin作用是一样的,插件放到这2个文件夹都会起作用。
  • _vimrc:VIM的个性化配置文件,快捷键、主题、字体等都在这里配置。

安装Pathogen(管理插件的插件)

VIM的一个强大的地方在于它有无数方便的插件,在Pathogen或Vundle出现之前,VIM的插件管理非常混乱,安装好VIM后的第一步事就应该是安装Pathogen或Vundle,管理插件的插件,以Pathogen为例,首先在vimfiles目录下手工新建两个文件夹,名字分别为autoload和bundle,然后下载Pathogen为一个ZIP文件,解压后将其中pathogen.vim文件拷贝到\Vim\vimfiles\autoload目录下。之后在配置文件_vimrc中加上如下代码,安装成功。以后通过pathogen安装的插件都会放在bundle目录下,不需要时删除即可。

" pathogen插件管理 
execute pathogen#infect()

 

安装配色主题

VIM原始界面朴素得就像TXT记事本,VIM内置了10多种配色方案可供选择,可以通过菜单(Edit -> ColorScheme)试用不同方案。在github上有许多漂亮的主题方案,推荐两款:solarized & molokai。以solarized为例,用两种方法来安装。

原始方法:

下载之后解压,将其中的solarized.vim文件拷贝至Vim\vimfiles\colors,然后在_vimrc中设定选用其作为默认主题,如下。其中,不同主题都有暗/亮色系之分,dark或light。

" 配色主题 
set background=dark 
colorscheme solarized

用pathogen来安装(推荐)

一个简单的方法是下载后,将解压后的文件夹拷贝到’vimfiles\bundle’目录,或者用git客户断,切到bundle目录,运行git clone命令即可。

$ cd ~/.vimfiles/bundle 
$ git clone https://github.com/altercation/vim-colors-solarized

 

之后在_vimrc的配置同上面方法一样。

去菜单工具条等

为了专注编程,不受干扰,不应该有工具条、菜单、滚动条浪费空间的元素,在_vimrc中配置如下即可去掉。

" 禁止光标闪烁 
set gcr=a:block-blinkon0

 

" 禁止显示滚动条 
set guioptions-=l 
set guioptions-=L 
set guioptions-=r 
set guioptions-=R
" 禁止显示菜单和工具条,并绑定到快捷键F2 
set guioptions-=m 
set guioptions-=T
map <silent> <F2> :if &guioptions =~# 'T' <Bar>
        \set guioptions-=T <Bar>
        \set guioptions-=m <bar>
    \else <Bar>
        \set guioptions+=T <Bar>
        \set guioptions+=m <Bar>
    \endif<CR>
全屏设置

设置全屏需要插件,在这里[下载](http://www.vim.org/scripts/script.php?script_id=2596)。下载得到的是一个zip压缩包,解压将gvimfullscreen.dll文件复制到安装目录下gvim.exe所在的文件夹,和gvim.exe同目录,打开Vim配置文件_vimrc,将其绑定到`F11`快捷键,配置如下:

 

" F11 为Vim全屏切换快捷键 
map <F11> <Esc>:call libcallnr("gvimfullscreen.dll", "ToggleFullScreen", 0)<CR>
" 在插入模式下也设置F11全屏 
imap <F11> <Esc>:call libcallnr("gvimfullscreen.dll", "ToggleFullScreen", 0)<CR>
" 启动 vim 时自动全屏 
au GUIEnter * simalt ~x

五、字体编码等
默认字体不好看,挑个自己喜欢的,前提是你的电脑得先安装好该字体。我用的是Mono

" 指定语言,由于字体名存在空格,需要用“\”进行转义,最后的12指字体大小
set guifont=DejaVu\ Sans\ Mono:h12
" 设置编码 
set encoding=utf-8
" 解决consle输出乱码 
language messages zh_CN.utf-8
" 设置文件编码检测类型及支持格式 
set fencs=utf-8,gbk,ucs-bom,gb18030,gb2312,cp936
" 指定菜单语言 
set langmenu=zh_CN.utf-8 
source $VIMRUNTIME/delmenu.vim 
source $VIMRUNTIME/menu.vim

添加辅助信息

" 总是显示状态栏 
set laststatus=2
" 显示光标当前位置 
set ruler
" 开启行号显示 
set number
" 高亮显示当前行/列 
set cursorline 
set cursorcolumn
" 高亮显示搜索结果 
set hlsearch
" 禁止折行 
set nowrap
" 开启语法高亮功能 
syntax enable
" 允许用指定语法高亮配色方案替换默认方案 
syntax on