C ++ implementa un servidor de chat en clúster

C ++ implementa un servidor de chat en clúster

JSON

Json es un modo de intercambio de datos ligero (también llamado método de serialización de datos). Json utiliza un formato de texto completamente independiente de los lenguajes de programación para almacenar y representar datos. Los conocimientos y la jerarquía clara hacen de Json un lenguaje de intercambio de datos ideal. Fácil de leer y escribir. Al mismo tiempo, es fácil admitir el análisis y la generación de máquinas, lo que mejora de manera efectiva la eficiencia de salida de la red.

La transmisión de red mencionada aquí implica serialización y deserialización . Tome la comunicación entre el cliente y el servidor como ejemplo. Generalmente, el cliente envía información al servidor. La información enviada puede ser cadenas, números enteros, etc., que primero deben convertirse en datos de flujo de bytes. Esto es serialización; De manera similar, cuando el servidor recibe la información del flujo de bytes del cliente, debe convertirla al formato de datos original, que es la deserialización.

JSON para C ++ moderno es una biblioteca JSON en C ++. Tiene las siguientes características: sintaxis intuitiva, solo usa json.hppdependencias de archivos de encabezado, escrita en el estándar C ++ 11, usando json similar a STL, STL y json se pueden convertir entre sí. y pruebas rigurosas (todas las clases se prueban rigurosamente por unidad).

Serialización de datos

En la red, los formatos de serialización de transmisión de datos comunes incluyen XML, Json y ProtoBuf, entre los cuales ProtoBuf es el más utilizado. Su transmisión de codificación de compresión de datos ocupa un ancho de banda pequeño. La misma información de datos es 1/10 de Json y 1/20. de XML, pero es un poco más complicado de usar que Json.

uso json

Archivos de encabezado introducidos y renombrados #include"json.hpp" using json = nlohmann::json;. Luego puedes usar json de manera similar a usar objetos.

#include"json.hpp"
using json = nlohmann::json;
#include <iostream>
#include <string>
using namespace std;

void func1()
{
    
    
    json js;
    js["from"] = "zhangsan";
    js["message_type"] = 2;
    js["to"] = "lisi";
    js["message"] = "Hi, what are you doing?";
    cout << js << endl;
	string str=js.dump();//序列化,转化成字符串格式
	cout<<str.c_str()<<endl;
    // 模拟从网络接收到json字符串,通过json::parse函数把json字符串字节流转化为json对象
    json js2 = json::parse(temp);
    cout << js2 << endl;
}
void func2()
{
    
    
    json js;
    js["id"] = {
    
    1, 2, 3, 4, 5};
    js["name"] = "zhang san";
    js["msg"]["zhang san"] = "hello world";
    js["msg"]["liu shuo"] = "hello china";
    js["msg"] = {
    
    {
    
    "zhang san", "hello world"}, {
    
    "liu shuo", "hello china"}};
    cout << js << endl;
}
int main()
{
    
    
    func1();
    func2();
    return 0;
}

La clave utiliza una tabla hash, que es una estructura desordenada.

Serialización de json—— json_obj.dump(). deserialización json—— json::parse(json_str).

Uso regular de cmake

Primero, se proporciona un ejemplo de código. A través del ejemplo de código, básicamente puede comprender algunos comandos cmake de uso común:

cmake_minimum_required(VERSION 3.0) #CMake最小版本

project(main)#定义当前工程的名字

# set表示创建一个变量,并初始化对应的值
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g) #配置编译选项

# include_directories()#头文件搜索目录

# link_directories() #库文件搜索目录

# 设置需要编译的源文件列表,其实也就是定义一个SRC_LIST变量名
set(SRC_LIST ./muduo_server.cpp)

# 设置可执行文件最终存储的目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)


# 把指定目录下的所有源文件名字放入变量名SRC_LIST里面
# aux_source_directory(./ SRC_LIST)

# 表示生成可执行文件server,由SRC_LIST变量所定义的源文件编译而来
add_executable(server ${SRC_LIST})

# 表示这个server目标程序,需要连接 muduo_net muduo_base pthread 等库文件
target_link_libraries(server muduo_net muduo_base pthread)

Generalmente, la estructura de directorio estándar de los proyectos de código abierto de C++ es como se muestra a continuación: La compilación

