UDP简单聊天室创建

目录

一.   服务端模块实现

二.   处理聊天消息模块实现

三.   调用服务端模块实现

四.   客户端模块实现

五.   效果展示


本文介绍了如何用UDP创建一个简单的聊天室。

一.   服务端模块实现

服务端仍然沿用我们前面的思想(高内聚低耦合),因此我们用一下上一篇UDP英译汉网络词典的服务端实现(点此查看)。

#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <stdlib.h>
#include<functional>
#include "Log.hpp"
#include"InetAddr.hpp"
#include"Dict.hpp"using namespace std;enum
{SOCKET_ERROR = 1,BIND_ERROR,USAGE_ERROR
};const static int defaultfd = -1;
using func_t=function<string(const string&,bool& ok)>;class UdpServer
{
public:UdpServer(uint16_t port,func_t func): _sockfd(defaultfd), _port(port), _func(func),_isrunning(false){}void InitServer(){// 1.创建udp socket 套接字...必须要做的_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error,%s,%d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success,sockfd: %d\n", _sockfd);// 2.1 填充sockaddr_in结构struct sockaddr_in local;     // struct sockaddr_in 系统提供的数据类型,local是变量,用户栈上开辟空间bzero(&local, sizeof(local)); // 清空local.sin_family = AF_INET;local.sin_port = htons(_port); // port要经过网络传输给对面,即port先到网络,所以要将_port,从主机序列转化为网络序列local.sin_addr.s_addr=INADDR_ANY;//htonl(INADDR_ANY)// 2.2 bind sockfd和网络信息(IP(?)+Port)int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(FATAL, "bind error,%s,%d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");}void Start()//所有的服务器,本质解决的是输入输出的问题!不想让网络通信模块和业务模块进行强耦合{}~UdpServer(){}private:int _sockfd;uint16_t _port; // 服务器所用的端口号bool _isrunning;//给服务器设定回调,用来让上层进行注册业务的处理方法func_t _func;
};

首先明确的是,初始化函数InitServer是不变的,我们再来看Start函数,也是大差不差,只需改动一捏捏,我们也可以仿照以前的思路让上层去实现这个聊天的功能,那么我们就知道了,这次的服务端也需要一个回调函数,让上层进行业务处理。我们稍作修改。

using handler_message_t=......

我们先定义出来处理业务的函数类型,参数部分留待下面解析。

那么我们的TcpServer类的类成员就变成了:

class UdpServer
{
private:int _sockfd;//string _ip;//不是必须的uint16_t _port; // 服务器所用的端口号bool _isrunning;//给服务器设定回调,用来让上层进行注册业务的处理方法handler_message_t _handler_message;
};

 由此来编写构造函数,以及Start函数就显得水到渠成了。

const static int defaultfd = -1;
using handler_message_t=......class UdpServer
{
public:UdpServer(uint16_t port,handler_message_t handler_message): _sockfd(defaultfd), _port(port), _handler_message(handler_message),_isrunning(false){}void Start()//所有的服务器,本质解决的是输入输出的问题!不想让网络通信模块和业务模块进行强耦合{//一直运行,直到管理者不想运行了,服务器都是死循环_isrunning=true;while(true){char message[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);//1.我们要让server先收数据ssize_t n=recvfrom(_sockfd,message,sizeof(message)-1,0,(struct sockaddr*)&peer,&len);if(n>0){message[n]=0;InetAddr addr(peer);LOG(DEBUG,"get message from [%s:%d]: %s\n",addr.Ip().c_str(),addr.Port(),message);_handler_message(_sockfd,message,addr);}}_isrunning=false;}~UdpServer(){}private:int _sockfd;//string _ip;//不是必须的uint16_t _port; // 服务器所用的端口号bool _isrunning;//给服务器设定回调,用来让上层进行注册业务的处理方法handler_message_t _handler_message;
};

那好我们下面就具体来看看该如何处理业务,以补充服务端的处理方法。

二.   处理聊天消息模块实现

大家不用猜也知道该怎么办了吧。没错,仍然封装成一个类。

来看看基本框架如何写。

class MessageRoute
{
public:MessageRoute(){pthread_mutex_init(&_mutex,nullptr);}~MessageRoute(){pthread_mutex_destroy(&_mutex);}
private:vector<InetAddr> _online_user;pthread_mutex_t _mutex;
};

我们的成员有两位,首先我们想想平时我的微信、QQ,聊天的话肯定不止一个人聊天,我不聊天但是别人的消息仍然能显示到我的屏幕。所以定义一个vector结构的数组用来装聊天成员。定义一个锁来保护临界资源,更加安全。

第一次看的朋友,可能不知道vector里面装的InetAddr是什么,其实是我们封装的一个类。

class InetAddr
{
private:void GetAddress(string* ip,uint16_t* port){*port=ntohs(_addr.sin_port);*ip=inet_ntoa(_addr.sin_addr);}
public:InetAddr(const struct sockaddr_in &addr):_addr(addr){GetAddress(&_ip,&_port);}string Ip(){return _ip;}bool operator==(const InetAddr& addr){//if(_ip==addr._ip) 任何时刻只允许一个用户if(_ip==addr._ip && _port==addr._port)//方便测试{return true;}return false;}struct sockaddr_in Addr(){return _addr;}uint16_t Port(){return _port;}~InetAddr(){}
private:struct sockaddr_in _addr;string _ip;uint16_t _port;
};

这样封装更便于我们的操作。

当有新用户进入聊天室进行聊天的时候,我们应该将其插入到用户数组中,而当由用户退出的时候,我们同样应该及时的将其从数组中删除。

bool IsExists(const InetAddr& addr)
{for(auto a:_online_user){if(a==addr) return true;}return false;
}void AddUser(const InetAddr& addr)
{LockGuard lockguard(&_mutex);if(IsExists(addr)) return;_online_user.push_back(addr);
}void DelUser(const InetAddr& addr)
{LockGuard lockguard(&_mutex);for(auto iter=_online_user.begin();iter!=_online_user.end();iter++){if(*iter==addr){_online_user.erase(iter);break;}}
}

这里出现了一个新东西----LockGuard,这是我们按照RAII(点此查看)的思路封装的锁。

#ifndef __lock_GUARD_HPP__
#define __lock_GUARD_HPP__#include<iostream>
#include<pthread.h>class LockGuard
{
public:LockGuard(pthread_mutex_t* mutex):_mutex(mutex){pthread_mutex_lock(_mutex);//构造加锁}~LockGuard(){pthread_mutex_unlock(_mutex);}
private:pthread_mutex_t* _mutex;
};#endif

那么正式来说该如何处理消息呢?

void RouteHelper(int sockfd,string message,InetAddr who)
{LockGuard lockguard(&_mutex);//2.进行消息转发for(auto user:_online_user){string send_message="\n["+who.Ip()+":"+to_string(who.Port())+"]#"+message+"\n";struct sockaddr_in clientaddr=user.Addr();::sendto(sockfd,send_message.c_str(),send_message.size(),0,(struct sockaddr*)&clientaddr,sizeof(clientaddr));}
}void Route(int sockfd,string message,InetAddr who)
{//1.1 我们任务:用户首次发消息,还要将自己,插入到在线用户中AddUser(who);//1.2 如果客户端要退出if(message=="Q" || message=="QUIT") {DelUser(who);}//2.构建任务对象,入队列,让线程池进行转发task_t t=bind(&MessageRoute::RouteHelper,this,sockfd,message,who);ThreadPool<task_t>::GetInstance()->Enqueue(t);
}

我们来说说逻辑,处理方法就是将发来的消息通过线程池进行转发。

#pragma once//单例模式的线程池
#include<iostream>
#include<vector>
#include<queue>
#include<pthread.h>
#include"Thread.hpp"
#include"Log.hpp"
#include"LockGuard.hpp"using namespace std;
using namespace ThreadModule;const static int gdefaultthreadnum=3;template<typename T>
class ThreadPool
{
private:void LockQueue(){pthread_mutex_lock(&_mutex);}void UnLockQueue(){pthread_mutex_unlock(&_mutex);}void ThreadSleep(){pthread_cond_wait(&_cond,&_mutex);}void ThreadWakeup(){pthread_cond_signal(&_cond);}void ThreadWakeAll(){pthread_cond_broadcast(&_cond);}//私有的ThreadPool(int threadnum=gdefaultthreadnum):_threadnum(threadnum),_waitnum(0),_isrunning(false){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);LOG(INFO,"ThreadPool Construct()");}void Start(){for(auto& thread:_threads){thread.Start();}}void HandlerTask(string name)//类的成员方法,也可以成为另一个类的回调方法,方便我们继续类级别的互相调用{LOG(INFO,"%s is running\n",name.c_str());while(true){//1.保证队列安全LockQueue();//2.队列中不一定有数据while(_task_queue.empty() && _isrunning){_waitnum++;ThreadSleep();_waitnum--;}//2.1 如果线程池已经退出了 && 任务队列是空的if(_task_queue.empty() && !_isrunning){UnLockQueue();break;}//2.2 如果线程池不退出 && 任务队列不是空的//2.3 如果线程池已经退出 && 任务队列不是空的 --- 处理完所有的任务,然后再退出//3.一定有任务,处理任务T t=_task_queue.front();_task_queue.pop();UnLockQueue();LOG(DEBUG,"%s get a task",name.c_str());//4.处理任务,这个任务属于线程独占的任务,所以不能放在加锁和解锁之间t();//LOG(DEBUG,"%s handler a task,result is: %s",name.c_str(),t.ResultToString());}}void InitThreadPool(){//指向构建出所有的线程,并不自动for(int num=0;num<_threadnum;num++){string name="thread-"+to_string(num+1);_threads.emplace_back(bind(&ThreadPool::HandlerTask,this,placeholders::_1),name);LOG(INFO,"init thread %s done\n",name.c_str());}_isrunning=true;}//复制拷贝禁用ThreadPool<T> &operator=(const ThreadPool<T>&)=delete;ThreadPool(const ThreadPool<T> &)=delete;
public:static ThreadPool<T> *GetInstance(){//如果是多线程获取线程池对象,下面的代码就有问题,所以要加锁//双判断的方式,可以有效减少获取单例的加锁成本,而且保证线程安全if(_instance==nullptr)//只有第一次会创建对象,后续都是获取,这样就不用每次都申请锁{//保证第二次之后,所有线程,不用再加锁,直接返回_instance单例对象LockGuard lockguard(&_lock);if (_instance == nullptr){_instance = new ThreadPool<T>();_instance->InitThreadPool();_instance->Start();LOG(DEBUG, "创建线程池单例\n");return _instance;}}LOG(DEBUG, "获取线程池单例\n");return _instance;}bool Enqueue(const T& t){bool ret=false;LockQueue();if(_isrunning){   _task_queue.push(t);if(_waitnum>0){ThreadWakeup();}LOG(DEBUG,"enqueue task success\n");ret=true;}UnLockQueue();return ret;}void Stop(){LockQueue();_isrunning=false;ThreadWakeAll();UnLockQueue();}void Wait(){for(auto& thread:_threads){thread.Join();LOG(INFO,"%s is quit",thread.name().c_str());}}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}
private:int _threadnum;vector<Thread> _threads;queue<T> _task_queue;pthread_mutex_t _mutex;pthread_cond_t _cond;int _waitnum;bool _isrunning;//添加单例模式--懒汉static ThreadPool<T> *_instance;static pthread_mutex_t _lock;//保护单例的锁
};template<typename T>
ThreadPool<T> *ThreadPool<T>::_instance=nullptr;template<typename T>
pthread_mutex_t ThreadPool<T>::_lock=PTHREAD_MUTEX_INITIALIZER;

我们可以知道,Route函数就是我们之前在服务器说的上层处理函数。那么handler_message_t类型的上层处理函数的参数就很明确了。

using handler_message_t=function<void(int sockfd,const string message,const InetAddr who)>;

那么调用服务端的主函数如何写就很明确了。

此处我们还封装了原生线程库,命名文件为Thread.hpp

//封装原生线程库#ifndef __THREAD_HPP__
#define __THREAD_HPP__#include<iostream>
#include<string>
#include<functional>
#include<unistd.h>
#include<pthread.h>using namespace std;namespace ThreadModule
{using func_t=function<void(string&)>;class Thread{public:void Excute(){_func(_threadname);}public:Thread(func_t func,const string& name="none-name"):_func(func),_threadname(name),_stop(true){}static void* threadroutine(void* args)//类成员函数,形参是有this指针的!{Thread *self=static_cast<Thread*>(args);self->Excute();return nullptr;}bool Start(){int n=pthread_create(&_tid,nullptr,threadroutine,this);if(!n){_stop=false;return true;}else {return false;}}void Detach(){if(!_stop){pthread_detach(_tid);}}void Join(){if(!_stop){pthread_join(_tid,nullptr);}}string name(){return _threadname;}void Stop(){_stop=true;}~Thread(){}private:pthread_t _tid;string _threadname;func_t _func;bool _stop;};
}#endif

三.   调用服务端模块实现

我们只需将服务端中处理业务函数初始化为处理业务模块中的Route函数,然后依次调用InitServer函数、Start函数即可。

#include<iostream>
#include<memory>
#include"UdpServer.hpp"
#include"Log.hpp"
#include"MessageRoute.hpp"
using namespace std;void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" local_port\n"<<endl;
}// ./udpserver ip
int main(int argc,char *argv[])
{if(argc!=2){Usage(argv[0]);exit(USAGE_ERROR);}EnableScreen();//string ip=argv[1];//定义消息转发模块MessageRoute route;//网络模块uint16_t port=stoi(argv[1]);unique_ptr<UdpServer> usvr=make_unique<UdpServer>(port,\bind(&MessageRoute::Route,&route,placeholders::_1,\placeholders::_2,placeholders::_3));//C++14usvr->InitServer();usvr->Start();return 0;
}

MessageRoute.hpp文件即我们的处理聊天消息模块。

 四.   客户端模块实现

此处虽说大体还是发送消息,并接收服务器发送回来的消息。

但是与众不同的是:此处发送消息和接收服务器发送回来的消息应该是两个不同的线程。因为要做到不发消息的时候还是能接收到消息。

#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"Thread.hpp"
#include"Comm.hpp"using namespace std;
using namespace ThreadModule;void recvmessage(int sockfd,string name)
{//version 1int fd=OpenDev("/dev/pts/0",O_WRONLY);while(true){struct sockaddr_in peer;socklen_t len=sizeof(peer);char buffer[1024];ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){buffer[n]=0;write(fd,buffer,strlen(buffer));}}//version 2// while(true)// {//     struct sockaddr_in peer;//     socklen_t len=sizeof(peer);//     char buffer[1024];//     ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);//     if(n>0)//     {//         buffer[n]=0;//         fprintf(stderr,"%s | %s\n",name.c_str(),buffer);//             //此时运行指令变为./udpclient + ip + port + 2>/dev/pts/2//     }// }
}void sendmessage(int sockfd,struct sockaddr_in& server,string name)
{string message;while(true){printf("%s | Enter# ",name.c_str());fflush(stdout);getline(cin,message);sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));}
}void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" serverip serverport\n"<<endl;
}int InitClient(string& serverip,uint16_t serverport,struct sockaddr_in *server)
{//1.创建socketint sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){cerr<<"socket error"<<endl;return -1;}//2.client一定要bind,client也有自己的ip和port,但是不建议显示(和server一样用bind函数)bind//a.那如何bind呢?当udp client首次发送数据的时候,os会自动随机的给client进行bind--为什么?要bind,必然要和port关联!防止client port冲突//b.什么时候bind?首次发送数据的时候//构建目标主机的socket信息memset(server,0,sizeof(struct sockaddr_in));server->sin_family=AF_INET;server->sin_port=htons(serverport);server->sin_addr.s_addr=inet_addr(serverip.c_str());return sockfd;
}// ./udpclient serverip serverport
int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip=argv[1];uint16_t serverport=stoi(argv[2]);struct sockaddr_in serveraddr;int sockfd=InitClient(serverip,serverport,&serveraddr);if(sockfd==-1) return 1;func_t r=bind(&recvmessage,sockfd,placeholders::_1);func_t s=bind(&sendmessage,sockfd,serveraddr,placeholders::_1);//创建两个线程,分别用来接收消息和发消息,使其两个互不受影响Thread Recver(r,"recver");//recver在前面,还是sender在前面,都行Thread Sender(s,"sender");Sender.Start();Recver.Start();Recver.Join();Sender.Join();return 0;
}

