TCP的socket编程

 TCP客户端逻辑

void Usage(const std::string & process) {std::cout << "Usage: " << process << " server_ip server_port" <<std::endl;
}
// ./tcp_client serverip serverport
int main(int argc, char * argv[]) {if (argc != 3) {Usage(argv[0]);return 1;}std::string serverip = argv[1];uint16_t serverport = stoi(argv[2]);// 1. 创建 socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {cerr << "socket error" << endl;return 1;}// 要不要 bind?// 2. connectstruct sockaddr_in server;memset( & server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);// p:process(进程), n(网络) -- 不太准确,但是好记忆inet_pton(AF_INET, serverip.c_str(), & server.sin_addr); // 1. 字符串 ip->4 字节 IP 2. 网络序列int n = connect(sockfd, CONV( & server), sizeof(server)); // 自动进行 bindif (n < 0) {cerr << "connect error" << endl;return 2;}// 未来我们就用 connect过后的的sockfd 进行通信即可while(true) {string inbuffer;cout << "Please Enter# ";getline(cin, inbuffer);ssize_t n = write(sockfd, inbuffer.c_str(),inbuffer.size());if (n > 0) {char buffer[1024];ssize_t m = read(sockfd, buffer, sizeof(buffer) - 1);if (m > 0) {buffer[m] = 0;cout << "get a echo messsge -> " << buffer <<endl;} else if (m == 0 || m < 0) {break;}} else {break;}}close(sockfd);return 0;
}

1.创建tcp套接字,创建服务器监听套接字地址结构体

我们是没有给客户端的通信套接字绑定地址的,要注意,我们不能手动bind客户端通信套接字,为什么?

手动绑定意味着客户端通信套接字的ip和端口是死的,那服务器端监听套接字的ip和端口也是死的,那假如创建多个客户端,则三次握手后这几个连接的五元组都是一样的,发送消息最后就无法正确给到套接字,玩不了一点

那咋整,有个接口叫个connect,你传给它套接字描述符,它来给你自动绑定套接字地址(ip和端口),这样最后每个连接的五元组都不会一样了

2.接下来调用connect(sockfd, CONV( & server), sizeof(server))

这个接口会先给客户端套接字绑定地址(ip和端口),然后会发起三次握手建立连接,建立连接之后,客户端通信套接字就用五元组(协议,源ip,源port,目的ip,目的port)来标识

3.因为是有连接的,所以和udp相比起来就是省点事,udp无连接,所以要用sendto,recvfrom来发送和接收消息,但是tcp不需要,直接用write和read就行

TCP多进程版本服务器

const static int default_backlog = 6;
// TODO
class TcpServer: public nocopy {public: TcpServer(uint16_t port): _port(port),_isrunning(false) {}// 都是固定套路void Init() {// 1. 创建 socket, 本质是文件_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0) {lg.LogMessage(Fatal, "create socket error, errnocode: % d, error string: % s\ n ", errno, strerror(errno));exit(Fatal);}int opt = 1;setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR |SO_REUSEPORT, & opt, sizeof(opt));lg.LogMessage(Debug, "create socket success,sockfd: % d\ n ", _listensock);// 2. 填充本地网络信息并 bindstruct sockaddr_in local;memset( & local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = htonl(INADDR_ANY);// 2.1 bindif (bind(_listensock, CONV( & local), sizeof(local)) != 0) {lg.LogMessage(Fatal, "bind socket error, errnocode: % d, error string: % s\ n ", errno, strerror(errno));exit(Bind_Err);}lg.LogMessage(Debug, "bind socket success, sockfd: %d\n",_listensock);// 3. 设置 socket 为监听状态,tcp 特有的if (listen(_listensock, default_backlog) != 0) {lg.LogMessage(Fatal, "listen socket error, errno code: % d, error string: % s\ n ", errno, strerror(errno));exit(Listen_Err);}lg.LogMessage(Debug, "listen socket success,sockfd: % d\ n ", _listensock);}// Tcp 连接全双工通信的.void Service(int sockfd) {char buffer[1024];// 一直进行 IOwhile (true) {ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0) {buffer[n] = 0;std::cout << "client say# " << buffer <<std::endl;std::string echo_string = "server echo# ";echo_string += buffer;write(sockfd, echo_string.c_str(),echo_string.size());} else if (n == 0) // read 如果返回值是 0,表示读到了文件结尾(对端关闭了连接!) {lg.LogMessage(Info, "client quit...\n");break;} else {lg.LogMessage(Error, "read socket error, errnocode: % d, error string: % s\ n ", errno, strerror(errno));break;}}}void ProcessConnection(int sockfd, struct sockaddr_in & peer) {// v2 多进程pid_t id = fork();if (id < 0) {close(sockfd);return;} else if (id == 0) {// childclose(_listensock);if (fork() > 0)exit(0);InetAddr addr(peer);// 获取 client 地址信息lg.LogMessage(Info, "process connection: %s:%d\n",addr.Ip().c_str(), addr.Port());// 孙子进程,孤儿进程,被系统领养,正常处理Service(sockfd);close(sockfd);exit(0);} else {close(sockfd);pid_t rid = waitpid(id, nullptr, 0);if (rid == id) {// do nothing}}}void Start() {_isrunning = true;while (_isrunning) {// 4. 获取连接struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensock, CONV( & peer), & len);if (sockfd < 0) {lg.LogMessage(Warning, "accept socket error, errnocode: % d, error string: % s\ n ", errno, strerror(errno));continue;}lg.LogMessage(Debug, "accept success, get n newsockfd: % d\ n ", sockfd);ProcessConnection(sockfd, peer);}}~TcpServer() {}private:uint16_t _port;int _listensock;// TODObool _isrunning;
};