generalmente se realiza en el directorio de compilación cmake .., y luego los archivos intermedios durante el proceso de compilación se generarán en el directorio de compilación. Habrá un archivo en él, Makefiley makeel comando se ejecutará para generar el documento ejecutable final.

PROJECT_NAME: Especifique el nombre del proyecto mediante project()
PROJECT_SOURCE_DIR: El directorio raíz del proyecto
PROJECT_BINARY_DIR: El directorio donde se ejecuta el comando cmake
CMAKE_CURRENT_SOURCE_DIR: El directorio donde se encuentra el archivo CMakeList.txt actual
CMAKE_CURRENT_BINARY_DIR: El directorio de compilación, que se puede modificar usando agregar subdirectorio
EXECUTABLE_OUTPUT_PATH: La ubicación de salida del archivo ejecutable binario
LIBRARY_OUTPUT_PATH: La ubicación de salida del archivo de la biblioteca
BUILD_SHARED_LIBS: El modo de compilación de la biblioteca predeterminado (compartido o estático), el valor predeterminado es estático
CMAKE_C_FLAGS: Establecer opciones de compilación de C
CMAKE_CXX_FLAGS: Establecer opciones de compilación de C++
CMAKE_CXX_FLAGS_DEBUG: Establecer opciones de compilación al compilar el tipo Depurar
CMAKE_CXX_FLAGS_RELEASE: Establece las opciones de compilación al compilar el tipo Lanzamiento
CMAKE_GENERATOR: Nombre del compilador
CMAKE_COMMAND: CMake can La ruta completa del archivo ejecutable
CMAKE_BUILD_TYPE: la versión generada por la compilación del proyecto, Depurar / Lanzar

Muduo

La biblioteca de red muduo proporciona a los usuarios dos clases principales:

  1. TcpServer: utilizado para escribir programas de servidor
  2. TcpClient: utilizado para escribir programas cliente

epoll+thread pool:
Ventajas: capacidad de distinguir el código de E/S de red del código comercial, la desconexión y conexión del usuario y los eventos que el usuario puede leer y escribir

servidor muduo

A continuación se proporciona un ejemplo de código para que muduo separe las E/S del servidor y los subprocesos de trabajo:

#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <iostream>
#include <functional>
#include<string>
using namespace std;
using namespace muduo;
using namespace muduo::net;

// 通用模板
class ChatServer
{
    
    
public:
    ChatServer(EventLoop *loop,
               const InetAddress &listenAddr,
               const string &nameArg) : _tcpserver(loop, listenAddr, nameArg), _loop(loop)
    {
    
    
        // 给服务器注册用户连接的创建和断开的回调
        _tcpserver.setConnectionCallback(bind(&ChatServer::onConnection, this, placeholders::_1));

        // 给服务器注册用户读写事件回调
        _tcpserver.setMessageCallback(bind(&ChatServer::onMessage, this, placeholders::_1, placeholders::_2, placeholders::_3));

        // 设置服务器线程数量,1个I/O线程,3个worker线程
        _tcpserver.setThreadNum(4);
    }
    //开启事件循环
    void start()
    {
    
    
        _tcpserver.start();
    }

private:
    // 专门处理用户的连接创建和断开  epoll、listenfd、accept
    void onConnection(const TcpConnectionPtr &conn)
    {
    
    
        if(conn->connected())
        {
    
    
            cout << conn->peerAddress().toIpPort() << " -> " << conn->localAddress().toIpPort() << " state online;"<<endl;
        }
        else
        {
    
    
            cout << conn->peerAddress().toIpPort() << " -> " << conn->localAddress().toIpPort() << " state offline;" << endl;
            conn->shutdown();//关闭连接
        }
    }
    // 专门处理用户的读写事件
    void onMessage(const TcpConnectionPtr &conn, // 连接
                   Buffer *buffer,                 // 缓冲区
                   Timestamp time)               // 接收到信息的时间信息
    {
    
    
        string buf = buffer->retrieveAllAsString();
        cout << "recv data: " << buf << " time: " << time.toString() << endl;
        conn->send(buf);
    }
    TcpServer _tcpserver;
    EventLoop *_loop;
};
int main()
{
    
    
    EventLoop loop;
    InetAddress addr("127.0.0.1", 6000);//本机地址
    ChatServer server(&loop,addr,"ChatServer");
    //将listenfd通过epoll_ctl ->(传递给) epoll
    server.start();
    //按照epoll_wait以阻塞方式等待新用户连接,已连接用户的读写事件等
    loop.loop();
    return 0;
}