同样用的是自己封装的线程。

值得注意的是这里接收消息模块有两个版本。此处的终端文件(/dev/pts)可以根据自己实际情况修改。

五.   效果展示

分别来看看两个版本都是怎么样的吧。

version 1:

 version 2:


总结:

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言小tip之函数递归

hello&#xff0c;各位小伙伴们今天我们来学习一下函数递归。 什么是函数递归呢&#xff1f;简单来说就是函数自己来调用自己。函数递归的主要思想是把大事化小&#xff0c;递归包含两层方面&#xff1a;1、递推 2、回归 在使用函数递归的时候要注意包含两个限制条件&#…

Linux 软硬连接

1. 硬链接 实际上并不是通过文件名来找到磁盘上的文件&#xff0c;而是通过inode。在linux中可以让多个文件名对应于同一个 inode&#xff0c;而这种方式就是建立硬链接。硬链接是文件系统中的一种链接类型&#xff0c;它创建了文件的一个额外的目录项&#xff0c;但不占用额外…

全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用

SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型&#xff0c;它综合考虑了土壤-水分-大气以及植被间的相互作用&#xff1b;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程&#xff0c;使其能够精确的模拟土壤中水分的运动&#xff0c;而且耦合了W…

EmguCV学习笔记 C# 7.1 角点检测

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

