基于websocket实现的聊天室


WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。

一、websocket工作原理

	威龙解决客户端和服务器的实时通讯而产生的技术。本质是通过**HTTP/HTTPS协议进行握手后,创建的一个用于,交换数据的TCP连接,此后客户端与服务器通过TCP连接进行实时通讯。**
	
	WebSocket protocol 。协议:

	现很多网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客户端的浏览器。这种传统的HTTP request 的模式带来很明显的缺点 – 浏览器需要不断的向服务器发出请求,然而HTTP request 的header是非常长的,里面包含的有用数据可能只是一个很小的值,这样会占用很多的带宽。

	而比较新的技术去做轮询的效果是Comet – 用了AJAX。但这种技术虽然可达到全双工通信,但依然需要发出请求。

	在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:

	1. Header

	互相沟通的Header是很小的-大概只有 2 Bytes

	2. Server Push

	服务器的推送,服务器不再被动的接收到浏览器的request之后才返回数据,而是在有新数据时就主动推送给浏览器。

二、创建步骤

	WebSocket握手
	WebSocket的握手过程就是建立WebSocket连接的过程,握手是借用了HTTP的。
	
	
	总体步骤大致如下:
	
	Step 1: 建立TCP连接(这一步是一切的基础,HTTP也一样)。
	
	Step 2: 浏览器借助一个WebSocket 客户端对象,连接类似 ws://yourdomain:port/path或wss://yourdomain:port/path的URL,WebSocket 客户端对象会自动解析并识别为要建立WebSocket 连接,从而发送HTTP Get 请求,并为请求自动添加一些供WebSocket使用的HTTP Headers字段。注意,连接头变成了ws,而不是一般的http,这是协议的意思,ws代表websocket。
	
	Step 3: Server收到HTTP请求后,会把Step 1的TCP连接的应用层协议从HTTP转变为WebSocket。至此,HTTP部分就退出舞台了,WebSocket开始接管一切。

三、WebSocket握手报文(HTTP)分析:

请求
GET /chat HTTP/1.1
Host: 127.0.0.1:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: JkTukkPc4Rha+nIkbYhEkQ==
Sec-WebSocket-Version: 13
Origin: http://localhost:8080

响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: RD69G0BS8RPH/GbY6rBsZI75pjk=
Server:Apache-Coyote/1.1
Date:Sat, 17 Jun 2017 15:35:41 GMT

以上报文和一般的HTTP有很多不同的地方:

1、请求和响应都有Upgrade: websocket和Connection: Upgrade,这是协议转换的意思,也就是请求时告诉服务器,本次连接需要的是websocket通信协议,而不是一般的HTTP,响应也带有该响应头表示转换成功。

一般响应成功的响应头是 HTTP/1.1 200 OK、HTTP/1.1 404 NOT FOUND等,此处是HTTP/1.1 101 Switching Protocols,同样101也是HTTP的一种响应状态,表示协议转换成功。

2、Sec-WebSocket-Key: JkTukkPc4Rha+nIkbYhEkQ==和Sec-WebSocket-Accept: RD69G0BS8RPH/GbY6rBsZI75pjk=

这是websocket握手的检验,也即测试服务器是否提供websocket服务,如果提供,Sec-WebSocket-Key和Sec-WebSocket-Accept必然是有对应关系的,具体的关系如下:

Sec-WebSocket-Key拼接上258EAFA5-E914-47DA-95CA-C5AB0DC85B11(这串magic string是写在协议里面的,所有的websocket底层实现都要拼接该串)

得到对拼接结果

JkTukkPc4Rha+nIkbYhEkQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11

使用SHA-1(160数位)进行哈希操作,对哈希后的结果base64 进行编码,就可以得到结果
RD69G0BS8RPH/GbY6rBsZI75pjk=
以上操作不仅仅是服务端要做,客户端也同样做了该操作,然后将自己计算得到的结果和服务器传来的Sec-WebSocket-Accept作比较,如果相等则没问题,握手成功,如果不一致,则握手失败。

3、Origin用于防止未授权的跨域脚本攻击,服务器可以从Origin决定是否接受该WebSocket连接,所有来源于浏览器的请求都会带上该请求头。如,已授权站点www.mydomain.com,并且产生cookie,用户没关闭授权站点的时候点击另外的www.hack.com下面的链接,则该连接可以带着授权的cookie去做很多事。如果有了Origin,并且在服务端setAllowedOrigins(“www.mydomain.com”)则可以避免。

4、Sec-WebSocket-Version: 13 ,表明了websocket的版本,以前各厂商混战的时候什么样的版本都有,还好现在已经统一了,用13版即可。