Comando de compilación:

server:muduo_server.cpp
	g++ $+ -o $@ -lmuduo_net -lmuduo_base -lpthread -std=c++11

$+: Indica dependencias, aquí significa muduo_server.cpp. $@: Indica el elemento de destino, aquí significa servidor.

Para desarrollar un programa de servidor basado en la biblioteca de red muduo , los pasos generales son los siguientes:
1. Combine el objeto TcpServer
2. Cree un puntero al objeto de bucle de eventos EventLoop
3. Aclare qué parámetros requiere el constructor TcpServer (TcpServer no tiene valores predeterminados constructor) y generar el constructor ChatServer
4. En el constructor del servidor actual, registre la función de devolución de llamada para procesar conexiones y la función de conexión para procesar eventos.
5. Establezca el número apropiado de subprocesos del lado del servidor. Muduo asignará I /O subprocesos y subprocesos de trabajo por sí solo (generalmente establece 1 E/S, n -1 subproceso de trabajo)

equilibrador de carga

En un entorno de sistema Linux de 32 bits, la concurrencia aproximada de un servidor sockfd es de aproximadamente 1024, lo que puede admitir unas 20.000 personas chateando al mismo tiempo. Utilice ulimit -nel comando para verificar el límite en la cantidad de archivos que el sistema permite abrir el proceso del usuario actual. Generalmente, cada proceso puede abrir hasta 1024 archivos. También debe eliminar la entrada estándar, la salida estándar y el error estándar. , monitoreo del servidor y proceso que debe abrirse para el proceso de usuario actual Comunicación y otros archivos, la cantidad de archivos restantes que se pueden conectar al socket del cliente es solo aproximadamente 1014. En otras palabras, un programa de comunicación basado en Linux puede ejecute hasta 1024 conexiones TCP simultáneas al mismo tiempo. Haga clic para ver más sobre las diversas restricciones sobre la cantidad máxima de conexiones de socket de alta concurrencia en Linux .

En un entorno real, puede haber varios servidores ejecutándose en segundo plano al mismo tiempo. Al iniciar la comunicación, debe seleccionar el servidor de chat.


LVS: un dispositivo comúnmente utilizado por los balanceadores de carga.

Equilibrador de carga nginx: equivale a conectar los servidores en serie. Después de que el usuario se conecta al servidor, el equilibrador de carga nginx asignará servidores al cliente. Si un servidor admite conexiones de usuario de 2 W, entonces tres servidores pueden admitir conexiones de usuario de 6 W.

El servidor de chat es un negocio de larga conexión.

Redis se basa en el modelo de publicación-suscripción , que es similar al patrón de observador del patrón de diseño .

instalación nginx

Antes de la versión 1.9, nginx solo admitía el equilibrio de carga de servidores web utilizando el protocolo HTTP. Las versiones posteriores comenzaron a admitir el equilibrio de carga de conexiones largas TCP. Sin embargo, nginx no compila el módulo de equilibrio de carga TCP de forma predeterminada y debe activarse --with-stream.

Vaya al sitio web oficial de nginx para descargar el paquete comprimido de nginx correspondiente.

Usamos el sistema Ubuntu, así que descargue nginx-1.25.2la versión en la segunda columna y obtenga el paquete de instalación después de la descarga. Úselo tar -zxvf nginx-1.25.2.tar.gzpara descomprimir. Después de la descompresión, hay auto CHANGES CHANGES.ru conf configure contrib html LICENSE man README srccarpetas y archivos en el directorio. ./configure --with-streamHabilite el equilibrio de carga basado en TCP durante la ejecución.

Es posible que falten archivos de biblioteca durante el proceso de instalación. Aquí encontré que faltaban zlib, PCRE, etc. Puede instalarlo siguiendo el siguiente comando:

sudo apt install zlib1g
sudo apt install zlib1g-dev
sudo apt-get install libpcre3 libpcre3-dev

Luego ejecute el comando (puede requerir derechos de administrador):

./configure --with-stream
make
make install
nginx -s reload #重新加载配置文件,例如添加服务器配置
nginx -s stop #停止nginx服务