Ubuntu22.04安装 docker和docker-compose环境

Docker简介 Docker 是一个开源的应用容器引擎&#xff0c;它使开发者能够打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间不会有任何接口&#xff08;…

蓝色炫酷碎粒子HTML5导航源码

源码介绍 蓝色炫酷碎粒子HTML5导航源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 效果预览 源码获取 蓝色炫酷碎粒…

Halcon提取边缘线段lines_gauss 算子

Halcon提取边缘线段lines_gauss 算子 edges_color_sub_pix和edges_sub_pix两个算子使用边缘滤波器进行边缘检测。还有一个常用的算子lines_gauss算子&#xff0c;也可以用于提取边缘线段&#xff0c;它的鲁棒性非常好&#xff0c;提取出的线段类型是亚像素精度的XLD轮廓。其原…

LLM训练、精调与加速:大型语言模型的高效开发与应用策略

创作不易&#xff0c;您的关注、点赞、收藏和转发是我坚持下去的动力&#xff01; 大家有技术交流指导、论文及技术文档写作指导、项目开发合作的需求可以私信联系我 LLM&#xff08;大型语言模型&#xff09;的训练、精调和加速是当前人工智能研究和应用中的重要话题。下面将…

JVM垃圾判定算法

垃圾收集技术是Java的一堵高墙。Java堆内存中存放着几乎所有的对象实例&#xff0c;垃圾收集器在对堆内存进行回收前&#xff0c;第一件事情就是要确定这些对象中哪些还存活&#xff0c;哪些已经死去&#xff08;即不可能再被任何途径使用的对象&#xff09;。也就是判定垃圾。…

