多人在线谈天服务器

打印 上一主题 下一主题

主题 555|帖子 555|积分 1665


欢迎诸位来阅读在下的博文~
在这里,在下会不定期发表一些浅薄的知识和经验,望诸位能与在下多多交换,共勉共勉!
山河如画,客心如若,欢迎到访,一展风采

  
一、配景

当今即时通信软件繁多,此中著名的就有QQ、微信、MSN Messenger、NET Messenger Service、ICQ等等。其主要用途是传递文字信息与传输文件。使用socket创建通信渠道,多线程实现多台盘算机同时进行信息的传递。通过简朴的注册登录后,即可在网络中乐成进行即时谈天。
即时通信(Instant Message,IM),这是一种可以让使用者在网络上创建某种私人谈天室的实时通信服务。大部分的即时通信服务都提供了状态信息的特征:显示联结人名单、联结人是否在线和能否与联结人交谈。
通常IM服务会在使用者清单(类似电话薄)上的某个人连上IM时,发出信息通知使用者,使用者可据此与此人通过网络开始进行实时的IM文字通信。除了文字外,在频宽充足的前提下,大部分IM服务事实上也提供视频通信能力。实时传讯与电子邮件最大的不同在于不消期待,不需要每隔两分钟就按一次“传送与吸收”,只要两个人都在线,就像多媒体电话一样传送文字、文件、声音、图像给对方。
二、体系平台的选择

1.应用体系平台模式的选择

所谓平台模式或盘算布局是指应用体系的体系布局,简朴来说就是体系的层次、模块布局。常见的四种模式如下:
(1)主机——终端模式
(2)单机模式
(3)客户机/服务器模式(C/S模式)
(4)欣赏器/n层服务器模式(B/nS模式)
考虑到的场景应用是要在公司或某单位内部创建起服务器,还要再每台盘算机里安装相关的通信体系(客户端),所以我们选择研究的体系模式为C/S模式。
2.C/S模式介绍

C/S(Client/Server)模式,即客户端/服务器模式,是一种网络盘算模子,它将使命和服务分为两个主要部分:客户端(Client)和服务器端(Server)。在这种模子中,客户端负责发送请求,服务器端则负责处理这些请求并返回相应。以下是C/S模式的基本介绍:
客户端(Client)

  • 脚色:客户端是请求服务的发起者,通常是一个用户界面,允许用户请求服务器提供的数据或服务。
  • 功能

    • 发送请求:客户端向服务器发送请求,请求可以包括数据查询、文件传输、盘算使命等。
    • 吸收相应:客户端吸收服务器处理请求后返回的相应,并将结果显示给用户。

  • 特点

    • 可以是桌面应用步伐、移动应用步伐或Web欣赏器。
    • 通常具有用户友好的界面。
    • 大概需要安装特定的客户端软件。

服务器端(Server)

  • 脚色:服务器端是提供服务的实体,负责处理客户端的请求,实利用命,并返回结果。
  • 功能

    • 处理请求:服务器吸收客户端的请求,根据请求范例实行相应的操纵。
    • 数据存储:服务器通常负责存储和管理数据。
    • 相应客户端:服务器将处理结果返回给客户端。

  • 特点

    • 通常运行在强大的硬件上,以支持多个客户端的请求。
    • 可以是Web服务器、数据库服务器、文件服务器等。
    • 需要持续运行,保证服务的可用性。

C/S模式的优点

  • 分布式盘算:将盘算使命分布在客户端和服务器端,可以充实使用网络资源。
  • 易于维护:服务器端集中管理数据和服务,便于维护和更新。
  • 扩展性:可以轻松地扩展服务器端以支持更多的客户端。
  • 安全性:可以在服务器端实行安全策略,保护数据安全。
C/S模式的缺点

  • 依靠服务器:客户端的功能依靠于服务器端的可用性。
  • 网络通信:C/S模式依靠于网络通信,网络延迟大概会影响用户体验。
  • 本钱:需要投资于服务器硬件和软件,以及维护本钱。
