TCP套接字通信与守护进程

news2024/12/21 16:03:29

目录

TCP socket API 详解

代码实现TCP通讯

服务端

客户端

Task

守护进程

守护进程

前台与后台

Linux进程间关系

​编辑

设置为独立会话setsid

daemon接口

为什么需要设置umask

会话ID与组ID

TCP的相关机制

下图是基于TCP协议的客户端/服务器程序的一般流程:

数据传输的过程

断开连接的过程:

TCP 和 UDP 对比


TCP socket API 详解

下面介绍程序中用到的 socket API, 这些函数都在 sys/socket.h 中。
socket( ):
socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;
应用程序可以像读写文件一样用read/write在网络上收发数据;
如果socket()调用出错则返回-1;
对于IPv4, family参数指定为AF_INET;
对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议
protocol参数的介绍从略,指定为0即可。
bind():
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;
bind()成功返回0,失败返回-1。
bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听
myaddr所描述的地址和端口号;
前面讲过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度
listen():
listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5),listen()成功返回0,失败返回-1;
accept():
三次握手完成后, 服务器调用accept()接受连接;
如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
如果给addr 参数传NULL,表示不关心客户端的地址;
addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
connect
客户端需要调用connect()连接服务器;
connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
connect()成功返回0,出错返回-1。

代码实现TCP通讯

服务端

using namespace std;

const int defaultfd = -1;
const string defaultip = "0.0.0.0";
extern Log lg;
const int backlog = 10;     //积压

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError,
};


class TcpServer
{
public:
    TcpServer(const uint16_t port, const string ip = defaultip)
        : _port(port)
        , _ip(ip)
        , _listensock(defaultfd)
    {}

    ~TcpServer()
    {
        if (_listensock!= defaultfd)
        {
            close(_listensock);
        }
    }

    void InitServer()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock == -1)
        {
            lg(Fatal, "create socket, errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg(Info, "create socket success, listensock_: %d", _listensock);

        int opt = 1;
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));     // 防止偶发性的服务器无法进行立即重启(tcp协议的时候再说)

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));   //bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = inet_addr(_ip.c_str());
        local.sin_port = htons(_port);

        if (bind(_listensock, (struct sockaddr*)&local, sizeof(local)) == -1)
        {
            lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }
        lg(Info, "bind socket success, listensock_: %d", _listensock);

        // Tcp是面向连接的,服务器一般是比较“被动的”,服务器一直处于一种,一直在等待连接到来的状态
        if (listen(_listensock, backlog) == -1)       //第二个参数用来设置最大积压
        {
            lg(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }
        lg(Info, "listen socket success, listensock_: %d", _listensock);
    }

    void Start()
    {
        Daemon();
        ThreadPool<Task>::GetInstance()->Start();
        lg(Info, "tcpServer is running....");

        for(;;)
        {
            //1.获取accsocket
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int accsock = accept(_listensock, (struct sockaddr*)&peer, &len);
            if (accsock == -1)
            {
                lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?
                continue;   //只是接受一次失败,继续接受下一个连接
            }

            uint16_t peerport = ntohs(peer.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(peer.sin_addr), clientip, sizeof(clientip));

            // 2. 根据新连接来进行通信
            lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", accsock, clientip, peerport);

            //线程池版本
            Task t(accsock, clientip, peerport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
        
    }


private:
    int _listensock;
    uint16_t _port;
    string _ip;
};

注意

int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len);

参数讲解

线程池版本的 TCP 服务器
由于TCP服务器可能被多个客户端去访问,因此可以优先创建一批线程池,让线程池去处理来自Client的需求。

客户端

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>


using namespace std;

void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

// ./tcpclient serverip serverport