【学习笔记】卫星通信NTN 3GPP标准化进展分析(五)- 3GPP Release19 研究计划

一、引言&#xff1a; 本文来自3GPP Joern Krause, 3GPP MCC (May 14,2024) Non-Terrestrial Networks (NTN) (3gpp.org) 本文总结了NTN标准化进程以及后续的研究计划&#xff0c;是学习NTN协议的入门。 【学习笔记】卫星通信NTN 3GPP标准化进展分析&#xff08;一&#xff…

第二证券:大洗牌!头部券商营收、净利集体下滑

前十券商营收团体下滑&#xff0c;银河证券跌幅最小 新股IPO数量锐减129家至44家&#xff0c;国内证券市场股票基金交易量日均规划 同比下降 6.83%……关于证券公司而言&#xff0c;本年上半年可谓多重要素叠加冲击&#xff0c;成果下滑难以避免。于大多数证券公司而言&#x…

Vue(三)内置指令v-text、html、cloak、once、pre;自定义指令的三种方式、Vue生命周期

文章目录 1. 内置指令1.1 v-text、v-html指令1.2 v-cloak指令1.3 v-once指令1.4 v-pre指令 2. 自定义指令(directives)2.1 函数式2.2 对象式2.3 注意点 3. 生命周期3.1 挂载流程3.2 更新流程3.3 销毁流程 1. 内置指令 1.1 v-text、v-html指令 v-text与v-html都是向所在的节点…