1.创建一个套接字,给套接字设置选项,使其可以复用地址,创建一个套接字地址结构体,ip填INADDR_ANY,port由构造TcpServer时给定,然后bind将套接字绑定地址,还没结束,将该套接字设置为监听套接字,从此,该套接字就和udp套接字一样,是没有连接,这个套接字的作用就是三次握手建立连接用的

2.start是主逻辑,创建一个套接字地址结构体,这是用来接收客户端套接字ip和端口的,接下来accept会从监听套接字全连接队列里选择一个完成三次握手的连接,然后构建连接对应的通信套接字,返回其套接字文件描述符,顺带要说的是,通信套接字和监听套接字绑定的地址是一样的,但监听套接字无连接,通信套接字有连接,所以是可以通过元组区分的

3.获得通信套接字后传参到ProcessConnection,主进程也就是父进程会创建子进程,然后父进程关闭通信套接字的文件描述符,子进程关闭监听套接字描述符,然后父进程阻塞等待回收子进程,子进程创建孙子进程,然后子进程退出释放其资源,父进程回收子进程释放子进程本身数据结构,之后父进程继续accept获取新连接,孙子进程因为子进程的退出变成了孤儿进程,孤儿进程最终由INIT进程负责回收,但在退出之前,该孙子进程将负责与客户端的通信

TCP服务器多线程版本

