基于Websocket协议的即时通讯系统设计与实现

本文探讨了基于TCP和WebSocket协议的即时通信系统设计与实现,重点介绍了系统的技术架构、功能模块、网络处理及安全性设计等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录
摘  要 III
ABSTRACT IV
第一章 引言 1
1.1 即时通信系统基本概念 1
1.2 即时通信系统的发展历程 1
1.3 系统研究目的和意义 1
1.4 系统可行性分析 2
第二章 相关技术介绍 3
2.1 TCP/UDP协议 3
2.2 WebSocket协议 3
2.3 服务器模型 4
2.3.1 同步 4
2.3.2 复制进程 4
2.3.3多线程 5
2.3.4 事件驱动 5
2.4 TLS/SSL协议 5
2.5编程语言 5
2.5.1 C++ 5
2.5.2 Nodejs 6
2.6 开源库 6
2.6.1 Boost C++ Libraries 6
2.6.2 Openssl 7
2.6.3 Protobuf 7
2.6.4 Hiredis 7
2.6.5 Socket.io 7
2.7 开发相关工具 8
2.7.1 Redis 8
2.7.2 Sqlite 8
2.7.3 Nginx 8
2.7.4 Visual Studio 2013 8
2.7.5 Visio 2013 9
第三章 即时通信系统的设计 10
3.1 即时通信系统功能描述 10
3.2服务器设计 11
3.3数据库设计 11
3.4主要的消息时序图 12
3.4.1 未读消息时序图 12
3.4.2点对点私聊时序图 13
3.4.3群聊消息时序图 14
3.5网络处理设计 15
3.5.1封包 15
3.5.2拆包 16
3.6负载均衡设计 17
3.7网络安全设计 18
第四章 即时通信系统的实现 19
4.1主要类和文件说明 19
4.2系统部分截图 23
第五章 即时通信系统的测试 26
5.1测试目的和任务 26
5.2测试流程 26
5.3 功能测试用例 27
第六章 结论和展望 28
6.1 结论 28
6.2 进一步工作的方向 28
参考文献 29
致谢 30
随着网络通信技术和计算机技术的发展,人们越来越希望能够快速得发送和接收互联网消息,服务器管理员也希望减轻服务器的负担。与此同时随着互联网的发展在HTML5中提出了websocket协议,能更好的节省服务器资源和带宽并且服务器和浏览器能够双向实时通讯。同时也是学习和实践网络编程、操作系统、软件工程、数据库概论、算法等相关知识。
为了能让用户体验传统客户端和web带来的即时通信结合的超爽体验,并利用在大学所学的知识打造高性能,高并发,高可拓展性服务器。同时更是为了方便人们之间的信息交流,让用户随时的接入即时通信,提升人们生活的效率。
可行性研究(feasibility study)的目的,是弄清楚将要开发的项目是不是能够实现和值得去实现,也是在高层次上进行的一次大大简化了的需求分析与设计。即使研究的结论是不值得进行,所花费的精力也并不白费,因为他避免了一次更大的浪费[2]。
对研究中可能提出的任何一种解决方法,都要从经济、技术、运行和法律等诸多方面来考虑其可行性,最终给出明确的结论来决策是否可行。
①经济可行性。即时通讯可以不赚钱,但没有却是不行的,就像一个地方要致富,不修路是不行的道理一样。有了大量用户群,提供其他服务自然会带来大量收益。因此经济上是可行的。
②技术可行性。通过TCP/UDP协议客户端的即时通信已经有很多成功的软件;在之前服务器要推送数据到浏览器,本文转载自http://www.biyezuopin.vip/onews.asp?id=14555通过Ajax定时查询,严重浪费互联网和服务器资源。随着互联网的发展在HTML5中提出了websocket协议,能更好的节省服务器资源和带宽并达到实时通讯。通过消息队列实现不同类型服务器的互联互通。因此技术上是可行的。
③运行可行性。目前已经学习了C++语言和JavaScript语言,学习了网络编程、软件工程、操作系统、数据库原理等课程。因此运行是可行的。
④法律可行性。采用开源库openssl、protobuf、hiredis,开源数据库软件redis、代理服务器nginx。因此在法律上是可行的。
综合上述分析,新的系统是可行的。
本文主要讨论了基于TCP和Websocket协议的即时通信系统的研究和实现。研究不同协议类型服务器通过统一的服务器消息格式实现互通互联,简单的负载均衡,异步网络编程,Publish/Subscribe设计模式,实现了Web端与传统客户端之间即时通信,服务器的高性能、高并发。本系统按照软件工程思想,使用面向对象设计模式设计并封装了大量的类,函数式编程方法解决异步编程的困难,并采用多服务器协作的方式,提高了服务器的健壮性和可拓展性。 在实现的过程中学习了多种网络协议。