int main(int argc, char *argv[])
{
    if (argc != 3) 
    {
        Usage(argv[0]);
        exit(1);
    }

    string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));

    while (true)
    {
        int cnt = 5;     //重连次数
        int isreconnect = false;
        int sockfd = 0;
        sockfd = socket(AF_INET, SOCK_STREAM, 0);

        if (sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return 1;
        }

        //设置重连机制
        do      
        {
            // tcp客户端要不要bind?1 要不要显示的bind?0 系统进行bind,随机端口
            // 客户端发起connect的时候,进行自动随机bind
            int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
            if (n < 0)
            {
                isreconnect = true;     //设置重连标志
                cnt--;
                std::cerr << "connect error..., reconnect: " << cnt << std::endl;
                sleep(2);
            }
            else
                break;
        }while (cnt && isreconnect);     //重连次数为0,退出循环

        if (cnt == 0)
        {
            std::cerr << "user offline..." << std::endl;
            break;
        }

        string msg;
        std::cout << "Please Enter# ";
        std::getline(std::cin, msg);

        // TCP在接受发送数据的时候使用的接口有send、write、read、recv
        int n = write(sockfd, msg.c_str(), msg.size());
        if (n < 0)
        {
            std::cerr << "write error..." << std::endl;
            // break;
        }

        char inbuf[1024] = {0};
        n = read(sockfd, inbuf, sizeof(inbuf));
        if (n > 0)
        {
            inbuf[n] = 0;
            std::cout << inbuf << std::endl;
        }
        else{       
            // break;   不选择退出,而是断线重连
        }
        close(sockfd);   //每次循环都会新建一个套接字描述符,所以需要关闭套接字描述符
    }

    return 0;
}

Task

#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Init.hpp"

Init init;
extern Log lg;


class Task
{
public:
    Task(int sockfd, const std::string &clientip, const uint16_t &clientport)
        : sockfd_(sockfd), clientip_(clientip), clientport_(clientport)
    {
    }
    Task()
    {
    }
    void run()
    {
        // 测试代码
        char buffer[4096];
        // Tcp是面向字节流的,你怎么保证,你读取上来的数据,是"一个" "完整" 的报文呢?
        ssize_t n = read(sockfd_, buffer, sizeof(buffer)); // BUG?
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client key# " << buffer << std::endl;
            std::string echo_string = init.translation(buffer);

            // sleep(5);
            // // close(sockfd_);
            // lg(Warning, "close sockfd %d done", sockfd_);

            // sleep(2);
            n = write(sockfd_, echo_string.c_str(), echo_string.size()); // 100 fd 不存在
            if(n < 0)
            {
                lg(Warning, "write error, errno : %d, errstring: %s", errno, strerror(errno));
            }
        }
        else if (n == 0)
        {
            lg(Info, "%s:%d quit, server close sockfd: %d", clientip_.c_str(), clientport_, sockfd_);
        }
        else
        {
            lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd_, clientip_.c_str(), clientport_);
        }
        close(sockfd_);
    }
    void operator()()
    {
        run();
    }
    ~Task()
    {
    }

private:
    int sockfd_;
    std::string clientip_;
    uint16_t clientport_;
};

守护进程

#pragma once

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const std::string nullfile = "/dev/null";

//1.忽略信号:SIGCHLD, SIGPIPE, SIGSTOP 2.独立会话 3.更改工作目录 4.重定向

