live555源碼學習(1)

1 基礎組件

live項目主要包含了四個基礎庫、程序入口類(mediaServer)和測試程序(testProgs)。四個基礎庫是UsageEnvironment、BasicUsageEnvironment、groupsock和liveMedia

UsageEnvironment

抽象了兩個類UsageEnvironment和TaskScheduler,UsageEnvironment表示整個運行環境,提供錯誤記錄和輸出的功能。TaskScheduler表示任務調度中心,用于異步事件的讀取和處理。該庫中還有一個抽象類HashTable,這是一個通用的HashTable,在整個項目中都可以使用它。

BasicUsageEnvironment

UsageEnvironment和TaskScheduler的具體實現類

groupsock

網絡接口的封裝,用于數據包的接收和發送,同時支持多播和單播。groupsock庫中包括了GroupEId、Groupsock、GroupsockHelper、NetAddress、NetInterface等類。GroupsockHelper類主要用于讀寫Socket。

liveMedia

基于基類Medium,實現各種流媒體和編解碼類型結構,定義了source(生產者)和sink(消費者)操作。

mediaServer

mediaServer下的live555MediaServer提供了main函數,DynamicRTSPServer繼承了RTSPServer并重寫了虛函數lookupServerMediaSession

2 主程序

2.1 基本概念和實體

MediaServer是服務器的抽象。

  • 它創建用于接受客戶端連接的socket。它還是其他實體的容器。

ClientConnection是與客戶端的數據連接的抽象。

  • 當客戶端連接服務器時,MediaServer創建ClientConnection的實例,保存在成員fClientConnections[]中。

MediaSession是媒體的抽象。

  • 當用戶請求建立媒體連接時,創建MediaSession實例,保存在MediaServer的成員fServerMediaSessions[]中。這是一個Hash表,key值是媒體的名字。

媒體中可以有多個通道,MediaSubsession是媒體通道的抽象。

  • 創建MediaSession時,同時為其中的通道創建MediaSubsession實例,保存在成員fSubsessionHead指向的鏈表中。

ClientSession是與客戶端的對話的抽象,承載在ClientConnection上。

  • 當客戶端請求開始播放時,創建ClientSession實例,保存在成員fClientSessions[]中。這是一個Hash表,key值是全局唯一的session id。

StreamState是ClientSession用于掛接到MediaSubsession的中介。

  • ClientSession要掛接到MediaSession上,獲得媒體源。它的成員fStreamStates[]引用MediaSession中的MediaSubsession實例。
  • fStreamStates[]是一個數組,這里的trackNum指它的索引。

2.2 main函數

main函數創建任務調度器,創建RTSPServer實例,將它的socket置于調度器的監聽下,最后運行調度器,處理socket事件。

  • 調用BasicTaskScheduler::createNew()創建任務調度器,這是一個BasicTaskScheduler實例。
  • 引用調度器實例,創建BasicUsageEnvironment實例。
  • 調用DynamicRTSPServer::createNew(),創建RTSPServer實例。
    • 調用GenericMediaServer::setUpOurSocket()創建TCP socket。createNew()的參數指定本地端口號,這里先指定端口號554。但綁定可能失敗,如果失敗了,main()會再次調用createNew(),用端口號8554再重試一次。
    • 創建的socket保存在GenericMediaServer的成員fServerSocket中。
    • 調用TaskSheduler::turnOnBackgroundReadHandling()將socket置于監聽狀態,回調函數為GenericMediaServer::incomingConnectionHandler()。
GenericMediaServer
::GenericMediaServer(UsageEnvironment& env, int ourSocketIPv4, int ourSocketIPv6, Port ourPort,unsigned reclamationSeconds): Medium(env),fServerSocketIPv4(ourSocketIPv4), fServerSocketIPv6(ourSocketIPv6),fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),fClientSessions(HashTable::create(STRING_HASH_KEYS)),fPreviousClientSessionId(0),fTLSCertificateFileName(NULL), fTLSPrivateKeyFileName(NULL) {ignoreSigPipeOnSocket(fServerSocketIPv4); // so that clients on the same host that are killed don't also kill usignoreSigPipeOnSocket(fServerSocketIPv6); // ditto// Arrange to handle connections from others:env.taskScheduler().turnOnBackgroundReadHandling(fServerSocketIPv4, incomingConnectionHandlerIPv4, this);env.taskScheduler().turnOnBackgroundReadHandling(fServerSocketIPv6, incomingConnectionHandlerIPv6, this);
}
  void turnOnBackgroundReadHandling(int socketNum, BackgroundHandlerProc* handlerProc, void* clientData) {setBackgroundHandling(socketNum, SOCKET_READABLE, handlerProc, clientData);}