var redis = require('redis');
var redisOption = {
    auth_pass: "014006",
    detect_buffers: true
}
var redisDB = redis.createClient(redisOption);
var redisSub = redis.createClient(redisOption);
//选择数据库
redisDB.SELECT("1", function (err) {
    if (!err) console.log("redis init ok!");
});
redisSub.SELECT("1", function (err) {
    if (!err) console.log("redisSub init ok!");
});
//服务器套接字
var g_socketStr = null;
function Subscribe(socketStr) {
    g_socketStr = socketStr;
    redisSub.subscribe('login', redis.print);
    redisSub.subscribe('unlogin', redis.print);
    redisSub.subscribe('online', redis.print);
    redisSub.subscribe('offline', redis.print);
    redisSub.subscribe('peerMsg:' + socketStr, redis.print);
    redisSub.subscribe('groupMsg:' + socketStr, redis.print);
}
redisSub.on('message', function (channel, message) {
    if ('online' == channel) {
        console.log("%s 上线了", message);
    }
    else if ('offline' == channel) {
        console.log("%s 离线了", message);
    }
    else if (channel == 'groupMsg:' + g_socketStr) {
        //读出二进制数据后转换为JSON数据
        //{ userId:[], msgData:''}
        var outObj = ServerGroupMsg.parse(message);
        var outMsg = MsgData.parse(outObj.msgData);
        //将GBK码转为UTF-8
        outObj.msgData = iconv.decode(outObj.msgData, 'gbk').toString();
        outObj.type = 'group';
        for (var i = 0; i < outObj.userId.length; i++) {
            var toSocket = userIdMap.get(outObj.userId[i]);
            if (undefined != toSocket) {
                console.log("%s %s", channel, JSON.stringify(outObj));
                toSocket.emit('group message', outObj);
            }
        }
    }
    else if (channel == 'peerMsg:' + g_socketStr) {
        //读出二进制数据后转换为JSON数据
        var outObj = MsgData.parse(message);
        //将GBK码转为UTF-8
        outObj.msgData = iconv.decode(outObj.msgData, 'gbk').toString();
        outObj.type = 'peer';
        var toSocket = userIdMap.get(outObj.toId);
        if (undefined != toSocket) {
            console.log("%s %s", channel, JSON.stringify(outObj));
            toSocket.emit('peer message', outObj);
        }
    }
});
//-------------------------------------protobuf-----------------------------
var promise = require('promise');
var fs = require('fs');
var iconv = require('iconv-lite');
var Schema = require('protobuf').Schema;
// "schema" contains all message types defined in buftest.proto|desc.
var schema = new Schema(fs.readFileSync('nodeJSON.desc'));
var MsgData = schema['nodeJSON.MsgData'];
var ServerGroupMsg = schema['nodeJSON.serverGroupMsg'];
/
var http = require('http');
var fs = require('fs');
var httpServer = http.createServer(function (req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello World\n');
});
function handler(req, res) {
    fs.readFile(__dirname + '/index.html',
  function (err, data) {
        if (err) {
            res.writeHead(500);
            return res.end('Error loading index.html');
        }
        
        res.writeHead(200);
        res.end(data);
    });
}
var io = require('socket.io')(httpServer);
//服务器监听端口
httpServer.listen(6181, "127.0.0.1", function () {
    console.log('server run at %s %s', httpServer.address().address, httpServer.address().port);
    Subscribe(httpServer.address().address + ':' + httpServer.address().port);
});