void Daemon(const std::string &cwd = "")
{
    // 1. 忽略其他异常信号
    signal(SIGCLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);

    // 2. 将自己变成独立的会话
    if (fork() > 0)
        exit(0);    // 父进程退出,子进程变成后台进程继续执行
    setsid();

    // 3. 更改当前调用进程的工作目录
    if (!cwd.empty())
        chdir(cwd.c_str());

    // 4. 标准输入,标准输出,标准错误重定向至/dev/null
    int fd = open(nullfile.c_str(), O_RDWR);
    if(fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
}

守护进程的几大关键:1.忽略信号(子进程会继承父进程的信号处理) 2.设置umask 3.创建子进程,独立会话 4.进行重定向到/dev/null

守护进程

守护进程(daemon)是一种在后台运行的进程,通常没有控制终端,它们执行系统任务,不需要用户交互。守护进程通常在系统启动时启动,并在系统关闭时终止。以下是一些为什么守护进程需要忽略某些信号的原因:

1. **防止意外终止**:守护进程通常执行关键任务,如网络服务、数据备份等。如果守护进程响应了某些信号(如SIGINT,通常由Ctrl+C产生),它们可能会被意外终止,这会影响系统的稳定性和服务的可用性。

2. **避免不必要的重启**:某些信号(如SIGHUP)默认情况下会导致进程重启。对于守护进程来说,这通常不是期望的行为,因为它们通常设计为长期运行,而不是需要定期重启。

3. **防止子进程成为僵尸进程**:守护进程可能会创建子进程来执行任务。如果子进程终止,而守护进程没有正确处理SIGCHLD信号,子进程可能会变成僵尸进程,占用系统资源。

4. **保持服务的连续性**:守护进程忽略某些信号可以确保它们不会因为信号而中断正在执行的任务,从而保持服务的连续性和可靠性。

前台与后台

一般来讲,谁拥有键盘文件(终端操作,如 Ctrl + C),谁就是前台进程。

前后台之间的指令操作

1.command + &

后台任务执行的时候,会给你一个后台任务号与pid

2.jobs查看任务

3.fg + 后台任务号  将后台任务提到前台

^C可以终止前台的2号任务

 4. 暂停程序并放到后台 :Ctrl + Z

暂停某个任务,将这个任务放到后台,shell重新回到前台

5. bg(back ground) + 任务:把因为暂停放到后台的任务,在后台重新跑起来

Linux进程间关系

组ID是多个进程id的第一个(父进程)的pid

最开始这些进程的父进程都是bash,但是bash退出之后,这些进程就被托孤了,被OS领养

不受到会话的影响,即不受到bash的影响,也就是让进程的父进程变成OS,而不是bash,也就说,守护进程的本质还是孤儿进程

设置为独立会话setsid

谁调用这个函数,这个进程就会被设置为独立的回话(前提这个进程不能是进程组的组长)

怎么保证自己不是组长呢?---

父进程fork子进程之后,父进程直接退出,那么子进程就变成了了孤儿进程。孤儿进程默认是后台进程。

父进程关闭之后,其子进程的进程组ID(PGID)不会自动改变。进程组ID是在进程创建时确定的,并且会一直保持不变,除非显式地通过系统调用(如setpgid)来修改。

守护进程不受bash登录与退出的影响,因此bash退出时,关闭的0 1 2号文件需要关闭,为了不受他们的影响,应该重定向到一个垃圾桶文件---/dev/null

daemon接口

实际上系统实现了这个接口

允许将两个参数设置为0.

如果参数是0

第一个参数:改成根目录

第二个参数:把0 1 2重定向为垃圾桶

为什么需要设置umask

会话ID与组ID

在UNIX和类UNIX操作系统中,每个进程都属于一个进程组,并且可以属于一个会话。下面是组ID与会话ID的介绍:

### 组ID(Group ID,GID)

组ID是用于标识进程组的一个整数。进程组是一组相关进程的集合通常是由一个进程通过 `fork()` 系统调用创建的。进程组的主要目的是允许信号被发送到一组进程,而不是单个进程

- **进程组ID(PGID)**:每个进程组都有一个唯一的进程组ID,它是该组中任一进程的组ID。通常,进程组ID是创建该组的第一个进程的进程ID。

- **获取和设置PGID**:进程可以通过 `getpgrp()` 函数获取其进程组ID,通过 `setpgid()` 函数设置其进程组ID或另一个进程的进程组ID。

### 会话ID(Session ID)

会话ID是用于标识会话的一个整数。会话是一个或多个进程组的集合,它们是由进程通过 `setsid()` 系统调用创建的,用于实现进程的独立性。

- **会话**:会话通常是由控制终端(如用户登录的终端)启动的第一个进程创建的。会话中的所有进程组共享同一个会话ID。

- **会话领导进程**:创建会话的进程称为会话领导进程会话领导进程的进程ID同时也是会话ID

- **控制终端**:会话可以有一个控制终端当会话领导进程打开一个终端时,该终端成为会话的控制终端。会话中的进程组可以接收来自控制终端的信号,如中断信号(SIGINT)。

### 关系和特性

- 一个会话可以包含多个进程组。

- 每个进程组只能属于一个会话。

- 会话领导进程不能成为另一个会话的成员。

- 如果会话领导进程终止,会话中的所有进程都不会受到影响,但会话可能失去控制终端。

- 如果会话没有进程了,会话会被销毁。

### 常用函数

- `getsid()`:获取调用进程的会话ID。

- `setsid()`:创建一个新的会话,并将调用进程设置为会话领导进程。

组ID与会话ID是UNIX系统中的基本概念,它们用于进程管理,特别是在信号处理和终端控制方面。了解这些概念对于编写多进程程序和守护进程至关重要。

TCP的相关机制

下图是基于TCP协议的客户端/服务器程序的一般流程:

服务器初始化 :
调用socket, 创建文件描述符;
调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败;
调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备;
调用accecpt, 并阻塞, 等待客户端连接过来;
建立连接的过程:
调用socket, 创建文件描述符;
调用connect, 向服务器发起连接请求;
connect会发出SYN段并阻塞等待服务器应答; (第一次)
服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"; (第二次)
客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次)
这个建立连接的过程, 通常称为 三次握手;