void BasicTaskScheduler::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) {if (socketNum < 0) return;
#if !defined(__WIN32__) && !defined(_WIN32) && defined(FD_SETSIZE)if (socketNum >= (int)(FD_SETSIZE)) return;
#endifFD_CLR((unsigned)socketNum, &fReadSet);FD_CLR((unsigned)socketNum, &fWriteSet);FD_CLR((unsigned)socketNum, &fExceptionSet);if (conditionSet == 0) {fHandlers->clearHandler(socketNum);if (socketNum+1 == fMaxNumSockets) {--fMaxNumSockets;}} else {fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData);if (socketNum+1 > fMaxNumSockets) {fMaxNumSockets = socketNum+1;}if (conditionSet&SOCKET_READABLE) FD_SET((unsigned)socketNum, &fReadSet);if (conditionSet&SOCKET_WRITABLE) FD_SET((unsigned)socketNum, &fWriteSet);if (conditionSet&SOCKET_EXCEPTION) FD_SET((unsigned)socketNum, &fExceptionSet);}
}
  • 調用RTSPServer::rtspURLPrefix()得到URL信息,其中調用getourIPAddress()得到本地地址。
  • 調用BasicTaskScheduler0::doEventLoop()調度任務。

GenericMediaServer::setUpOurSocket()創建TCP socket。

  • 調用setupStreamSocket()創建TCP socket。
  • 調用increaseSendBufferTo()重新設置增加緩存大小。
  • 調用listen()開始監聽。

BasicTaskScheduler0::doEventLoop()消息循環。

  • 循環調用BasicTaskScheduler::SingleStep()
    • 為所有需要操作的socket執行select。
    • 找到第一個socket?handler執行。
    • 找到第一個響應事件執行。
    • 找到第一個延遲任務執行。

2.3?GenericMediaServer::incomingConnectionHandler()

當有新的數據連接請求時,GenericMediaServer::incomingConnectionHandler()被調用。其中調用incomingConnectionHandlerOnSocket(),參數是成員fServerSocket。

  • 調用accept(),返回新數據連接的socket,同時得到客戶端的地址。
  • 調用makeSocketNonBlocking()設置socket為非阻塞模式。
  • 調用increaseSendBufferTo()增加發送緩存大小。
  • 用新連接的socket和客戶端地址,調用RTSPServer::createNewClientConnection()。

在createNewClientConnection()中

  • 創建RTSPClientConnection實例。
    • ClientConnection的成員fOurSocket保存新連接的socket值,成員fClientAddr保存客戶端地址。
    • GenericMediaServer的成員fClientConnections->Add保存ClientConnection的實例。
    • 調用TaskScheduler::setBackgroundHandling(),將新連接的socket置于監聽狀態,回調函數為ClientConnection::incomingRequestHandler()。
  • 調用RTSPClientConnection::resetRequestBuffer()復位與接收緩存有關的位置指示變量。

2.4?ClientConnection::incomingRequestHandler()

當socket有數據到達時,ClientConnection::incomingRequestHandler()被調用。

  • 調用readSocket()讀取數據,這是RTSP請求字符串。接收緩存是成員fRequestBuffer[]。
  • 調用RTSPClientConnection::handleRequestBytes解析RTSP請求并處理。
    • 調用parseRTSPRequestString()解析請求字符串,得到RTSP命令字及參數,包括:cmdName請求命令字(如:OPTIONS),urlSuffix是文件名,cseq是請求序列號。
    • 后面根據cmdName,調用不同的函數處理。如handleCmd_OPTIONS()處理OPTIONS請求等。
    • 對于SETUP請求,調用GenericMediaServer::createNewClientSessionWithId創建ClientSession實例。
    • 對于SETUP之后的請求,parseRTSPRequestString()得到有效的sessionIdStr,調用GenericMediaServer::lookupClientSession()得到對應的ClientSession實例。然后調用RTSPClientSession::handleCmd_withinSession(),再調用相應的處理函數。
	  if (authenticationOK("SETUP", urlTotalSuffix, (char const*)fRequestBuffer)) {clientSession= (RTSPServer::RTSPClientSession*)fOurRTSPServer.createNewClientSessionWithId();} else {areAuthenticated = False;}
      if (requestIncludedSessionId) {clientSession= (RTSPServer::RTSPClientSession*)(fOurRTSPServer.lookupClientSession(sessionIdStr));if (clientSession != NULL) clientSession->noteLiveness();}
