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
英文地址,請注明出處:http://en.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…