C/S模式广泛应用于各种网络应用中,如Web服务、电子邮件、数据库应用等。随着技术的发展,C/S模式也在不停演变,例如在云盘算和移动盘算范畴,出现了新的变体和优化。
3.数据库体系的选择

现在可以使用的数据库有很多种,包括但不限于:MySQL, DB2, Informix, Qracle和SQL Server。基于满足需求。价格和技术三方面的考虑,本体系在分析开发过程中接纳MySQL作为数据库体系。
三、体系需求分析

1.即时消息的一样平常需求

即时消息的一样平常需求包括格式需求、可靠性需求和性能需求。


  • 格式需求
    (1)所有实体必须至少使用一种消息格式。
    (2)一样平常即时消息格式必须定义发信者和即时收件箱的标识。
    (3)一样平常即时消息格式必须包罗一个让吸收者可以回消息的地点。
    (4)一样平常即时消息格式应该包罗其它通信方法和联系地点,例如电话号码、邮件地点等。
    (5)一样平常即时消息格式必须允许对信息有用负载编码和鉴别(非ASCII内容)。
    (6)一样平常即时消息格式必须反映当前最好的国际化实践。
    (7)一样平常即时消息格式必须反映当前最好的可用性实践。
    (8)必须存在方法,在扩展一样平常即时消息格式时,不影响原有的域。
    (9)必须提供扩展和注册即时消息格式的模式的机制。
  • 可靠性需求
    协议必须存在机制,保证实时消息乐成投递,或者投递失败时发信者得到充足的信息。
  • 性能需求
    (1)即时消息的传输必须充足敏捷。
    (2)即时消息的内容必须充足丰富。
    (3)即时消息的长度只管充足长。
2.即时消息的协议需求

协议是一系列的步骤,它包括双方或者多方,设计它的目的是要完成一项使命。即时通信协议,加入的双方或者多方是即时通信的实体。协议必须是双方或者多方加入的,一方单独完成的不算协议。在协议操纵的过程中,双方必须交换信息,包括控制信息、状态信息等。这些信息的格式必须是协议加入方同意并且遵照的。好的协议要求清晰完整,每一步必须有明确的定义,并且不会引出误解;对每种大概的情况必须规定具体的动作。
3.即时消息的安全需求

A发送即时消息M给B,有以下几种情况和相关需求:
(1)假如无法发送,A必须接到确认。
(2)假如M被投递了,B只能接受一次M。
(3)协议必须为B提供方法查抄A是否发送了这条消息。
(4)协议必须允许B使用另一条即时消息来回复信息。
(5)协议不能袒露A的IP地点。
(6)协议必须为A提供方法保证没有其他个体C可以看到M。
(7)协议必须为A提供方法保证没有其他个体C可以篡改M。
(8)协议必须为B提供方法鉴别是否发生篡改。
(9)B必须能够阅读M,B可以制止A发送消息给他。
(10)协议必须允许A使用现在的数字签名标准对信息进行签名。
4.即时消息的加密和鉴别

(1)协议必须提供方法保证通知和即时消息的置信度,确保信息未被监听或者破坏。
(2)协议必须提供方法保证通知和即时消息的置信度,确保信息未被重排序或者回放。
(3)协议必须提供方法保证通知和即时消息被正确的实体阅读。
(4)协议必须允许客户本身使用方法确保信息不被截获、不被重放和解密。
5.即时消息的注册需求

(1)即时通讯体系拥有多个账户,允许多个用户注册。
(2)一个用户可以注册多个ID。
(3)注册所使用的账号范例为字母ID。
6.即时消息的通信需求

(1)用户可以传输文本消息。
(2)用户可以传输RTF格式消息。
(3)用户可以传输多个文件/文件夹。
(4)用户可以加密/解密信息等。
四、体系总体设计