else if (strcmp(cmdName, "TEARDOWN") == 0|| strcmp(cmdName, "PLAY") == 0|| strcmp(cmdName, "PAUSE") == 0|| strcmp(cmdName, "GET_PARAMETER") == 0|| strcmp(cmdName, "SET_PARAMETER") == 0) {if (clientSession != NULL) {clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);}

2.5?RTSPClientConnection::handleCmd_OPTIONS

填充回復字符串,告訴客戶端支持哪些請求

void RTSPServer::RTSPClientConnection::handleCmd_OPTIONS() {snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,"RTSP/1.0 200 OK\r\nCSeq: %s\r\n%sPublic: %s\r\n\r\n",fCurrentCSeq, dateHeader(), fOurRTSPServer.allowedCommandNames());
}

2.6?RTSPClientConnection::handleCmd_DESCRIBE

  • 調用DynamicRTSPServer::lookupServerMediaSession()查找ServerMediaSession實例,因為這時實例還不存在,所以這里會先創建一個。執行回調RTSPClientConnection::handleCmd_DESCRIBE_afterLookup
  • 用這個ServerMediaSeesin實例調用generateSDPDescription()。
    • 調用ourIPAddress()得到本地地址。
    • 構建會話級sdp字符串,本地地址是它的一個組成部分。
    • 遍歷ServerMediaSession的成員fSubsessionHead,對其中的subsession,調用OnDemandServerMediaSubsession::sdpLines()得到媒體級sdp字符串。
    • 連接會話級sdp字符串和媒體級sdp字符串。
  • 調用RTSPServer::rtspURL()得到URL信息。這是頭部字符串的一部分。

DynamicRTSPServer::lookupServerMediaSession()根據key值,在GenericMediaServer的成員fSerMediaSessions中查找對應的實例。fServerMediaSessesions是一個Hash表。lookupServerMediaSession()實現:

  • fopen打開文件,如test.264
  • 實例不存在調用全局函數createNewSMS()創建新的MediaSession實例。并調用GenericMediaServer::addServerMediaSession()將它加入成員fServerMediaSessions。

在全局函數createSMS()中

  • 從文件名test.264中,解析出擴展名“.264”。根據擴展名做不同處理。 對于擴展名“.264”,NEW_SMS("H.264 Video"),創建ServerMediaSession實例。
  • 調用H264VideoFileServerMediaSubsession::createNew(),它創建ServerMediaSubsession實例,文件名保存在成員fFileName中。后面創建FileSource實例時將用到它。
  • 調用ServerMediaSession::addSubsession把這個ServerMediaSubsession加入ServerMediaSession的子session表。ServerMediaSession的成員fSubsesisionHead和fSubsessionTail指向這個表的首部和尾部。

OnDemandServerMediaSubsession::sdpLines()得到子session的sdp字符串。sdpLines實現:

  • 調用createNewStreamSource()創建FramedSource實例
  • 調用createGroupsock()創建Groupsock實例
  • 基于FramedSource和Groupsock實例,調用createNewRTPSink()創建RTPSink實例。
  • 調用setSDPLinesFromRTPSink()。其中調用RTPSink的接口構建sdp字符串。
  • 清理掉以上創建的FramedSource、Groupsock、和RTPSink實例。這時只有ServerMediaSession和ServerMediaSubsession的實例保留下來。

2.7?RTSPClientSession::handleCmd_SETUP

先調用createNewClientSessionWithId()創建ClientSession實例,再調用handleCmd_SETUP()處理。注意handleCmd_SETUP是RTSPClientSession的成員函數,handleCmd_DESCRIBE是RTSPClientConnection的成員函數。

createNewClientSessionWithId()實現:

  • 調用our_random32()生成sessionid。
  • 用sessionid調用RTSPServer::createNewClientSession()創建RTSPClientSession實例。
    • ClientConnection的構造函數調用TaskScheduler::resheduleDelayedTask()設置延時任務,回調函數是ClientSession::LivenessTimeoutTask()。這個函數的作用是在這個Client session實例長時間不工作時,刪除它。
  • 將這個實例加入GenericMediaServer的成員fClientSessions,這是一個ClientSession實例的Hash表。