数据传输的过程

建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方可以同时写数据; 相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据;
服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;
这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期间客户端调用read()阻塞等待服务器的应答;
服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求;
客户端收到后从read()返回, 发送下一条请求,如此循环下去;

断开连接的过程:

如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);
此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);
read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)客户端收到FIN, 再返回一个ACK给服务器; (第四次).
这个断开连接的过程, 通常称为 四次挥手
在学习socket API时要注意应用程序和TCP协议层是如何交互的:
应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段
应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段

TCP 和 UDP 对比

可靠传输 vs 不可靠传输
有连接 vs 无连接
字节流 vs 数据报

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2263310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单点登录平台Casdoor搭建与使用,集成gitlab同步创建删除账号

一&#xff0c;简介 一般来说&#xff0c;公司有很多系统使用&#xff0c;为了实现统一的用户名管理和登录所有系统&#xff08;如 GitLab、Harbor 等&#xff09;&#xff0c;并在员工离职时只需删除一个主账号即可实现权限清除&#xff0c;可以采用 单点登录 (SSO) 和 集中式…

OCR:文字识别

使用场景: 远程身份认证 自动识别录入用户身份/企业资质信息&#xff0c;应用于金融、政务、保险、电商、直播等场景&#xff0c;对用户、商家、主播进行实名身份认证&#xff0c;有效降低用户输入成本&#xff0c;控制业务风险 文档电子化 识别提取各类办公文档、合同文件、企…

亚信安全春节14天双倍假期通告

亚信安全14天双倍假期来袭 “网安福利王”再次实至名归 2024年 8773小时&#xff0c;31582680秒 亚信安全一直驰骋于云网安世界 奋战在“安全 数智化”的壮阔征途上 如今&#xff0c;新春的脚步渐近 长达14天的春节长假 能让我们暂且放下忙碌的工作 去除班味&#xff0c…

使用Python打开资源管理器并选择文件

from PySide6.QtWidgets import QFileDialogdef openSelectFile(Path):filename, _ QFileDialog.getOpenFileName(Path, "打开文件", "", "所有文件 (*)")if filename:print(f"选择的文件: {filename}")return filename 代码解释 &a…

uniapp blob格式转换为video .mp4文件使用ffmpeg工具

前言 介绍一下这三种对象使用场景 您前端一旦涉及到文件或图片上传Q到服务器&#xff0c;就势必离不了 Blob/File /base64 三种主流的类型它们之间 互转 也成了常态 Blob - FileBlob -Base64Base64 - BlobFile-Base64Base64 _ File uniapp 上传文件 现在已获取到了blob格式的…

五、windows上vscode构建c/c++环境

1、安装vscode 官网下载界面&#xff1a;https://code.visualstudio.com/Download 请根据电脑系统安装所需版本点击下载链接&#xff08;一般情况下点击windows按钮即可&#xff09;鼠标左键双击&#xff0c;即可运行安装程序&#xff0c;点击【确认】&#xff1b;选择安装路径…

ElasticSearch中的模糊搜索:为什么输入错误还能搜索出来?

引言 在日常搜索中&#xff0c;用户经常会因为拼写错误或输入笔误导致搜索结果不准确。然而&#xff0c;ElasticSearch 提供了一种非常智能的模糊搜索&#xff08;Fuzzy Search&#xff09;功能&#xff0c;使得即使关键词输入错误&#xff0c;依然能够返回准确或接近的结果。…

prober.php探针

raw.githubusercontent.com/kmvan/x-prober/master/dist/prober.php

DIY-ESP8266移动PM2.5传感器-带屏幕-APP