该体系的命名为MyICQ,现在对该体系接纳客户机服务器的模式来进行总体设计,它是一个3层的C/S布局:数据库服务器–>应用步伐服务器–>应用步伐客户端,其分层机构如图所示:



  • 客户层也叫应用表示层,即我们所说的客户端,这是应用步伐的用户接口部分。为即时通信工具设计一个客户层有很多优点,这是因为客户层担负着用户与应用之间的对话功能。它用于查抄用户的输入数据,显示应用的输出数据。为了使用户能直接进行操纵,客户层需要使用图形用户接口。假如通信用户变动,体系只需要改写显示控制和数据查抄步伐就可以了,而不会影响其它两层。数据查抄的内容限于数据的形势和值的范围,不包括有关业务本身的处理逻辑。
  • 服务层又叫功能层,相当于应用层的本体,它是将具体业务处理逻辑编入步伐中。例如,用户需要查抄数据,体系设法将有关见所要求的信息一次性的传送给功能层;而用户登录后,谈天登录信息是由功能层处理过的检索结果数据,他也是一次性传送给功能层的。在应用设计中,必须制止在表示层和功能层之间进行多次的数据交换,这就需要尽大概一次性的业务处理,达到优化整体设计的目的。
  • 数据层就是DBMS,本体系使用了MySQL数据库服务器来管理数据。MySQL能敏捷实行大量数据的更新和检索,因此,从功能层传送到数据层的“要求”一样平常都使用SQL语言。
五、即时通信体系的实行原理

IM的工作方式如下:用户登录IM通信服务器,获取一个自创建的汗青交换对象列表,然后自身标记位在线状态,当好友列表中的某人在任何时间登岸上线并试图通过盘算机联系用户时,IM体系会发一个消息提示该用户,然后用户能与此人创建一个谈天会话通道进行各种消息交换。
IM的基本技术原理

(1)用户A输入本身的用户名和密码登录IM服务器,服务器通过读取用户数据库来查验用户身份。假如查验通过,登录用户A的IP地点、IM客户端软件的版本号及使用的TCP/UDP端口号,然后返回用户A登录乐成的标记,此时用户A在IM体系中为在线状态。
(2)根据用户A存储在IM服务器上的好友列表,服务器将用户A在线等相关信息发送给同时在线的IM好友的PC,这些信息包括在线状态、IP地点、IM客户端使用的TCP端口号等,IM好友的客户端收到此信息后将在客户端软件的界面上显示。
(3)IM服务器把用户A存储在服务器上的好友列表及相关信息回送到其客户端,这些信息包括在线状态、IP地点、IM客户端使用的TCP端口号信息,用户A的IM客户端收到后将显示这些好友列表及其在线状态。
IM通信方式


  • 在线直接通信
  • 在线署理通信
  • 离线署理通信
  • 扩展方式通信
六、功能模块的划分

模块划分


服务器端多线程



  • 服务器
    服务器端需要和多个客户端同时进行通信,简朴来说这就是服务器段端的多线程。假如服务器发现一个新的客户端并与之连线,则马上新建一个线程与该客户端进行通信。用多线程的利益在于可以同时处理多个通信毗连,不会出现由于数据排队等候而发生延迟或者丢失等问题,可以很好地使用体系的性能。
    服务器为每一个毗连着的客户创建一个线程,为了同时相应多个客户端,需要设计一个主线程来启动服务器端。主线程与历程布局类似,他在得到新毗连时生成一个线程来处理这个毗连。线程调理的速度很快、占用资源少,可共享历程空间中的数据,因此服务器的相应速度较快,且I/O吞吐量较大。
  • 客户端
    客户端能够完成信息的接受和发送操纵,这与服务器的多线程概念不同,他可以接纳循环等候的方法来实现客户端。使用循环等候的方式,客户端首先接受用户输入的内容并将其发送到服务器,然后接受来自服务器的信息,将其返回给客户端的用户。
七、数据库设计