RTSPClientSession::handleCmd_SETUP()實現:

  • 調用lookupServerMediaSession(),找到對應的ServerMediaSession實例。這個實例在handleCmd_DESCRIBE()中已經創建好了。
  • 調用numSubsessions()得到ServerMediaSession中的subsession數量。
  • 新建streamState數組fStreamStates[],長度為subsession數量。fStreamStates[]引用ServerMediaSubsession對象,這些對象保存在fSubsessionsHead鏈表中
  • 比對subsession的trackId成員和請求串中的trackId(如"track1"),相同則紀錄trackNum。
  • 調用parseTransportHeader()從請求串中解析得到傳輸頭中包括的數據流配置信息。如請求串"RTP/AVP;unicast;client_port=65512-65513",代表單播UDP,RTP連接的客戶端端口為65512,RTCP連接的客戶端端口為65513。
  • 調用parseRangeHeader()解析得到點播范圍。如果失敗,則再調用parsePlayNowHeader()看能不能找到。沒有設置點播范圍兩個函數都返回失敗,則從頭開始播到尾。
  • 用前面找到的trackNum對應的subsession,調用OnDemandServerMediaSubsession::getStreamParameters()創建數據流通道。
    • 與subsesssion對應的streamState,它的成員streamToken,是這個函數的一個參數。OnDemandServerMediaSubsession實例將它當做輔助數據結構,它被定義為類StreamState,與streamState只是首字母不同。
  • 構造回復字符串。

getStreamParameters()實現:

  • 它的參數clientAddress指定了目標地址。這里因為請求中沒有包括“destination=xxx”,所以這個地址是0。這時將目標地址指定ClientConnection的客戶端地址。
  • 調用createNewStreamSource()創建FramedSource實例。
  • 創建用于RTP連接和RTCP連接的Groupsock實例。
    • 從成員fInitialPortNum指定的起始端口開始,嘗試可用的本地端口,作為RTP連接的本地端口。fInitialPortNum在OnDemandServerMediaSubsession的構造函數中,指定缺省值為6970。
    • 調用createGroupsock()創建RTP連接的Groupsock實例。
    • 如果不復用RTCP連接和RTP連接,則將RTCP連接的端口加1,創建RTCP連接的Groupsock實例。
    • 為RTP連接,調用createNewRTPSink()創建RTPSink實例。如:.264格式文件調用H264VideoFileServerMediaSubsession::createNewRTPSink()。
    • 基于上述Groupsock和RTPSink實例,創建StreamState實例。
    • 創建Destinations實例,保存客戶端地址和端口,將Destination實例加入成員fDestinationHashTable中。

2.8?RTSPClientSession::handleCmd_PLAY

實現:

  • 調用parseScaleHeader()和parseRangeHeader(),得到播放的起點和終點。
  • 遍歷成員fNumStreamStates,streamState的成員subsession,調用OnDemandServerMediaSubsession::seekStream(),調整播放位置。
  • 遍歷成員fNumStreamStates,streamState的成員subsession,調用OnDemandServerMediaSubsession::startStream(),開始播放。
  • 構造回復字符串

OnDemandServrMediaSubsession::startStream()實現:

  • 根據clientSessionId在成員fDestinationHashTable中查找對應的Destinations實例,其中保存了目標地址。
  • 調用StreamState::startPlaying()開始播放。
    • 這個StreamState實例是在處理SETUP請求時創建的, streamState結構體的成員streamToken保存了這個實例。
    • 調用OnDemandServerMediaSubsession::createRTCP()創建RTCPInstance實例。RTPSink實例和RTCP連接的Groupsock實例作為參數。并調用setAppHandler()和setSpecificRRHandler()設置回調函數。
    • 調用MediaSink::startPlaying(),開始解析媒體幀并通過RTP連接發送,同時調用RTCPInstance::sendReport()發送RTCP信息。

接收RTCP信息實現:

  • 調用RTCPInstance::setByeHandler設置一個回調函數,當收到bye消息時,該回調函數被調用。回調函數保存在成員fByeHandlerTask。
  • 在RTCPInstance構造函數中,調用RTCPInterface::startNetworkReading(),將RTCPInstance::incomingReportHandler()設置為數據可讀時的回調函數。

在RTCPInstance::incomingReportHandler()中

  • 調用RTCPInterface::handleRead()讀取數據到本地緩存。
  • 調用processIncomingReport()處理緩存。如果是bye消息,調用fByeHandlerTask()進行處理。