///socket.io
//用户ID对应socket  [userId, socket]
var userIdMap = new Map();
//socket对应用户ID  [socket, userId]
var socketMap = new Map();
//私聊
var chat = io.on('connection', function (socket) {
    socket.emit('a message', {
        'message': 'connection'
    });

    socket.on('peer message', function (data) {
        if (undefined != data.toId) {
            //找到对应的Socket
            var toSocket = userIdMap[data.toId];
            console.log(data);
            //判断对方是否在本服务器
            if (undefined != toSocket) {
                toSocket.emit('peer message', data);
            }
            else {
                data.type = 'CHAT_TEXT';
                redisDB.get('online:' + data.toId, function (err, reply) {
                    if (!err && null != reply) {
                        console.log('用户在服务器' , reply);
                        //发布一条消息
                        data.msgData = iconv.encode(data.msgData, 'gbk');
                        redisDB.publish('peerMsg:' + reply, MsgData.serialize(data));

                    }
                    else {
                        //如果用户未登陆;将消息插入到未读消息列表;	
                        var buf = MsgData.serialize(data);
                        redisDB.lpush('unreadMsg:' + data.toId, buf , function (err, reply) {
                            if (!err && null != reply) {
                                //只保存99条消息;如果超过截取后面99条;
                                if (parseInt(reply) > 99) {
                                    redisDB.ltrim('unreadMsg:' + data.toId, 0, 99, redis.print);
                                }
                            }
                        });
                    }
                });
            }
        }
    });

    socket.on('group message', function (data) {
        if (undefined != data.toId) {
            //给所有服务器Publish消息,并存入数据库;
            data.type = 'GROUPCHAT_TEXT';
            console.log(data);
            //处理群在线的用户消息
            redisDB.sort('onlineGroup:' + data.toId, 'get', '#', 'get', 'online:*', function (err, reply) {
                
                if (!err && null != reply) {
                    //[userid, serverSocket]
                    //javascript的Map简化了很多操作
                    //[serverSocket => [userId, userId]];
                    var dataMap = new Map();
                    for (var i = 0; i < reply.length / 2; i++) {
                        //判断是否在本服务器上
                        if (g_socketStr != reply[2 * i + 1]) {
                            var value = dataMap.get(reply[2 * i]);
                            if (undefined == value) {
                                dataMap.set(reply[2 * i], [reply[2 * i + 1]]);
                            }
                            else {
                                value.push(reply[2 * i + 1]);
                                dataMap.set(reply[2 * i], value);
                            }
                        }
                        else {
                            //如果在本服务器上直接发送
                            var userSocket = userIdMap.get(reply[2 * i]);
                            if (undefined != userSocket) {
                                data.type = 'group'
                                userSocket.emit('group message', data);
                            }
                        }
                    }
                    //对其他服务器Publish消息
                    if (dataMap.size >= 1) {
                        dataMap.forEach(function (value, key, map) {
                            var temp = data;
                            temp.msgData = iconv.encode(data.msgData, 'gbk');
                            var groupMsg = {};
                            groupMsg.userId = value;
                            groupMsg.msgData = MsgData.serialize(temp);
                            //发布一条消息
                            redisDB.publish('groupMsg:' + key, ServerGroupMsg.serialize(data));
                            console.log('Publish groupMsg:' + key);
                        });
                    }
                }
            });
            //处理群未在线的用户消息
            //从左边入列表存储二进制数据;
            redisDB.lpush('groupMsg:' + data.toId, MsgData.serialize(data), function (err, reply) {
                //只保存99条消息;如果超过截取后面99条;
                if (!err && null != reply) {
                    if (parseInt(reply) > 99) {
                        redisDB.ltrim('groupMsg:' + data.toId, 0, 99, redis.print);
                    }
                }
            });
            //返回一个集合的全部成员,该集合是所有给定集合之间的差集。;第一个集合与其他集合的差集;
            redisDB.sdiff('groupMember:' + data.toId, 'onlineGroup:' + data.toId, function (err, reply) {
                if (!err && null != reply) {
                    //reply [userId]
                    //pipeline
                    //Key unreadNumber : userid : groupid ;+ 1;方便查询数据;
                    // start a separate multi command queue 
                    var multi = redisDB.multi();
                    for (var i = 0; i < reply.length; i++) {
                        var key = 'unreadNumber:';
                        key += reply[i];
                        key += ':';
                        key += data.toId;
                        multi.incr(key);
                    }
                    multi.exec(function (err, replies) {
                        console.log(replies);
                    });
                }
            });
        }
    });
    //握手连接
    socket.on('handleShake', function (data) {
        //将用户ID与Socket映射
        if (undefined != data.userId) {
            userIdMap.set(data.userId, socket);
            socketMap.set(socket, data.userId);
            //添加online:userid键值对;
            redisDB.set('online:' + data.userId, g_socketStr);
            //Publish 在线消息;
            redisDB.publish('online', data.userId);
            //找到用户所有的群;
            redisDB.smembers('groupSet:' + data.userId, function (err, reply) {
                if (!err && null != reply) {
                    //对所有的群的onlineGroup:groupid添加成员;
                    for (var i = 0; i < reply.length; i++) {
                        redisDB.sadd('onlineGroup:' + reply[i], data.userId, redis.print);
                    }
                }
            });
            console.log("%s 已经连接", data.userId);
        }
    });
    socket.on('disconnect', function () {
        var nowUserId = socketMap.get(socket);
        if (undefined != nowUserId) {
            console.log('%s disconnected', nowUserId);
            //Publish 离线消息;
            redisDB.publish('offline', nowUserId);
            //删除online:userid键值对;
            redisDB.del("online:" + nowUserId);
            //找到用户所有的群;
            redisDB.smembers('groupSet:' + nowUserId, function (err, reply) {
                if (!err && null != reply) {
                    //对所有的群的onlineGroup:groupid移除成员;
                    for (var i = 0; i < reply.length; i++) {
                        redisDB.srem('onlineGroup:' + reply[i], nowUserId, redis.print);
                    }
                }
            });
            //删除用户ID与Socket映射
            socketMap.delete(socket);
            userIdMap.delete(nowUserId);
        }
    });
});

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值