MySQL是一个真正的多用户、多线程SQL数据库服务器。它是以客户机/服务器布局实现的,由一个服务器守护步伐mysqld以及很多不同的客户步伐和库构成。它能够快捷、有用和安全地处理大量的数据。相对于Qracle等数据库来说,MySQL的使用非常简朴。
数据库脚本设计

  1. DROP DATABASE IF EXISTS chatdb;
  2. create database chatdb default character set utf8 collate utf8_bin;
  3. flush privileges;
  4. use chatdb;
  5. SET NAMES utf8mb4;
  6. SET FOREIGN_KEY_CHECKS = 0;
  7. SET FOREIGN_KEY_CHECKS=0;
  8. -- ----------------------------
  9. -- Table structure for qqnum
  10. -- ----------------------------
  11. DROP TABLE IF EXISTS `qqnum`;
  12. CREATE TABLE `qqnum` (
  13.   `id` int(11) NOT NULL AUTO_INCREMENT,
  14.   `name` varchar(50) DEFAULT NULL,
  15.   PRIMARY KEY (`id`)
  16. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码
验证


八、服务器端设计

客户端和服务器是TCP毗连并交互的。服务器端的主要功能如下:
(1)接受客户端用户的注册,然后把注册信息生存到数据库表中。
(2)接受客户端用户的登录,用户登录乐成后,就可以在谈天室里面谈天了。
该谈天服务器接纳select通信模子,目前没有使用到线程池,假如以后并发需求大了,很容易可以扩展到“线程池+select模子”的方式。服务器端收到客户端毗连后,就开始等候客户端的要求,具体要求是通过客户端发来的命令来实现的,具体命令如下:
  1. #define CL_CMD_REG 'r'  //客户端请求注册命令
  2. #define CL_CMD_LOGIN 'l'        //客户端请求登录命令
  3. #define CL_CMD_CHAT 'c'                //客户端请求聊天命令
复制代码
这几个命令号服务器和客户端必须同等。命令号是包罗在通信协议中的,通信协议是服务器端和客户端相互理解对方要求的手段。这里的协议设计得比力简朴,但也可以满足交互需求。
客户端发送给服务器端的协议:
命令号,参数(字符串,长度不定) 比如:“r,Tom” 表示客户端要求注册,用户名是Tom。
服务器端发送给客户端的协议:
命令号,返回结果(字符串,长度不定) 并发谈天服务器的具体设计

以下是服务器的核心代码:
  1.     while(1){
  2.         rset = allset;
  3.         nready = select(maxfd+1, &rset, NULL, NULL, NULL);
  4.         if(nready < 0)
  5.             puts("select error");
  6.             
  7.         // 一、listenfd是否在rset集合中,处理acception事件
  8.         if(FD_ISSET(listenfd, &rset)){
  9.             cliaddr_len = sizeof(cliaddr);
  10.             // accept返回通信套接字,当前非阻塞,因为select已经发生读写事件
  11.             connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
  12.             printf("received from %s at PORT %d\n",
  13.                     inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
  14.                     ntohs(cliaddr.sin_port));
  15.             for(i = 0; i < FD_SETSIZE; i++){
  16.                 if(client[i] < 0){
  17.                     client[i] = connfd; //保存accept返回的通信套接字connfd存到client[]里
  18.                     break;
  19.                 }
  20.                
  21.             }
  22.             if(i == FD_SETSIZE){
  23.                 fputs("too many clients\n", stderr);
  24.                 exit(1);
  25.             }
  26.             FD_SET(connfd, &allset); //将connfd加入allset集合
  27.             //更新最大文件描述符数
  28.             if(connfd > maxfd)
  29.                 maxfd = connfd;
  30.             if(i > maxi)
  31.                 maxi = i; //更新client[]最大下标值
  32.             if(--nready == 0){
  33.                 continue;
  34.             }
  35.         }
  36.         // 二、处理其他客户端的读写事件
  37.         for(i = 0; i<=maxi; i++){
  38.             // 清空buf
  39.             bzero(buf, MAXLINE);
  40.             // 检查clients 哪个有数据就绪
  41.             if((sockfd = client[i]) < 0)
  42.                 continue;
  43.             // 检查sockfd是否在rset集合中
  44.             if(FD_ISSET(sockfd, &rset)){
  45.                 // 接收客户端数据,不用阻塞立即读取,因为select已经发生读写事件
  46.                 if((n = read(sockfd, buf, MAXLINE)) == 0){
  47.                     close(sockfd); //关闭客户端套接字
  48.                     FD_CLR(sockfd, &allset);
  49.                     client[i] = -1;
  50.                 }
  51.                 else{
  52.                     // 处理客户端数据
  53.                     char code = buf[0];
  54.                     switch (code)
  55.                     {
  56.                     // 注册命令处理
  57.                     case CL_CMD_REG:
  58.                         if(1 != countChar(buf, ',')){
  59.                             puts("invalid protocol");
  60.                             break;
  61.                         }
  62.                         GetName(buf, szName);
  63.                         //判断名字是否重复
  64.                         if(IsExist(szName)){
  65.                             sprintf(repBuf,"r,exist");
  66.                         }
  67.                         else{
  68.                             insert(szName);
  69.                             showTable();
  70.                             sprintf(repBuf, "r,ok");
  71.                             printf("reg ok,%s\n",szName);
  72.                         }
  73.                         write(sockfd, repBuf, strlen(repBuf));
  74.                         break;
  75.                     
  76.                     case CL_CMD_LOG:
  77.                         if(1 != countChar(buf, ',')){
  78.                             puts("invalid protocol");
  79.                             break;
  80.                         }
  81.                         GetName(buf, szName);
  82.                         //判断是否注册过
  83.                         if(IsExist(szName)){
  84.                             sprintf(repBuf,"l,ok");
  85.                             printf("login ok,%s\n",szName);
  86.                         }
  87.                         else sprintf(repBuf,"l,notexist");
  88.                         write(sockfd, repBuf, strlen(repBuf));
  89.                         break;
  90.                     case CL_CMD_CHAT:
  91.                         puts("send all");
  92.                         // 群发
  93.                         for(i=0;i<=maxi;i++)
  94.                             if(client[i]!=-1)
  95.                                 write(client[i], buf+2, n); //去掉命令头,写回客户端
  96.                         break;
  97.                     }
  98.                 }
  99.                 if(--nready == 0)
  100.                     break;
  101.             }
  102.         }
  103.     }
  104.     close(listenfd);
  105.     return 0;
  106. }