const static int default_backlog = 6;
// TODOclass TcpServer : public nocopy {public:TcpServer(uint16_t port) : _port(port), _isrunning(false) {}// 都是固定套路void Init() {// 1. 创建 socket, file fd, 本质是文件_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0) {lg.LogMessage(Fatal, "create socket error, errno
code: %d, error string: %s\n", errno, strerror(errno));exit(Fatal);}int opt = 1;setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR |SO_REUSEPORT, &opt, sizeof(opt));lg.LogMessage(Debug, "create socket success,
sockfd: %d\n", _listensock);// 2. 填充本地网络信息并 bindstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = htonl(INADDR_ANY);// 2.1 bindif (bind(_listensock, CONV(&local), sizeof(local)) != 0) {lg.LogMessage(Fatal, "bind socket error, errno
code: %d, error string: %s\n", errno, strerror(errno));exit(Bind_Err);}lg.LogMessage(Debug, "bind socket success, sockfd: %d\n",_listensock);// 3. 设置 socket 为监听状态,tcp 特有的if (listen(_listensock, default_backlog) != 0) {lg.LogMessage(Fatal, "listen socket error, errno
code: %d, error string: %s\n", errno, strerror(errno));exit(Listen_Err);}lg.LogMessage(Debug, "listen socket success,
sockfd: %d\n", _listensock);}class ThreadData {public:ThreadData(int sockfd, struct sockaddr_in addr): _sockfd(sockfd), _addr(addr) {}~ThreadData() {}public:int _sockfd;InetAddr _addr;};// Tcp 连接全双工通信的.// 新增 staticstatic void Service(ThreadData &td) {char buffer[1024];// 一直进行 IOwhile (true) {ssize_t n = read(td._sockfd, buffer, sizeof(buffer) -1);if (n > 0) {buffer[n] = 0;std::cout << "client say# " << buffer <<std::endl;std::string echo_string = "server echo# ";echo_string += buffer;write(td._sockfd, echo_string.c_str(),echo_string.size());} else if (n == 0) // read 如果返回值是 0,表示读到了文件结尾(对端关闭了连接!) {lg.LogMessage(Info, "client[%s:%d] quit...\n",td._addr.Ip().c_str(), td._addr.Port());break;} else {lg.LogMessage(Error, "read socket error, errno
code: %d, error string: %s\n", errno, strerror(errno));break;}}}static void *threadExcute(void *args) {pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);TcpServer::Service(*td);close(td->_sockfd);delete td;return nullptr;}void ProcessConnection(int sockfd, struct sockaddr_in &peer) {// v3 多线程InetAddr addr(peer);pthread_t tid;ThreadData *td = new ThreadData(sockfd, peer);pthread_create(&tid, nullptr, threadExcute, (void*)td);}void Start() {_isrunning = true;while (_isrunning) {// 4. 获取连接struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensock, CONV(&peer), &len);if (sockfd < 0) {lg.LogMessage(Warning, "accept socket error, errno
code: %d, error string: %s\n", errno, strerror(errno));continue;}lg.LogMessage(Debug, "accept success, get n new
sockfd: %d\n", sockfd);ProcessConnection(sockfd, peer);}}~TcpServer() {}private:uint16_t _port;int _listensock;// TODObool _isrunning;
};

1、2.过程和多进程的一样,咱们直接看accept出新连接后怎么办

3.accept得到通信套接字后,将通信套接字描述符和套接字地址传参到ProcessConnection,创建ThreadData结构体,因为线程执行函数只有一个参数,想要将通信套接字描述符和套接字地址都传过去就只能封装一下,然后pthread_create创建新线程,线程又叫轻量级进程,依靠时间片并发交替运行,主线程执行完ProcessConnection后就是继续去接收新连接去了,子线程执行threadExcute,先detach使该线程退出无需主线程回收,然后将封装的结构转回类型,并利用套接字描述符进行通信

TCP底层详细剖析

socket接口

前两个参数分别指定套接字的IP层协议和传输层协议,第三个参数没用,然后函数会创建对应类型套接字的所有数据结构,包括struct file,struct socket,struct sock,然后返回该套接字的描述符

bind接口

给套接字绑定地址,也就是ip和端口,传参用的是套接字地址结构体,一般填写地址族,ip,port字段就完了,这个接口服务器在创建listen套接字时会用到,而客户端不能自己绑定,不然多个连接最后五元组是一样的,没得玩

listen接口

将一个套接字声明为监听套接字,从此这个套接字会像udp套接字一样是无连接的,即使socket创建它时是用的TCP协议。它将专门用来进行三次握手,当然三次握手是靠硬件中断推动,但listen套接字绝对是其数据结构基础,第二个参数用来描述listen套接字的全连接队列的大小,linux中这个队列大小是backlog+1

connect接口

