【Linux網絡編程】IO多路轉接之poll

poll

  • 1.poll初始
  • 2.poll函數接口
  • 3.poll服務器
  • 4.poll的優點缺點

在這里插入圖片描述

點贊👍👍收藏🌟🌟關注💖💖
你的支持是對我最大的鼓勵,我們一起努力吧!😃😃

1.poll初始

poll也是一種linux中多路轉接的方案。它所對應的多路轉接方案主要是解決select兩個問題。

  1. select的文件描述符有上限的問題
  2. select每次都要重新設置關心的fd

下面通過poll接口來認識它是怎么解決select的問題的。

2.poll函數接口

在這里插入圖片描述

struct pollfd * fds:這里可以把它想象一個動態數組、數組或者new/malloc出來的結構體數組

nfds_t nfds:代表這個數組的長度

int timeout:純輸入型,時間單位ms

  1. 大于0:在timeout以內 阻塞,超過timeout非阻塞返回一次
  2. 等于0 :非阻塞
  3. 小于<0:阻塞

這個和select一模一樣的意思。用起來更簡單了。

返回值:同select一模一樣

  1. 大于0:表示有幾個fd就緒了
  2. 等于0:表示超時了
  3. 小于0:表示poll等待失敗了

poll的作用和select一模一樣:只負責等待!

在這里插入圖片描述

這個struct pollfd 結構體 在傳給poll表示 用戶->內核

int fd:你要關心一下這個fd哦

short events:關心的是這個fd的什么事件。我們把對應的事件設置進events里

輸入看:fd+events

當poll返回時這個struct pollfd 結構體 表示內核->用戶

你要關心的fd上面的events中有那些事件已經就緒啦

short revents:就緒事件由revents返回

輸出看:fd+revents

很顯然這種設計解決了這樣的問題:

  1. 輸入輸出分離!

現在,用戶->內核,內核->用戶,events和revents的分離!以前select就用一張位圖表示不同含義,因為輸入輸出分離了所以決定了poll不需要對參數進行重新設定

events和revents類型是整數,對應的事件如下:

在這里插入圖片描述

其中對我們來說常用的是POLLIN、POLLOUT、POLLERR ,這些都是大寫的宏每一個占一個比特位,不同比特位表示不同事件。

所以用戶->內核,只要將events設置成要關心的宏值,那么操作系統就幫我們進行關心了。當操作系統返回時只要把revents設置成對應的宏值,不就把那些事件就緒不就告訴我們了嗎。

因為它的類型是short而沒有用操作系統自己封裝的各種各樣的結構體,所以對于事件的設計,我們自己用戶檢測事件有沒有設置或者就緒一定要由我們自己來做,按位與,按位或這樣的操作。

  1. select等待fd有上限的問題

struct pollfd *fds不是一個數組嗎,nfds_t nfds不就是該數組大小也就是上限嗎,你怎么說poll解決了select等待fd上限的問題?

select是一個具體的數據類型fd_set,既然是一個具體的類型那就直接決定了數據類型大小只能由你的編譯環境自己定,今天不一樣了,因為這個數組由我們自己說的算!

3.poll服務器

前面不是寫了select服務器嗎,現在我們把它改成poll服務器

錯誤碼封裝

#pragma onceenum
{USAGG_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR
};

日志封裝