MediaSink::startPlaying()實現:

  • 調用MultiFramedRTPSink::continuePlaying(),其實是調用MultiFramedRTPSink::buildAndSendPacket()。
    • buildAndSendPacket封裝RTP頭,調用packFrame()打包幀數據
      • packFrame()從source文件讀取一幀數據,通過回調afterGettingFrame()返回給sink,其實是調用afterGettingFrame1()。
        • afterGettingFrame1()向包中打幀數據,包完成則調用sendPacketIfNecessary()發送包,反之則調用packFrame()從source獲取幀數據打包。

MultiFramedRTPSink中的幀數據和包緩沖區共用一個,只是用一些額外的變量指明緩沖區中屬于包的部分以及屬于幀數據的部分(包以外的數據叫做overflow data)。

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

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

相關文章

力扣hot5---雙指針

題目&#xff1a; 解決方案&#xff1a;雙指針 指針 i 指向最左側&#xff0c;指針 j 指向最右側。此時在寬度上達到了最大值&#xff0c;那么哪個柱子更矮&#xff0c;哪個柱子向內部移動&#xff0c;知道 i 與 j 相遇。為什么呢&#xff1f; 如果哪個哪個柱子更矮&#xff0c…

代碼隨想錄算法訓練營第四十一天|198.打家劫舍,213.打家劫舍II,337.打家劫舍III

系列文章目錄 代碼隨想錄算法訓練營第一天|數組理論基礎&#xff0c;704. 二分查找&#xff0c;27. 移除元素 代碼隨想錄算法訓練營第二天|977.有序數組的平方 &#xff0c;209.長度最小的子數組 &#xff0c;59.螺旋矩陣II 代碼隨想錄算法訓練營第三天|鏈表理論基礎&#xff…

Node.js基礎---模塊化

基本概念 模塊化 模塊化是指解決一個復雜問題時&#xff0c;自上向下逐層把系統劃分成若干模塊的過程&#xff0c;對于整個系統來說&#xff0c;模塊是可組合&#xff0c;分解和更換的單元 遵守固定規則&#xff0c;把大文件拆分成獨立并互相依賴的多個小模塊 好處&#xff1a…

【計算機畢業設計】208基于SSM的在線教育網站

&#x1f64a;作者簡介&#xff1a;擁有多年開發工作經驗&#xff0c;分享技術代碼幫助學生學習&#xff0c;獨立完成自己的項目或者畢業設計。 代碼可以私聊博主獲取。&#x1f339;贈送計算機畢業設計600個選題excel文件&#xff0c;幫助大學選題。贈送開題報告模板&#xff…

OLLAMA:如何像專業人士一樣運行本地語言模型

原文 https://cheatsheet.md/llm-leaderboard/ollama.en簡介&#xff1a;揭示 OLLAMA 對本地語言模型的強大功能 您是否曾經發現自己陷入了基于云的語言模型網絡中&#xff0c;渴望獲得更本地化、更具成本效益的解決方案&#xff1f;好吧&#xff0c;您的搜索到此結束。歡迎來…

逆向案例四、進階,爬取精靈數據咨詢前五十頁數據

python代碼示例: import csv import execjs import requests f open(精靈數據.csv,w,encodingutf-8,newline) csv_writer csv.DictWriter(f,fieldnames[標題,發布時間,新聞來源,詳情頁鏈接,轉自,點擊量,新聞作者,發布時間小時,]) csv_writer.writeheader() data [] for pa…

【Ansys Fluent Web 】全新用戶界面支持訪問大規模多GPU CFD仿真

基于Web的技術將釋放云計算的強大功能&#xff0c;加速CFD仿真&#xff0c;從而減少對硬件資源的依賴。 主要亮點 ? 使用Ansys Fluent Web用戶界面?&#xff08;UI&#xff09;&#xff0c;用戶可通過任何設備與云端運行的仿真進行遠程交互 ? 該界面通過利用多GPU和云計算功…

理解python3中的回調函數

百度百科說&#xff1a;回調函數就是一個通過函數指針調用的函數。如果你把函數的指針&#xff08;地址&#xff09;作為參數傳遞給另一個函數&#xff0c;當這個指針被用來調用其所指向的函數時&#xff0c;我們就說這是回調函數。回調函數不是由該函數的實現方直接調用&#…

Sqli-labs靶場第13關詳解[Sqli-labs-less-13]

Sqli-labs-Less-13 #手工注入 post傳參了 根據題目看&#xff0c;像一個登錄頁面&#xff0c;嘗試使用布爾型盲注測試能否登錄網站 1. Username輸入a 測試是否會有報錯&#xff0c;burp抓包 報錯&#xff1a;syntax to use near a) and password() LIMIT 0,1 at line 1 分…

