java websocket实现聊天室 附源码

目录

1.Socket基础知识

2.socket代码实现

2.1 引入依赖

2.2 配置websocket

2.3 websocket的使用

2.4 webSocket服务端模块

2.5 前端代码

3.测试发送消息

4.websocket源码地址

5.线上nginx的websocket配置


1.Socket基础知识

Socket(套接字)用于描述IP地址和端口,是通信链的句柄,应用程序可以通过Socket向网络发出请求或者应答网络请求。

Socket是支持TCP/IP协议的网络通信的基本操作单元,是对网络通信过程中端点的抽象表示,包含了进行网络通信所必需的5种信息:连接所使用的协议、本地主机的IP地址、本地进程的协议端口、远地主机的IP地址以及远地进程的协议端口。
 

2.socket代码实现

2.1 引入依赖

我这里使用了swagger,方便调试

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--websocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <!--springboot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--swagger包-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

2.2 配置websocket

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author yt
 * @create 2023/4/20 13:45
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

2.3 websocket的使用

前端传一个用户id,将用户id和对应的session进行绑定,一对一就是客户端根据对应的用户id将消息发送给对应的session

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * https://blog.csdn.net/weixin_56079712/article/details/121602008
 *
 * @author yt
 * @create 2023/4/20 13:46
 */

@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {
    private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);


    /**
     * 记录当前在线连接数
     */
    private static AtomicInteger onlineCount = new AtomicInteger(0);


    /**
     * 用户id  对应的session
     */
    private static Map<String, Session> userIdToSession = new ConcurrentHashMap<>();
    /**
     * session  对应的用户id
     */
    private static Map<Session, String> SessionTouserId = new ConcurrentHashMap<>();


    /**
     * 创建一个数组用来存放所有需要向客户端发送消息的窗口号
     */
    private static List<String> list = new ArrayList();

    public static List<String> getList() {
        return list;
    }


    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        // 在线数加1
        onlineCount.incrementAndGet();
        userIdToSession.put(userId, session);
        SessionTouserId.put(session, userId);
        list.add(userId);
        String msg = userId + "连接成功";
        sendMessage(msg, session);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        //在线连接-1
        onlineCount.decrementAndGet();
        String userId = SessionTouserId.get(session);
        list.remove(userId);
        logger.info(userId + "断开连接,当前连接数为 " + onlineCount);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("客户端发送消息为" + message);
        sendMessage(message, session);
    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println(" 发生错误 ");
        error.printStackTrace();
    }

    /**
     * 服务端发送消息给客户端
     */
    public void sendMessage(String message, Session session) {
        try {
            logger.info("服务端给客户端[{}]发送消息[{}]", session.getId(), message);
            session.getBasicRemote().sendText(message);
        } catch (Exception e) {
            logger.error("服务端发送消息给客户端失败:{}", e);
        }
    }

    public void sendOneMessage(String message, String userId) {
        try {
            Session session = userIdToSession.get(userId);
            if (session != null && session.isOpen()) {
                logger.info("服务端给客户端[{}]发送消息[{}]", session.getId(), message);
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            logger.error("服务端发送消息给客户端失败:{}", e);
        }
    }

    public void sendAllMessage(String message) {
        try {
            if (list.size() < 1) {
                logger.info("当前在线人数为0,发送消息为" + message);
                return;
            }
            for (String userId : list) {
                Session session = userIdToSession.get(userId);
                if (session != null && session.isOpen()) {
                    logger.info("服务端给客户端[{}]发送消息[{}]", session.getId(), message);
                    session.getBasicRemote().sendText(message);
                }
            }

        } catch (Exception e) {
            logger.error("服务端发送消息给客户端失败:{}", e);
        }
    }

}

2.4 webSocket服务端模块

import com.yt.websocket.websocket.WebSocketServer;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Api(tags = "webSocket服务端模块")
@RestController
@RequestMapping("/test")
public class SendMessageController {

    @Autowired
    private WebSocketServer webSocketServer;

    @ApiOperation(value = "群发消息")
    @GetMapping("/sendMsg/{msg}")
    public String sendAllMsg(@PathVariable("msg") String msg) {
        webSocketServer.sendAllMessage(msg);
        return "群发消息【" + msg + "】发送成功";
    }