四、HTML的WEBSOCKET

# 1. 创建一个WebSocket连接
var ws = new WebSocket("ws://127.0.0.1:8001/echo")

# 2. ws.onopen方法(当 ws 连接建立时触发)
ws.onopen = function () {
    console.log('WebSocket open');   //成功连接上Websocket
};

# 3. 当 ws 连接接收到数据时触发
ws.onmessage = function (e) {
    console.log('message: ' + e.data);  //打印出服务端返回过来的数据
};

# 4. 当 ws 连接发生通信错误时触发
ws.onerror = function () {
    console.log('连接出错');
};

# 5. 当连接关闭时触发
ws.onclose = function(){
    console.log('连接关闭')
}

代码::::

有一些tomcat内部没有 javax.websocket 包得导入
目录结构

login.html----->/login servlet ----->chat.jsp ---->
var socket = new WebSocket(“ws://” + window.location.host + “/Demo8/ChatServer”);
–>@ServerEndpoint(value = “/ChatServer”, configurator = GetHttpSessionConfigurator.class)
就是这么回事so easy

chatServer.java

package web;

import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

/**
 * @Author 911
 * @create 2021/02/08 17:19
 */
@ServerEndpoint(value = "/ChatServer", configurator = GetHttpSessionConfigurator.class)
public class ChatServer {
    private String nickname;
    private Session session;
    private HttpSession httpSession;

    public String getNickname() {
        return nickname;
    }

    public Session getSession() {
        return session;
    }

    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {

        this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        this.nickname = (String) httpSession.getAttribute("userid");
        InitServlet.getOnlineUsers().add(this);

        this.session = session;
        InitServlet.getOnlineUserMap().put(this.nickname, this);
        String message = String.format("* %s %s", "用户" + session.getId(), "参加聊天");
        System.out.println(message);
        broadcast(message);
    }

    @OnClose
    public void onClose() {
        System.out.println("退出     sessionid:" + this.session.getId());
        InitServlet.getOnlineUsers().stream().forEach(System.out::println);
        System.out.println("-------------------------------------remove(this.session);-----------------------------------------------------------");
        InitServlet.getOnlineUsers().remove(this);
        InitServlet.getOnlineUsers().stream().forEach(System.out::println);

        InitServlet.getOnlineUserMap().remove(this.nickname);
    }

    @OnError
    public void onError(Throwable t) {
        t.printStackTrace();
        System.out.println("聊天室出错:" + t.toString());
    }

    @OnMessage
    public void onMessage(String content) throws IOException {
        String[] contentSplit = content.split("\\|");
        String message = contentSplit[0];
        String sendTypeValue = contentSplit[1];
        String sendWhoValue = contentSplit[2];
        if ("1".equals(sendTypeValue)) {
            broadcast(message);
        } else {
            sayto(sendWhoValue, message);
        }
    }

    /**
     * 私聊
     * @param sendWhoValue
     * @param message
     * @throws IOException
     */
    private void sayto(String sendWhoValue, String message) throws IOException {

        System.out.println(sendWhoValue);
        System.out.println(message);

        ChatServer chatServer = InitServlet.getOnlineUserMap().get(sendWhoValue);
        chatServer.session.getBasicRemote().sendText(message);
    }

    /**
     * 广播消息
     *
     * @param message
     */
    private void broadcast(String message) {
        for (ChatServer client : InitServlet.getOnlineUsers()) {

            try {
                synchronized (client) {
                    client.session.getBasicRemote().sendText(message);
                }
            } catch (IOException e) {
                try {
//                    e.printStackTrace();

                    InitServlet.getOnlineUsers().stream().forEach(System.out::println);
                    client.session.close();
                    InitServlet.getOnlineUsers().remove(client);


                } catch (IOException e1) {
                    InitServlet.getOnlineUsers().stream().forEach(System.out::println);
                    System.out.println("e1   错了");
                    e.printStackTrace();

                }
            }

        }
    }
}

宝贝,代码不全。有需要找我,不过上面那就是核心代码了,

	参考:科普文章
	[very nice    --------->>>>> websocket实现聊天室应用,包括文字和图片上传](https://blog.csdn.net/a4227139/article/details/75041160?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522161283280216780271549014%252522%25252C%252522scm%252522%25253A%25252220140713.130102334..%252522%25257D&request_id=161283280216780271549014&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~baidu_landing_v2~default-6-75041160.pc_v1_rank_blog_v1&utm_term=websocket%25E8%2581%258A%25E5%25A4%25A9%25E5%25AE%25A4)
	[和上面一样](https://www.zhihu.com/question/20215561)
	https://www.cnblogs.com/xiaonq/p/12238651.html
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值