先给客户端套接字随机绑定地址(ip加端口),然后封装SYN包,交给IP层,IP层封装报头,然后根据目的ip查路由表,得到下一跳ip和发送接口,一同交给数据链路层,网卡驱动根据下一跳ip查找arp缓存得到mac地址,然后添加mac头和校验位,写进发送接口对应网卡的发送缓冲区,然后写网卡的TDT寄存器,网卡用DMA读出数据HVY转换数据接口发出去,注意,此时报文中目的ip端口和源ip端口都很直接,就是那俩套接字绑定的,但等下就不是了,假如客户端是私网,服务器是公网,那下一跳很明显是路由器lan口ip,路由器网卡接收数据写进接收缓冲区,网卡触发硬件中断,中断控制器使目标cpu陷入内核,执行中断方法,从缓冲区读数据,然后看帧类型是ip帧,那就检查mac地址和crc校验,无问题就交给IP层,IP层会用wan口ip和序列端口替换源ip和端口,并且在地址转换表里记录(源ip,源port,目的ip,目的port)和(新ip,新port,目的IP,目的port)的映射,然后查路由表,确定下一跳ip和发送网卡,交给数据链路层发出去,当这个报文到达服务器后,网卡接收然后写进缓冲区,触发硬件中断,cpu执行中断方法,网卡驱动读出数据,根据帧类型是ip帧,需要检查校验位然后交给ip层,IP层根据报头里的协议类型交给tcp层,tcp层一看是syn报文,那就查三元组,这也是为什么服务器需要比客户端提前启动的原因了,服务器已经创建好了监听套接字并且accept搁那里阻塞,服务器进程在accept的全连接队列上阻塞,然后我们接着聊这次网卡的硬件中断,tcp层查三元组(协议,目的ip,目的端口)找到了监听套接字,那还说啥,在监听套接字的半连接队列上创建struct  request_sock,然后构建ack+syn捎带应答tcp报文,交给ip层,IP层填充ip报头,这个响应报文的四元组其实就是发送过来时ip和端口将’‘源‘’和‘’目的‘’反过来填就是了,从这里我们也可以知道,服务器这边的通信套接字的五元组其源ip和源port其实是广域网路由器的,并不是客户端主机的,而客户端主机那里的则是双方主机的(我的心里有你,而你只看见了中间的那个,就这样记),接着聊,紧接着交给数据链路层发送出去,路由器收到以后,到了ip层,根据地址转换表里的记录,将报文中的目的ip和端口改了,然后再根据新目的ip查路由表,后面一系列不再重复,最后交给客户端主机,客户端主机同样网卡接收,硬件中断处理,构建最后的ack然后重新发回去,发回去ack后客户端的connect就结束了,接下来就是开始发消息,服务器收到ack后,在tcp层根据三元组找到监听套接字,再将半连接队列中的struct  request_sock给升级成全连接队列里的struct  sock,唤醒全连接队列等待队列上的进程,服务器进程状态被设置为R,并且被切换到运行队列里,服务器继续执行accept,将全连接队列中的struct sock给创建对应的struct socket和struct file,然后返回套接字描述符,至此服务器accept也执行完毕,开始创建新进程或新线程和客户端,并通过通信套接字进程通信了,这样父进程或主线程只负责创建新连接,而不参与与客户端的通信,这也是我为什么说监听套接字是无连接的(就像一个海王,谁都可以来,那它谁也不爱)

write和read接口

还记得吗,udp套接字无连接是三元组,那发消息和接收消息都不知道对方是谁,所以用的是sendto和recvfrom,需要加上套接字地址结构体,而我们tcp套接字,除了listen套接字是无连接的,其他的都是成对的五元组通信套接字,可以直接用write和read来发送和接受消息,因为有链接,所以知道对方是谁

close接口