    @ApiOperation(value = "给单个用户发送消息")
    @GetMapping("/sendOneMsg/{msg}/{userId}")
    public String sendOneMsg(@PathVariable("msg") String msg, @PathVariable("userId") String userId) {
        webSocketServer.sendOneMessage(msg, userId);
        return "消息【" + msg + "】给 " + userId + "发送成功";
    }

    @ApiOperation(value = "获取在线用户")
    @GetMapping("/get/userId")
    public List getUserId() {
        return WebSocketServer.getList();
    }
}

2.5 前端代码

<!DOCTYPE html>
<meta charset="utf-8"/>
<title>WebSocket 测试 乔丹</title>
<body>
<h2>WebSocket 测试 乔丹</h2>
<HEADER class="header">
    <a class="back" ></a>
    <h3 class="tit">服务端:</h3>
</HEADER>
<div id="message">

</div>

<HEADER class="header1">
    <a class="back" ></a>
    <h3 class="tit">客户端:</h3>
</HEADER>

<div id="footer">
    <input id="text" class="my-input" type="text" />
    <button onclick="send()" >发&nbsp;送</button>
</div>

<div id="footer1">
    <br/>
    <button onclick="closeWebSocket()" >关闭websocket连接</button>
    <button onclick="openWebSocket()" >建立websocket连接</button>
</div>


<script language="javascript" type="text/javascript">
    var websocket = null;
    //判断当前浏览器是否支持WebSocket,是则创建WebSocket
    if ('WebSocket' in window) {
        console.log("浏览器支持Websocket");
        websocket = new WebSocket("ws://localhost:9091/websocket/乔丹");
    } else {
        alert('当前浏览器 Not support websocket')
    }

    //连接发生错误的回调方法
    websocket.onerror = function () {
        console.log("WebSocket连接发生错误");
        setMessageInnerHTML("WebSocket连接发生错误");
    };
    //连接成功建立的回调方法
    websocket.onopen = function () {
//	setMessageInnerHTML("WebSocket连接成功");
        console.log("WebSocket连接成功");
    }
    //接收到消息的回调方法
    websocket.onmessage = function (event) {
        if (event.data) {
            setMessageInnerHTML(event.data);
        }
        console.log(event.data);
    }
    //连接关闭的回调方法
    websocket.onclose = function () {
        console.log("WebSocket连接关闭");
    }

    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
    }

    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
    }


    // 建立连接的方法
    function openWebSocket() {
        websocket = new WebSocket("ws://localhost:8888/websocket/乔丹");
        websocket.onopen = function () {
//	setMessageInnerHTML("WebSocket连接成功");
            console.log("WebSocket连接成功");
        }
    }


    //将消息显示在网页上

    function setMessageInnerHTML(innerHTML) {

        document.getElementById('message').innerHTML += innerHTML + '<br/>';

    }


    //如果websocket连接还没断开就关闭了窗口,后台server端会抛异常。
    //所以增加监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接
    window.onbeforeunload = function () {
        closeWebSocket();
    }

</script>
</body>
<div id="output"></div>
</html>

3.测试发送消息

用户端:

服务端:

用户端成功接收到消息

 

4.websocket源码地址

https://gitee.com/yutao0730/web-socket-chat-room.git

5.线上nginx的websocket配置

共两处:在server上面配置:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

 在里面配置:

 	    #webSocket
        location   /luck/socket/test {
			proxy_pass http://127.0.0.1:8013/luck/socket/test;           
			proxy_http_version 1.1;
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection $connection_upgrade;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header X-Real-IP $remote_addr;
		}

 完整配置:

在socket的使用过程中,nginx默认60s会切断连接,如果想保持长链接需要配置:

proxy_read_timeout 86400s;

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }


        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    # webSocket配置
    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }
    server {
        listen       443 ssl;
        server_name  localhost;

        ssl_certificate      8941078_hz.icbc.cardwinner.com.pem;
        ssl_certificate_key  8941078_hz.icbc.cardwinner.com.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
            root   html;
            index  index.html index.htm;
        }
        
        #webSocket
        location   /luck/socket/test {
			proxy_pass http://127.0.0.1:8013/luck/socket/test;           
			proxy_http_version 1.1;
            #超时时间
            proxy_read_timeout 86400s;
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection $connection_upgrade;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header X-Real-IP $remote_addr;
		}

        
    }

}

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值