EPLAN中部件库的导入和使用方法

EPLAN中部件库的导入和使用方法 如下图所示,点击工具-----部件------管理, 在弹出的窗口中点击附加------导入, 找到自己需要导入的文件,后缀名为EDZ,点击打开, 如下图所示,勾选"更新已有数据集并添加新建数据集",点击确定, 如下图所示,正在导

为什么一些行业刚起步就白热化竞争-例如机器人行业?

部分从事机器人行业的从业者交流就是特别卷。 明明是一个刚起飞的行业为何竞争如此残酷&#xff1f; 抛开降本增效的商业逻辑不谈。 只从一个侧面去观察-供需。 从事脑力劳动的机器人-处理文档 从事体力劳动的机器人-打螺丝 交流 机器人时代什么时候到来&#xff1f; 相似…

编译器基础介绍

随着深度学习的不断发展&#xff0c;AI 模型结构在快速演化&#xff0c;底层计算硬件技术更是层出不穷&#xff0c;对于广大开发者来说不仅要考虑如何在复杂多变的场景下有效的将算力发挥出来&#xff0c;还要应对 AI 框架的持续迭代。AI 编译器就成了应对以上问题广受关注的技…

论文翻译:Scaling Instruction-Finetuned Language Models

Scaling Instruction-Finetuned Language Models https://www.jmlr.org/papers/volume25/23-0870/23-0870.pdf 指令微调语言模型 文章目录 指令微调语言模型摘要1. 引言2. Flan微调2.1 微调数据2.2 微调过程2.3 评估协议 3. 扩展到5400亿参数和1836个任务4. 带有思维链注释的微…