客户端这边键盘按下ctrl+c,那键盘触发硬件中断,cpu陷入内核,经一系列操作后终端模拟器收到字符,将其显示在终端上,然后write写进主设备,终端驱动给客户端设置SIGINT信号,然后唤醒客户端,客户端接下来处理时钟中断或软中断(write)等结束后,会处理信号,检查struct thread_info里的标志发现被设置了TIF_SIGPEDING,于是调用do_signal,从头开始遍历pending表,找到第一个设置了pending有没有block的信号,这里肯定是SIGINT了,那于是给cpu设置好新寄存器,然后切换到用户态,执行信号处理函数,具体就是,pcb中信号码设置为2,退出码是0不管,进程状态改为Z,从运行队列中除去,释放文件描述符表等资源,给父进程shell设置SIGCHLD信号然后唤醒wait的父进程,父进程根据客户端pcb中的退出信息构建好返回status,然后释放客户端pcb本身空间,这都没有问题,但我要说的是,在客户端文件描述符表中每个文件描述符都被close,其中的的通信套接字在调close时,会向服务器发送fin包,然后释放struct file和struct socket,只剩struct sock被OS管理,它还不能释放,因为要完成四次挥手,我们以多进程的服务器来看,网卡收到消息后写进网卡接收缓冲区,然后触发硬件中断往上解包,当收到fin包解包到传输层后,一看标志位是fin包,那就根据五元组找到套接字,然后将套接字的连接状态改成CLOSE_WAIT,唤醒接收缓冲区等待队列上的服务器进程,组装ack包响应给客户端,然后该次中断就结束了,至于被唤醒的服务器进程,继续执行read,缓冲区数据是0,然后看套接字状态,是CLOSE_WAIT,那read就返回0,表示对面不会再发数据了,read返回值是零,我们再看下面的逻辑,close通信套接字,那于是自个套接字状态改成LAST_ACK,并给客户端发送fin包,然后释放struct file和struct socket,struct sock由OS管理完成剩下四次挥手,然后服务器进程exit(0),那就是执行atexit()注册的处理函数,然后把所有struct FILE的用户级缓冲区都进行刷新,也就是调用对应系统调用,最后执行_exit(),设置pcb中退出码为0,信号码不管,状态设S,移除运行队列,清除资源,给父进程设SIGCHLD,这里服务器进程是孙子进程,也就是说父进程是INIT进程,然后INIT进程通过时钟中断定时wait僵尸进程,释放其资源。客户端收到ack,触发硬件中断,解包到tcp层,根据五元组找到套接字,将套接字状态改成TIME_WAIT,然后发送ack包给服务器套接字,服务器网卡接口收到消息后触发硬件中断,往上解包到tcp层,根据五元组找到套接字,然后直接把struct sock释放,至此,服务器端通信套接字彻底释放,而客户端套接字在两分钟内如果没有收到FIN包,那说明ack包已经成功被服务器收到了,因此服务器没有超时重传fin包,这时客户端套接字的struct sock也释放了,非常完美。由此引入两个问题,一个是TIME_WAIT状态是要求在两分钟内不再受到fin包则认为对方收到ack包,假如在此两分钟内收到了fin包,那就是服务器没收到ack所以超时重传了,这时服务器端就刷新时间,并重新发送ack包。TIME_WAIT作为主动断开连接的一方会进入的一个状态,会导致这段时间会占用该地址端口,假如是客户端那影响不大,因为客户端每次是connect随机绑定地址,而服务器则会因为旧套接字占用地址而重启服务器创建监听套接字后会bind失败,怎么解决呢?那就是给套接字设置SO_REUSEADDR,这样当一个地址被TIME_WAIT占据时,就允许一个且只允许一个活跃套接字bind这个地址,这就是端口复用,所以这就是为什么多进程服务器监听套接字要设置端口复用

read和recvfrom的返回值

read和recvfrom的返回值逻辑相同,如果大于零那就是读到的数据字节数,如果是-1那就是读数据出错,如果等于0那就是连接已断开(tcp才会出现这个返回值,udp是不会出现的)

具体逻辑:udp的话如果有数据就读数据,如果没数据就阻塞等待;tcp的话如果有数据就读数据,如果没数据就检查连接状态,如果是ESTABLISHED那就阻塞等待,如果是CLOSE_WAIT那就返回0(对端主动关闭连接,也只能是对方主动关闭,如果是你主动关闭,那后面还搁这读?)

端口复用

1.显式 bind vs 隐式绑定​

​类型​​定义​​示例​
​显式 bind通过 bind() 系统调用明确绑定地址和端口bind(sockfd, &addr, sizeof(addr))
​隐式绑定​由内核自动关联地址accept() 返回的通信套接字复用监听套接字的地址

2. SO_REUSEADDR ​​

  • ​允许绑定处于 TIME_WAIT 状态的地址​​。
  • ​不允许多个活跃套接字同时绑定同一地址​​。
  • 主要解决服务器重启问题

边界条件验证​