Debe agregar el siguiente contenido al archivo de configuración de nginx (el archivo de configuración de nginx está aquí /usr/local/nginx/conf/nginx.conf, el archivo ejecutable está aquí /usr/local/nginx/sbin/nginx)

stream {
    upstream MyServer {
        server 127.0.0.1:6000 weight=1 max_fails=3 fail_timeout=30s;
        server 127.0.0.1:6002 weight=1 max_fails=3 fail_timeout=30s;
    }
    server {
        proxy_connect_timeout 1s;
        listen 8000;
        proxy_pass MyServer;
        tcp_nodelay on;
    }
}

La imagen de arriba muestra un cliente conectándose al servidor (la conexión es la IP y el puerto proporcionados por nginx). nginx distribuirá la respuesta del cliente a cada servidor de acuerdo con la configuración. Indirectamente es igual al cliente que se conecta directamente al servidor (aún necesita pasar por el equilibrador de carga nginx), no afecta la comunicación entre usuarios (no la comunicación entre servidores).

Después de la configuración, debe volver a cargar la configuración nginx -s reload.

instalación de redis

Instalar redis en ubuntu es muy simple:

sudo apt-get install redis-server

Comprueba el funcionamiento de redis.

ps -ef | grep redis
netstat -tanp

Por defecto se ejecuta en el puerto 6379 tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN 144028/redis-server.

Redis es un potente servidor de caché que admite una variedad de estructuras de datos, como cadenas, listas, conjuntos, mapas y otras estructuras. Admite el almacenamiento persistente de datos (almacenados en el disco duro) y se usa a menudo para alta concurrencia. Entorno de servidor se está diseñando.

Redis es en realidad similar a MySQL y está diseñado para cliente/servidor. El propio Redis admite el procesamiento de transacciones y el subproceso múltiple es seguro para subprocesos para incrementos y disminuciones de claves.

redis-cli #启动redis客户端

valor clave

root@xiehou--ubuntu:~# redis-cli
127.0.0.1:6379> get "abc"
(nil)
127.0.0.1:6379> set "abc" 122
OK
127.0.0.1:6379> get "abc"
"122"


Mecanismo de publicación-suscripción de Redis: el modelo de publicación-suscripción incluye dos roles, a saber, editor de mensajes y suscriptor de mensajes. Los suscriptores pueden definir uno o más canales y los editores pueden enviar mensajes a un canal determinado. Todos los suscriptores de este canal recibirán este mensaje.

El comando de suscripción es suscribirse. Después de ingresar al modo de suscripción, el cliente en este estado no puede usar los cuatro comandos de publicación y suscripción excepto suscribir, cancelar suscripción, psubscribe y punsubscribe; de ​​lo contrario, se informará un error.

Después de ingresar al estado de suscripción, el cliente puede recibir tres tipos de respuestas. Cada tipo de respuesta contiene 3 valores. El primer valor es
el tipo de mensaje. Dependiendo del tipo de mensaje, el segundo y tercer parámetro pueden tener significados diferentes. El tipo de mensaje puede tener los siguientes tres valores:

  1. suscribirse: información de comentarios que indica una suscripción exitosa. El segundo valor es el nombre del canal al que se suscribió correctamente y el tercer valor es el número de canales actualmente suscritos por el cliente.
  2. mensaje: indica el mensaje recibido, el segundo valor indica el nombre del canal que generó el mensaje y el tercer valor es el contenido del mensaje.
  3. darse de baja: Indica la cancelación exitosa de la suscripción de un canal. El segundo valor es el nombre del canal correspondiente y el tercer valor es el número de canales actualmente suscritos por el cliente. Cuando este valor es 0, el cliente saldrá del estado de suscripción y luego podrá ejecutar otros que no sean "publicar/suscribir". comandos de modo. .


Depuración con parámetros de entrada gdb --args ./chatserver 127.0.0.1 8000. O ejecutarlo primero gdb ./chatservery luego run 127.0.0.1 8000. Puntos de ruptura en un archivo cpp break chatservice.cpp:23.

Dirección del proyecto: https://gitee.com/xiehou-design/ChatServer

Supongo que te gusta

Origin blog.csdn.net/qq_45041871/article/details/132521310
Recomendado
Clasificación