HMI触屏网关-VISION如何与Modbus TCP从机通信

上文&#xff1a;HMI触屏网关-VISION如何与Modbus RTU从机通信-CSDN博客 1. 硬件连接 Modbus TCP协议采用网口通信的方式&#xff0c;因此&#xff0c;只需要保证网关的LAN口IP和Modbus TCP从机的IP在同一网段即可。 Modbus TCP从机参数说明&#xff1a; 2. VISION创建Modbu…

LaViT:这也行,微软提出直接用上一层的注意力权重生成当前层的注意力权重 | CVPR 2024

Less-Attention Vision Transformer利用了在多头自注意力&#xff08;MHSA&#xff09;块中计算的依赖关系&#xff0c;通过重复使用先前MSA块的注意力来绕过注意力计算&#xff0c;还额外增加了一个简单的保持对角性的损失函数&#xff0c;旨在促进注意力矩阵在表示标记之间关…

从0到1搭建用户管理系统

手把手教你搭建前后端框架 新手对于很多成熟框架&#xff0c;不知道如何搭建的&#xff0c;不知道如何实现等等&#xff0c;忙碌之余&#xff0c;写了一篇博客 手把手教你搭建前后端框架源码&#xff0c; springbootmysqlelementuivue 从0到1&#xff0c;搭建springboot框架&am…

出租车4G5G无线车载监控系统解决方案(下)

目录 一、项目概述 1.1 项目背景 1.2 设计原则 1.3 设计目标 1.4 实施意义 二、系统总体设计 2.1建设目标 2.2系统模式 2.3设计思路 2.4设计架构 2.5系统组成 2.6优势分析 2.7设备达到的功能要求 2.8系统组成 三、系统详细设计 3.1 出租车车载监控 3.1.1 系统功能设计 3.2系统前…