#pragma once#include<iostream>
#include<string>
#include<stdio.h>
#include <cstdarg>
#include<ctime>
#include<sys/types.h>
#include<unistd.h>
#include<fstream>#define DEBUG  0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"const char* level_to_string(int level)
{switch(level){case DEBUG: return "DEBUG";case NORMAL: return "NORMAL";case WARNING: return "WARNING";case ERROR: return "ERROR";case FATAL: return "FATAL";}
}//時間戳變成時間
char* timeChange()
{time_t now=time(nullptr);struct tm* local_time;local_time=localtime(&now);static char time_str[1024];snprintf(time_str,sizeof time_str,"%d-%d-%d %d-%d-%d",local_time->tm_year + 1900,\local_time->tm_mon + 1, local_time->tm_mday,local_time->tm_hour, \local_time->tm_min, local_time->tm_sec);return time_str;
}void logMessage(int level,const char* format,...)
{//[日志等級] [時間戳/時間] [pid] [message]//[WARNING] [2024-3-21 10-46-03] [123] [創建sock失敗]
#define NUM 1024//獲取時間char* nowtime=timeChange();char logprefix[NUM];snprintf(logprefix,sizeof logprefix,"[%s][%s][pid: %d]",level_to_string(level),nowtime,getpid());//char logconten[NUM];va_list arg;va_start(arg,format);vsnprintf(logconten,sizeof logconten,format,arg);std::cout<<logprefix<<logconten<<std::endl;};

套接字封裝

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include "err.hpp"using namespace std;class Sock
{const static int backlog = 32;public:static int sock(){// 1. 創建socket文件套接字對象int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){logMessage(FATAL, "create socket error");exit(SOCKET_ERR);}logMessage(NORMAL, "create socket success: %d", sock);int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt));return sock;}static void Bind(int sock,int port){// 2. bind綁定自己的網絡信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind socket error");exit(BIND_ERR);}logMessage(NORMAL, "bind socket success");}static void Listen(int sock){// 3. 設置socket 為監聽狀態if (listen(sock, backlog) < 0) {logMessage(FATAL, "listen socket error");exit(LISTEN_ERR);}logMessage(NORMAL, "listen socket success");}static int Accept(int listensock, std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(listensock, (struct sockaddr *)&peer, &len);if (sock < 0)logMessage(ERROR, "accept error, next");else{logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?*clientip = inet_ntoa(peer.sin_addr);*clientport = ntohs(peer.sin_port);}return sock;}
};

調用邏輯

#include "pollServer.hpp"
#include "err.hpp"
#include <memory>static void usage(std::string proc)
{std::cerr << "Usage:\n\t" << proc << " port" << "\n\n";
}string service(string request)
{return request;
}int main(int argc,char* argv[])
{if(argc != 2){usage(argv[0]);exit(USAGG_ERR);}unique_ptr<pollServer> usl(new pollServer(service,atoi(argv[1])));usl->initServer();usl->start();return 0;

今天poll服務器,也是需要一個數組。只不過以前select數組純純的保存文件描述符,poll這里必須是保存struct pollfd結構體的數組。

一般我們如果把fd設置為-1或者小于0的值,操作系統就不會關注這樣的文件描述符了。它只會關心大于等于0的fd。

在這里插入圖片描述

因此我們要重新定義一個指針,構造析構都跟著改一下

class pollServer
{static const int defaultport = 8080;static const int defaultfd = -1;static const int defaultnum=2048;using func_t=function<string(string)>;public:pollServer(func_t f,int port = defaultport) : _cbs(f),_port(port), _listensock(-1), _rfds(nullptr){}~pollServer(){if (_listensock != defaultfd)close(_listensock);if (_rfds)delete[] _rfds;}private:int _listensock;int _port;struct pollfd* _rfds;func_t _cbs;
};

接下來初始化服務器這里創建結構體數組大小自己隨意定

void initServer()
{// 1.創建套接字_listensock = Sock::sock();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);_rfds=new struct pollfd[defaultnum];//大小這里自己隨便定for (int i = 0; i < defaultnum; ++i)//數組初始化{_rfds[i].fd = defaultfd;_rfds[i].events=0;_rfds[i].revents=0;}_rfds[0].fd = _listensock; // 這個位置后面就不變了_rfds[0].events=POLLIN; //告訴內核幫我關心_listensock讀事件
}

打印這里也改一下

void print()
{for (int i = 0; i < defaultnum; ++i){if (_rfds[i].fd != defaultfd)cout << _rfds[i].fd << " ";}cout << endl;
}

現在當我們啟動服務之后,就不需要每次調用select之前都需要重新設置fd了添加到讀文件描述符集里面了,然后才能添加到select里面。現在直接把數組給poll。所以能明顯感覺到poll比select簡單

void start()
{int timenout=1000;for (;;){int n=poll(_rfds,defaultnum,timenout);switch (n){case 0:logMessage(NORMAL, "timeout...");break;case -1:logMessage(WARNING, "poll error, code: %d, err string: %s", errno, strerror(errno));break;default:// 說明有事件就緒了,目前只有一個監聽事件就緒了logMessage(NORMAL, "have event ready!");HandlerEvent();//這里不用傳了,因為就緒事件就在_rfds里break;}}
}

今天這里我們只處理讀事件就緒的情況

// 1.handler event _rfds 中,不僅僅是有一個fd是就緒的,可能存在多個
// 2.我們的poll目前只處理了read事件
void HandlerEvent()
{// 你怎么知道那些fd就緒了呢? 我不知道,我只能遍歷for (int i = 0; i < defaultnum; ++i){// 不合法fdif (_rfds[i].fd == defaultfd)continue;// 合法fd,但必須曾經向內核設置過幫我關心對應fd讀事件才能往下走if (!(_rfds[i].events & POLLIN))continue;if (_rfds[i].fd == _listensock && _rfds[i].revents & POLLIN)Accepter(_listensock);else if (_rfds[i].revents & POLLIN)Recver(i);}

處理_listensock讀就緒事件

void Accepter(int listensock)
{logMessage(DEBUG, "Accepter in");// 走到這里,accept 函數,會不會被阻塞?// 走到這里就是, poll 告送我,_listensock就緒了,然后才能執行下面代碼string clientip;uint16_t clientport;int sock = Sock::Accept(listensock, &clientip, &clientport);  accept = 等 + 獲取if (sock < 0)return;logMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);// 得到一個sock套接字后,然后我們可以直接進行read/recv嗎? 不能,整個代碼只有poll有資格檢測事件是否就緒// 將新的sock 托管給poll!// 將新的sock,托管給poll的本質,其實就是將sock,添加到_rfds數組里!int i = 0;for (; i < defaultnum; ++i){if (_rfds[i].fd != defaultfd)continue;elsebreak;}if (i == defaultnum){logMessage(WARNING, "server if full, please wait");close(sock);}else{_rfds[i].fd = sock;_rfds[i].events = POLLIN;_rfds[i].revents = 0;}print();logMessage(DEBUG, "Accepter out");
}

處理普通sock讀就緒事件

void ResetItem(int i)
{_rfds[i].fd = defaultfd;_rfds[i].events = 0;_rfds[i].revents = 0;
}void Recver(int pos)
{logMessage(DEBUG, "in Recver");// 1. 讀取request// 這樣讀取是有問題的!char buffer[1024];ssize_t s = recv(_rfds[pos].fd, buffer, sizeof(buffer) - 1, 0); // 這里在進行讀取的時候,會不會被阻塞?if (s > 0)                                                      // 讀取成功{buffer[s] = 0;logMessage(NORMAL, "client# %s", buffer);}else if (s == 0) // 對方關閉了文件描述符{close(_rfds[pos].fd);ResetItem(pos);logMessage(NORMAL, "client quit");return;}else // 讀取失敗{close(_rfds[pos].fd);ResetItem(pos);logMessage(ERROR, "client quit: %s", strerror(errno));return;}// 2. 處理requeststd::string response = _cbs(buffer);// 3. 返回response// write bugwrite(_rfds[pos].fd, response.c_str(), response.size());logMessage(DEBUG, "out Recver");
}

在這里插入圖片描述

自此poll服務器就已經寫完了,很顯然poll服務器主體代碼和select服務器一模一樣,只不過poll在進行事件監聽的時候明顯要比select簡潔,而且數組沒有上限。

poll服務器完整代碼

#pragma once#include <iostream>
#include <functional>
#include <poll.h>
#include "sock.hpp"using namespace std;class pollServer
{static const int defaultport = 8080;static const int defaultfd = -1;static const int defaultnum = 2048;using func_t = function<string(string)>;public:pollServer(func_t f, int port = defaultport) : _cbs(f), _port(port), _listensock(-1), _rfds(nullptr){}void initServer(){// 1.創建套接字_listensock = Sock::sock();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);_rfds = new struct pollfd[defaultnum]; // 大小這里自己隨便定for (int i = 0; i < defaultnum; ++i)    ResetItem(i);_rfds[0].fd = _listensock; // 這個位置后面就不變了_rfds[0].events = POLLIN;  // 告訴內核幫我關心_listensock讀事件}void print(){for (int i = 0; i < defaultnum; ++i){if (_rfds[i].fd != defaultfd)cout << _rfds[i].fd << " ";}cout << endl;}void Accepter(int listensock){logMessage(DEBUG, "Accepter in");// 走到這里,accept 函數,會不會被阻塞?// 走到這里就是, poll 告送我,_listensock就緒了,然后才能執行下面代碼string clientip;uint16_t clientport;int sock = Sock::Accept(listensock, &clientip, &clientport);  accept = 等 + 獲取if (sock < 0)return;logMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);// 得到一個sock套接字后,然后我們可以直接進行read/recv嗎? 不能,整個代碼只有poll有資格檢測事件是否就緒// 將新的sock 托管給poll!// 將新的sock,托管給select的本質,其實就是將sock,添加到fdarray數組里!int i = 0;for (; i < defaultnum; ++i){if (_rfds[i].fd != defaultfd)continue;elsebreak;}if (i == defaultnum){logMessage(WARNING, "server if full, please wait");close(sock);}else{_rfds[i].fd = sock;_rfds[i].events = POLLIN;_rfds[i].revents = 0;}print();logMessage(DEBUG, "Accepter out");}void ResetItem(int i){_rfds[i].fd = defaultfd;_rfds[i].events = 0;_rfds[i].revents = 0;}void Recver(int pos){logMessage(DEBUG, "in Recver");// 1. 讀取request// 這樣讀取是有問題的!char buffer[1024];ssize_t s = recv(_rfds[pos].fd, buffer, sizeof(buffer) - 1, 0); // 這里在進行讀取的時候,會不會被阻塞?if (s > 0)                                                      // 讀取成功{buffer[s] = 0;logMessage(NORMAL, "client# %s", buffer);}else if (s == 0) // 對方關閉了文件描述符{close(_rfds[pos].fd);ResetItem(pos);logMessage(NORMAL, "client quit");return;}else // 讀取失敗{close(_rfds[pos].fd);ResetItem(pos);logMessage(ERROR, "client quit: %s", strerror(errno));return;}// 2. 處理requeststd::string response = _cbs(buffer);// 3. 返回response// write bugwrite(_rfds[pos].fd, response.c_str(), response.size());logMessage(DEBUG, "out Recver");}// 1.handler event _rfds 中,不僅僅是有一個fd是就緒的,可能存在多個// 2.我們的poll目前只處理了read事件void HandlerEvent(){// 你怎么知道那些fd就緒了呢? 我不知道,我只能遍歷for (int i = 0; i < defaultnum; ++i){// 不合法fdif (_rfds[i].fd == defaultfd)continue;// 合法fd,但必須曾經向內核設置過幫我關心對應fd讀事件才能往下走if (!(_rfds[i].events & POLLIN))continue;if (_rfds[i].fd == _listensock && _rfds[i].revents & POLLIN)Accepter(_listensock);else if (_rfds[i].revents & POLLIN)Recver(i);}}void start(){int timenout = -1;for (;;){int n = poll(_rfds, defaultnum, timenout);switch (n){case 0:logMessage(NORMAL, "timeout...");break;case -1:logMessage(WARNING, "poll error, code: %d, err string: %s", errno, strerror(errno));break;default:// 說明有事件就緒了,目前只有一個監聽事件就緒了logMessage(NORMAL, "have event ready!");HandlerEvent(); // 這里不用傳了,因為就緒事件就在_rfds里break;}}}~pollServer(){if (_listensock != defaultfd)close(_listensock);if (_rfds)delete[] _rfds;}private:int _listensock;int _port;struct pollfd *_rfds;func_t _cbs;
};

4.poll的優點缺點

poll的優點就不用過多介紹,輸入輸出分離,而且沒有select上限的問題

poll的主要缺點依舊是遍歷問題,因為我們交給poll多個文件描述符,poll在底層去遍歷去查找。隨著等待的文件描述符變多,poll要線性遍歷的方式檢測所有文件描述符,這勢必會帶來效率的降低 。

正是因為poll有這樣的問題,所有才有了下一個多路轉接之epoll

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/12795.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/12795.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/12795.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Unity設計模式之工廠模式

什么是工廠模式&#xff1f; 工廠是一種創建型設計模式。通俗來講就是提供一種封裝對象創建的方式&#xff0c;將對象的創建和使用區分開。就是Unity里面通常用到的創建和管理對象。 工廠模式有什么優點&#xff1f; 1、封裝對象的創建方式&#xff0c;使其更加靈活、易于管理…

vue elementui時間選擇器返回的時間轉成年月日-格式

關鍵點&#xff1a;value-format“yyyy-MM-dd” <template><el-date-pickerv-model"date"type"date"placeholder"選擇日期"format"yyyy-MM-dd"value-format"yyyy-MM-dd"></el-date-picker> </template…

【教學類-55-04】20240515圖層順序挑戰(四格長條紙加黑色邊框、4*4、7張,不重復5400張,16坐標點顏色哈希值去重、保留7色)

背景需求&#xff1a; 前文實現了7張色彩紙條加上黑色邊框的需求。 【教學類-55-02】20240512圖層順序挑戰&#xff08;四格長條紙加黑色邊框、4*4、7張 、43200張去掉非7色有23040張&#xff0c;哈希算法快速去重剩余1221張&#xff09;-CSDN博客文章瀏覽閱讀1k次&#xff0…

GPT-4o模型介紹和使用方法

大家好,我是herosunly。985院校碩士畢業,現擔任算法研究員一職,熱衷于機器學習算法研究與應用。曾獲得阿里云天池比賽第一名,CCF比賽第二名,科大訊飛比賽第三名。擁有多項發明專利。對機器學習和深度學習擁有自己獨到的見解。曾經輔導過若干個非計算機專業的學生進入到算法…

9個優質免費視頻素材網站推薦丨2024年最新資源合集

在短視頻火爆的時代&#xff0c;高清、無水印、可商用的視頻素材變得尤為重要。下面是我精心整理的9個常用免費視頻素材網站&#xff0c;適合各類視頻創作者。希望你能找到滿意的素材&#xff01; 一、視頻素材 1. 蛙學府 優點&#xff1a;豐富的正版商用素材&#xff0c;涵蓋…

AI 繪畫神器 Fooocus 高級用法:設置、風格、模型、高級設置

本文收錄于《AI繪畫從入門到精通》專欄&#xff0c;專欄總目錄&#xff1a;點這里&#xff0c;訂閱后可閱讀專欄內所有文章。 大家好&#xff0c;我是水滴~~ 本文精選了一系列高級技巧和細致調整&#xff0c;旨在提升 Fooocus 用戶在利用 Stable Diffusion 模型進行圖像生成時的…

vue使用marked和highlight.js實現代碼高亮效果

marked是對markdown進行解析的插件&#xff0c;它可以把markdown語法解析成html語法&#xff0c;從而實現頁面效果&#xff0c;而highlight.js是對解析出的代碼實現高亮效果 效果&#xff1a; 安裝&#xff1a;避免踩我走的坑&#xff0c;安裝盡量按照這個版本安裝 npm install…

Java實現ZIP壓縮并支持設置密碼:輕松上手指南

在日常開發中&#xff0c;我們常常需要對文件進行壓縮處理&#xff0c;尤其是當文件需要傳輸或存儲時&#xff0c;壓縮不僅能節省空間&#xff0c;還能提升效率。而為壓縮文件添加密碼則為數據安全提供了額外保障。本文將指導你如何在Java中實現ZIP文件的壓縮&#xff0c;并為其…

Linux進程——進程地址空間

前言&#xff1a;在講完環境變量后&#xff0c;相信大家對Linux有更進一步的認識&#xff0c;而Linux進程概念到這也快接近尾聲了&#xff0c;現在我們了解Linux進程中的地址空間&#xff01; 本篇主要內容&#xff1a; 了解程序地址空間 理解進程地址空間 探究頁表和虛擬地址空…

matlab使用教程(71)—控制坐標區布局

1.與位置相關的屬性和函數 有幾個屬性和函數可用于獲取和設置坐標區的大小與位置。下表摘要顯示了這些屬性和函數。 函數或屬性描述 OuterPosition 屬性 使用此屬性可以查詢或更改坐標區的外邊界&#xff0c;包括標題、標簽和邊距。要更改外邊界&#xff0c;請將此屬性指定為…

MySQL、JDBC復盤及規劃

數據庫仍有習題尚未做完&#xff0c;策略從一天做完改為每天5到10題&#xff0c;以此達到掌握和復習的效果&#xff0c;JDBC的六部仍需每天練習&#xff0c;從明天開始正式進行JavaWeb的學習&#xff0c;預計持續到七月中旬&#xff0c;還會完成一個書城項目&#xff0c;六月底…

基于Hadoop的課程診改大數據可視化分析研究與應用

基于Hadoop的課程診改大數據可視化分析研究與應用 “A Study and Application of Big Data Visualization Analysis for Course Diagnosis and Improvement based on Hadoop” 完整下載鏈接:基于Hadoop的課程診改大數據可視化分析研究與應用 文章目錄 基于Hadoop的課程診改大…

Vue 快速入門:Vue初級

語法規則 前端渲染 渲染有幾種方式&#xff1a;原生js、js模板、Vue模板語法 原生js 使用字符串拼接 js模板語法 Vue.js 模板語法概述 Vue.js 是一個用于構建用戶界面的漸進式框架&#xff0c;其模板語法非常靈活和直觀。Vue 的模板語法基于 HTML&#xff0c;可以通過指令…

Symbol類型的作用

在TypeScript&#xff08;和JavaScript&#xff09;中&#xff0c;Symbol 是一個內置對象&#xff0c;它提供了一個唯一的且不可改變的數據類型&#xff0c;用于作為對象的鍵&#xff08;key&#xff09;。這種類型主要用于避免命名沖突&#xff0c;特別是在使用像 for...in 或…

kali更換鏡像源

vim /etc/apt/sources.list.d/docker.list 或 vim /ect/apt/sources.list #清華大學源 deb http://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib non-free deb-src https://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib non-free #中科大 de…

PID調節常見的問題----積分飽和等問題--參考學習

1&#xff0c; PID控制學習筆記之三—積分的處理 https://zhuanlan.zhihu.com/p/264238608 2&#xff0c;PID控制參數整定&#xff08;調節方法&#xff09;原理圖示MATLAB調試 https://blog.csdn.net/viafcccy/article/details/107988093 3&#xff0c;如何理解PID控制算法中…

Java為什么會成為現在主流的編程語言

Java為什么會成為現在的主流語言 前言一、Java語言概述Java是什么為什么大多數人會選擇從事Java為什么從事Java的工作者數量從年遞減 二、Java語言的特點簡單性面向對象分布式&#xff08;微服務&#xff09;健壯性安全性體系結構中立可移植性解釋型高性能多線程動態性 三、Jav…

UDP多對多組播通信

廣播和多播僅應用于UDP。TCP是一個面向連接的協議&#xff0c;TCP一定是點對點的&#xff0c;一點是兩個主機來建立連接的&#xff0c;TCP肯定是單播。只有UDP才會使用廣播和組播。 如下示例實現一個UDP多對多的組播通信&#xff0c;進程中有收、發兩個線程&#xff0c;分別表…

6款電腦精選工具軟件推薦!

AI視頻生成&#xff1a;小說文案智能分鏡智能識別角色和場景批量Ai繪圖自動配音添加音樂一鍵合成視頻https://aitools.jurilu.com/ 1.IP地址查看工具——純真ip數據庫 純真IP數據庫是一個易于操作的IP地址查詢工具&#xff0c;它允許用戶通過輸入IP地址來查詢其對應的地理位置…

Django創建網站的地基

相關文檔 1、為新網站創建一個文件夾&#xff08;這里是&#xff1a;locallibrary&#xff09; D:\django>mkdir locallibraryD:\django>cd locallibraryD:\django\locallibrary>dirVolume in drive D is 新加卷Volume Serial Number is B68C-03F7Directory of D:\dj…