// 场景1:前一个套接字处于 TIME_WAIT
setsockopt(sock1, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bind(sock1, &addr, sizeof(addr));  // 成功(即使端口在 TIME_WAIT)// 场景2:前一个套接字仍活跃(未关闭)
setsockopt(sock2, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bind(sock2, &addr, sizeof(addr));  // 失败(errno=EADDRINUSE)

3. SO_REUSEPORT ​​

  • ​允许多个套接字同时显式绑定同一地址​​(IP:PORT)。
  • ​隐含 SO_REUSEADDR 的功能​​。
  • 所有套接字都需要设置 SO_REUSEPORT

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

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

相关文章

【理念●体系】模板规范篇:打造可标准化复用的 AI 项目骨架

【理念●体系】从零打造 Windows WSL Docker Anaconda PyCharm 的 AI 全链路开发体系-CSDN博客 【理念●体系】Windows AI 开发环境搭建实录&#xff1a;六层架构的逐步实现与路径治理指南-CSDN博客 【理念●体系】路径治理篇&#xff1a;打造可控、可迁移、可复现的 AI 开…

Skia---渐变色着色器

今天介绍的是实际工作中最常用到的着色器&#xff1a;渐变色着色器。 渐变色着色器是一个从一种颜色平滑的过渡到另一种颜色的效果&#xff0c;渐变色着色器的作用主要是增强图形的视觉吸引力。 线性渐变 Skia 里的线性渐变色着色器是最简单的渐变色着色器&#xff0c;它用于…

2025.07.09华为机考真题解析-第二题200分

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 02. 地铁线路故障预警系统 问题描述 LYA 负责管理一个城市的地铁网络系统。地铁网络由 n n n

数学建模:非线性规划:凸规划问题

一、定义凸集定义​​&#xff1a;设Ω是n维欧氏空间的一点集&#xff0c;若任意两点x₁∈Ω&#xff0c;x₂∈Ω&#xff0c;其连线上的所有点αx₁(1-α)x₂∈Ω&#xff0c;(0≤α≤1)&#xff0c;则称Ω为凸集。​​凸函数定义​​&#xff1a;给定函数f(x)(x∈D⊂Rⁿ)&…

ISIS | 广播网络中的 ISIS 伪节点 LSP

注&#xff1a;本文为 “ISIS | 伪节点 LSP” 相关合辑。 英文引文&#xff0c;机翻未校。 中文引文&#xff0c;略作重排。 如有内容异常&#xff0c;请看原文。 ISIS in Broadcast Network and Pseudonode LSP 广播网络中 的 ISIS 伪节点 LSP ISIS in broadcast network is…

ARIA UWB安全雷达主要产品型号与核心功能全解析

ARIA UWB雷达拥有LT系列与AHM系列两大产品线。LT103 XBT、LT102 V2、LT103 OEM等代表型号具备高精度定位、低功耗和强穿透能力&#xff0c;适用于工业自动化与物联网。AHM3D、AHM2D、AHM3DSC则专注三维检测和智能计算&#xff0c;广泛服务于医疗健康、安防监控等场景。Hydrogen…

NLP自然语言处理04 transformer架构模拟实现

总体架构输入部分代码实现:导包# -*-coding:utf-8-*- import matplotlib.pyplot as plt import numpy as np import torch import torch.nn as nn # -*-coding:utf-8-*- import copy import torch.nn.functional as F import math位置编码器部分词嵌入WordEmbedding# todo 作用…

记录一本书: Python机器学习:基于PyTorch和Scikit-Learn

记录一本书&#xff1a; Python机器学习&#xff1a;基于PyTorch和Scikit-Learn 作者&#xff1a;&#xff08;美&#xff09;塞巴斯蒂安拉施卡(Sebastian Raschka)&#xff08;美&#xff09;刘玉溪&#xff08;海登&#xff09;(Yuxi(Hayden)Liu) &#xff08;美&#xff09;…

Datomic数据库简介(TBC)

Datomic 数据库详细介绍 Datomic 是一个由 Rich Hickey&#xff08;Clojure 语言创始人&#xff09;设计的 不可变、时间感知、分布式数据库&#xff0c;专为现代应用程序设计&#xff0c;强调 数据不变性&#xff08;immutability&#xff09;、查询灵活性和可审计性。它结合…

xformers 完整安装教程【pip conda】(解决 conda 版本 pytorch 自适应安装 xformers)

我个人喜欢用 mamba&#xff08;conda&#xff09;创建环境&#xff0c;然后用 mamba 安装 pytorch CUDA&#xff08;如果需要使用 CUDA 编译&#xff09;&#xff0c;还有一些比如 gcc/g 等与 python 无关的一些工具。 但是最近我在扩充环境的时候&#xff0c;发现需要额外安…

VM虚拟机全版本网盘+免费本地网络穿透端口映射实时同步动态家庭IP教程

VM虚拟机全版本秘钥&#xff0c;文章末尾。 首先网络穿透的意义是让公网可以直接访问家庭电脑&#xff0c;这样本地电脑的硬件性能得以完全发挥&#xff0c;特别是在云服务器贵性能又没家庭电脑好&#xff0c;专线宽带又贵&#xff0c;第三方网络穿透贵的场景下。一般第三方网…

C++ - 仿 RabbitMQ 实现消息队列--项目介绍与环境搭建

目录 项目介绍 开发环境 技术选型 环境搭建 安装 wget(一般情况下默认会自带) 更换国内软件源 安装 lrzsz 传输工具 安装编译器 安装项目构建工具 make 安装调试器 安装 git 安装 cmake 安装 Protobuf 安装 Muduo 安装 SQLite3 安装 Gtest 项目介绍 首先说一下…

《目标检测模块实践手册:从原理到落地的尝试与分享》第一期

大家好&#xff0c;欢迎来到《目标检测模块实践手册》系列的第一篇。从今天开始&#xff0c;我想以一种 “实践记录者” 的身份&#xff0c;和大家聊聊在目标检测任务中那些形形色色的模块。这些内容没有权威结论&#xff0c;更多的是我在实际操作中的一些尝试、发现和踩过的坑…

C++11笑传之引用

C11前言列表初始化{}进行初始化initializer_list右值引用和移动语义左值与右值左值引用与右值引用引用延长生命周期右值引用和移动语义的使用场景左值引用移动构造和移动赋值右值引用在容器插入的提效引用折叠万能折叠完美转发前言 C11是C继98后的更新&#xff0c;其更新了许多…

瀚高数据库提交数据后,是否需要COMMIT(APP)

文章目录环境症状问题原因解决方案报错编码环境 系统平台&#xff1a; 版本&#xff1a;5.6.5,4.5 症状 瀚高数据库提交数据后&#xff0c;是否需要commit&#xff0c;瀚高数据库是否有配置项。 问题原因 瀚高数据库默认自动COMMIT&#xff08;提交数据&#xff09;&#…

深大计算机游戏开发实验三

主要步骤主角飞船的创建和移动边界设置以及护盾设置创建敌机自动生成敌机图层设置弹丸设置武器创建不同发射模式管理竞态条件击败敌机掉落升级道具不同敌机的生成分值显示实现退出游戏界面之后进入游戏的最高记录重置游戏界面失败后重新加载最记录不会重置任何时候在游戏界面按…

详解缓存淘汰策略:LRU

文章目录缓存淘汰策略LRU核心结构核心操作流程局限性源码走读AddGet缓存淘汰策略 缓存淘汰策略的存在是为了解决 缓存容量有限性 和 高缓存命中率 之间的矛盾。其核心目标是在有限的缓存空间内&#xff0c;尽可能提高缓存命中率 缓存容量有限性&#xff1a;缓存&#xff08;例…

什么是 Bootloader?怎么把它移植到 STM32 上?

一、Bootloader 是啥&#xff1f;它都干了些啥&#xff1f;想象一下你的 MCU&#xff08;比如 STM32&#xff09;是一个小机器人&#xff0c;上电之后第一件事&#xff0c;它不会立马开始“干正事”&#xff08;运行你的主程序&#xff09;&#xff0c;而是先去运行一个“开场引…

无人机避障——感知篇(Ego_Planner_v2中的滚动窗口实现动态实时感知建图grid_map ROS节点理解与参数调整影响)

处理器&#xff1a;Orin nx 双目视觉传感器&#xff1a;ZED2 实时感知建图方法&#xff1a;Vins Fusion Raycast &#xff08;VIO与射线投影法感知定位加建图方法&#xff09; 项目地址&#xff1a;https://github.com/ZJU-FAST-Lab/EGO-Planner-v2 【注意】&#xff1a;建…

26-计组-寻址方式

指令寻址与PC自增一、指令寻址方式定义&#xff1a;寻找下一条将要执行的指令地址的过程。 核心部件&#xff1a;程序计数器&#xff08;PC&#xff09;&#xff0c;用于指示待执行指令的地址。 执行流程&#xff1a;CPU根据PC值从主存取指令。取指后&#xff0c;PC自动自增&am…