[python] `json.dumps()` TypeError: Object of type set is not JSON serializable

在Python中&#xff0c;當你嘗試將一個集合&#xff08;set&#xff09;類型的對象轉換為JSON格式時&#xff0c;可能會遇到“TypeError: Object of type set is not JSON serializable”的錯誤。這是因為標準的JSON格式不支持Python中的集合類型&#xff0c;JSON格式支持的數據…

【04】C語言括號匹配問題

歡迎來到土土的博客~&#x1f973;&#x1f973;&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;個人主頁&#xff1a;大耳朵土土垚的博客 &#x1f4a5; 所屬專欄&#xff1a;C語言系列函數實現 題目描述&#xff1a; 給定一個只包括 ‘(’&#xff0c;‘)’&#xf…

加密隧道技術

在現在的互聯網上傳輸數據&#xff0c;首要考慮的就是安全。這關乎到你的隱私&#xff0c;個人信息&#xff0c;財產安全等等重大問題。如果你的程序本身傳輸的信息沒有加密&#xff0c;也可以通過其他輔助方式讓你的通信加密。一些工具的就是為了解決這樣的場景的&#xff0c;…

之前續寫抖音開發者接入字節小游戲的緩存一下,現在說一下在 Windows 或者 Mac 如何用終端更換路徑?

window: 比方說你的 window 目錄下是這個路徑: 第一:E:\project\Q1\trunk\client\src,然后你想切換到下一個路徑的話,你可以這樣子操作: 第二:E:\project\Q1\trunk\client\src> cd .\usersetting 然后回車,這里不會計較大小寫 第三:你就可以在這個目錄下執行你的腳本:E:…

學習大數據,所必需的java基礎(7)

文章目錄 File類File 的靜態成員File的構造方法File的獲取方法相對路徑和絕對路徑File的創建方法File類中的刪除方法File的遍歷方法 字節流IO流介紹以及輸入輸出以及流向的介紹IO流的流向IO流分類IO流分類 OutputStream中的子類FileOutoutStream的介紹以及方法的簡單介紹InputS…

服務器中如何檢查端口是否開放

有多種方法可以檢測服務器端口是否開放。以下是一些常用的方法&#xff1a; 1. Telnet 命令&#xff1a; 使用 Telnet 命令來測試端口的可達性。在命令提示符或終端中執行以下命令&#xff1a; telnet your_server_ip your_port_number 如果連接成功&#xff0c;表示端口是…

C++ //練習 10.22 重寫統計長度小于等于6 的單詞數量的程序,使用函數代替lambda。

C Primer&#xff08;第5版&#xff09; 練習 10.22 練習 10.22 重寫統計長度小于等于6 的單詞數量的程序&#xff0c;使用函數代替lambda。 環境&#xff1a;Linux Ubuntu&#xff08;云服務器&#xff09; 工具&#xff1a;vim 代碼塊 /********************************…

PDF標準詳解(二)——PDF 對象

上一篇文章我們介紹了一個PDF文檔應該包含的最基本的結構&#xff0c;并且手寫了一個最簡單的 “Hello World” 的PDF文檔。后面我們介紹新的PDF標準給出示例時將以這個文檔為基礎&#xff0c;而不再給出完整的文檔示例&#xff0c;小伙伴想自己測試可以根據上一節的文檔來進行…

分布式ID選型對比(3)

redis自增 一, 引入依賴: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.6.5</version> </dependency> 二, 配置信息: spring:redis:# 地…

YOLOv8有效漲點,添加GAM注意力機制,使用Wise-IoU有效提升目標檢測效果

目錄 摘要 基本原理 通道注意力機制 空間注意力機制 GAM代碼實現 Wise-IoU WIoU代碼實現 yaml文件編寫 完整代碼分享&#xff08;含多種注意力機制&#xff09; 摘要 人們已經研究了各種注意力機制來提高各種計算機視覺任務的性能。然而&#xff0c;現有方法忽視了…

【C/C++隨筆】static 的用法和作用

「前言」所有文章已經分類好&#xff0c;放心食用 「歸屬專欄」C語言 | C嘎嘎 「主頁鏈接」個人主頁 「筆者」楓葉先生(fy) static 的用法和作用&#xff1f;&#xff1f;&#xff1f; static作用&#xff1a; 作用1修改存儲方式&#xff1a;用 static 修飾的變量存儲在靜態區…