复制代码
九、客户端具体设计

在客户端设计上,考虑到平台的适应性和便捷性,可以使用Visual Studio 或者 Qt开发。综合利弊,本人打算使用Qt来实现一个友好简便的人机界面。我们整套体系的通信是在Linux和Windows之间进行的,这也是常见的应用场景。在企业一线开发中,客户端险些没有运行在Linux下的。
谈天客户端主要功能:
(1)提供注册界面,供用户输入注册信息,然后把注册信息以TCP方式发送给服务器进行注册登记(实在在服务器端就是写入数据库)。
(2)注册乐成后,提供登录界面,让用户输入登录信息进行登录。登录时主要输入用户名,并以TCP方式发送给服务器端,服务器端查抄用户名是否存在后,将反馈结果发送给客户端。
(3)用户登录乐成后,就可以发送谈天信息,所有在线的人都可以看到该谈天信息。
以下是具体的代码:
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. MainWindow::MainWindow(QWidget *parent)
  4.     : QMainWindow(parent)
  5.     , ui(new Ui::MainWindow)
  6. {
  7.     ui->setupUi(this);
  8.     // 网络配置
  9.     qDebug() << "Connecting to server...";
  10.     tcpSocket = new QTcpSocket(this);
  11.     tcpSocket->connectToHost(host, port);
  12.     qDebug() << "1111";
  13.     if (!tcpSocket->waitForConnected(3000)) {
  14.         qDebug() << "Error: " << tcpSocket->errorString();
  15.     }
  16.     // 连接相关信号和槽
  17.     connect(tcpSocket, &QTcpSocket::connected, this, &MainWindow::onConnected);
  18.     connect(tcpSocket, &QTcpSocket::disconnected, this, &MainWindow::onDisconnected);
  19.     connect(tcpSocket, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);
  20.     connect(&myform, &Form::sendMessage, this, &MainWindow::onRecvMessage, Qt::UniqueConnection);
  21.     connect(this, &MainWindow::send, &myform, &Form::recv);
  22. }
  23. MainWindow::~MainWindow()
  24. {
  25.     delete ui;
  26. }
  27. void MainWindow::sendMessage(const QString &message)
  28. {
  29.     if (tcpSocket->state() == QAbstractSocket::ConnectedState) {
  30.         tcpSocket->write(message.toUtf8());
  31.     } else {
  32.         qDebug() << "Not connected to server!";
  33.     }
  34. }
  35. // 登录按钮点击事件
  36. void MainWindow::on_pushButton_clicked()
  37. {
  38.     name = ui->lineEdit->text();
  39.     //借助网络发送数据
  40.     QString message = QString("l,%1").arg(name);
  41.     sendMessage(message);
  42. }
  43. // 注册按钮点击事件
  44. void MainWindow::on_pushButton_2_clicked()
  45. {
  46.     connect(&myresform, &Dialog::sendText, this, &MainWindow::onReceiveText, Qt::UniqueConnection);
  47.     myresform.exec();
  48. }
  49. void MainWindow::onReceiveText(const QString &text)
  50. {
  51.     resname.clear();
  52.     resname = text;
  53.     // 借助网络发送数据
  54.     QString message = QString("r,%1").arg(resname);
  55.     sendMessage(message);
  56. }
  57. void MainWindow::onReadyRead()
  58. {
  59.     QByteArray data = tcpSocket->readAll();
  60.     qDebug() << "Received from server:" << QString::fromUtf8(data);
  61.     QString message = QString::fromUtf8(data);
  62.     if(message == "r,exist"){
  63.         QMessageBox::warning(this,"提示","用户名已存在");
  64.     }
  65.     else if(message == "r,ok"){
  66.         QMessageBox::warning(this,"提示","用户名创建成功");
  67.     }
  68.     else if(message == "l,notexist"){
  69.         QMessageBox::warning(this,"提示","用户不存在");
  70.     }
  71.     else if(message == "l,ok"){
  72.         this->hide();
  73.         myform.name = name;
  74.         myform.show();
  75.     }
  76.     // 显示消息
  77.     else{
  78.         emit send(message);
  79.     }
  80. }
  81. void MainWindow::onConnected()
  82. {
  83.     qDebug() << "Connected to server!";
  84. }
  85. void MainWindow::onDisconnected()
  86. {
  87.     qDebug() << "Disconnected from server.";
  88. }
  89. void MainWindow::onRecvMessage(const QString &text)
  90. {
  91.     QString sendMes = QString("c,%1:%2").arg(name).arg(text);
  92.     sendMessage(sendMes);
  93. }
复制代码
十、结果演示

(1)首先运行起服务器

(2)然后运行客户端

(3)注册,输入用户名,点击确定,提示注册乐成

(4)登录,并且开始谈天

(5)重复以上操纵,继承开一个客户端

可以发现,两人通过服务器正常谈天了。
十一、留给读者的话

本次项目,从数据库、服务器,再到客户端,基本包罗了整个软件的设计流程(不包罗背面的维护和迭代更新)。在下从中受益颇多,同时也考虑着在后续中对服务器进行更新。首先打算把线程池引入,进一步提高服务器的并发性;然后考虑将select更新成epoll,epoll的接口和性能远非select可比;再来是客户端之间允许直接使用udp协议通信,减轻服务器的负担;最后,假如可以的话,在下想美满客户端的界面,最后加上一个好友列表,能够查看本身好友的在线状态。这些都可以在后续慢慢美满,固然,假如诸位有更好的idea,欢迎批评区留言,在下不胜感激~
最后,诸位假如想获取完整源码的话,可以三连+私信留下邮箱~
至此,结束~

望诸位不忘三连支持一下~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

星球的眼睛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表