本教程将指导您制作一台专业级的空气质量检测仪。这个项目使用经济实惠的ESP8266和PMS5003传感器&#xff0c;配合OLED显示屏&#xff0c;不仅能实时显示PM2.5数值&#xff0c;还能通过手机APP随时查看数据。总成本70元&#xff0c;相比几百的用的便宜&#xff0c;用的心理踏实…

怎么将pdf中的某一个提取出来?介绍几种提取PDF中页面的方法

怎么将pdf中的某一个提取出来&#xff1f;传统上&#xff0c;我们可能通过手动截取屏幕或使用PDF阅读器的复制功能来提取信息&#xff0c;但这种方法往往不够精确&#xff0c;且无法保留原文档的排版和格式。此外&#xff0c;很多时候我们需要提取的内容可能涉及多个页面、多个…

2024微博用户消费趋势报告:七成城市用户更爱用微博

文 | 魏力 发布 | 大力财经 站在岁末回首这一年&#xff0c;在信息浪潮的汹涌翻涌之下&#xff0c;社交媒体平台犹如社会经济的晴雨表&#xff0c;精准地折射出大众生活与消费的万千景象。近日&#xff0c;大力财经看到一份报告&#xff0c;微博发布了《2024微博用户消费趋势…

#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

【数据安全】如何保证其安全

数据安全风险 数字经济时代&#xff0c;数据已成为重要的生产要素。智慧城市、智慧政务的建设&#xff0c;正以数据为核心&#xff0c;推动城市管理的智能化和公共服务的优化。然而&#xff0c;公共数据开放共享与隐私保护之间的矛盾日益凸显&#xff0c;如何在确保数据安全的…

武汉市电子信息与通信工程职称公示了

2024年武汉市电子信息与通信工程专业职称公示了&#xff0c;本次公示通过人员有109人。 基本这已经是今年武汉市工程相关职称最后公示了&#xff0c;等待出证即可。 为什么有人好奇&#xff0c;一样的资料&#xff0c;都是业绩、论文等&#xff0c;有的人可以过&#xff0c;有的…

勤研低代码平台:高效数据集成助力企业数字化转型

在数字化转型的浪潮中&#xff0c;企业对高效开发工具的需求日益增长。勤研低代码平台强大的开发能力和灵活的数据集成方案&#xff0c;是企业提升效率、降低成本的理想选择。数据集成作为勤研低代码平台的核心功能之一&#xff0c;为企业提供了高效整合和利用数据的能力&#…

【毕业设计】A079-基于Java的影院订票系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看项目链接获取⬇️&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600个选题ex…

大腾智能受邀出席南京工业软件云工程应用创新中心工业软件生态应用推广大会并领奖

12月18日&#xff0c;南京工业软件云工程应用创新中心工业软件生态应用推广大会在南京江北新区圆满召开。本次大会由南京江北新区管委会主办&#xff0c;南京工业软件云工程应用创新中心、南京江北新区智能制造产业发展管理办公室联合承办&#xff0c;华为云计算技术有限公司支…

EasyPlayer.js播放器Web播放H.265要兼顾哪些方面?

在数字化时代&#xff0c;流媒体技术已经成为信息传播和娱乐消费的重要方式。随着互联网技术的飞速发展和移动设备的普及&#xff0c;流媒体服务正在重塑我们的生活和工作方式。从视频点播、在线直播到音乐流媒体&#xff0c;流媒体技术的广泛应用不仅改变了内容的分发和消费模…

fabric.js

目录 一、在canvas上画简单的图形 二、在canvas上用路径(Path)画不规则图形 三、在canvas上插入图片并设置旋转属性(angle) 四、让元素动起来(animate) 五、图像过滤器(filters)让图片多姿多彩 六、颜色模式(Color)和相互转换(toRgb、toHex) 七、对图形的渐变填充(Gradi…

白话AI大模型(LLM)原理

大模型&#xff08;例如 GPT-4或类似的深度学习模型&#xff09;是基于神经网络的系统&#xff0c;用于理解、生成文本、图像或其他数据类型。其工作原理可以分为以下几个核心步骤&#xff0c;我将通过易于理解的例子逐一解释。 1. 神经网络的基本概念 大模型背后有一个非常庞…