基于Linux平臺的網絡聊天軟件的設計與研究_第1頁
基于Linux平臺的網絡聊天軟件的設計與研究_第2頁
基于Linux平臺的網絡聊天軟件的設計與研究_第3頁
基于Linux平臺的網絡聊天軟件的設計與研究_第4頁
基于Linux平臺的網絡聊天軟件的設計與研究_第5頁
已閱讀5頁,還剩49頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

南京工程學院畢業設計說明書(論文)南京工程學院畢業設計說明書(論文)院系:通信工程學院專業:通信工程(電力通信)題目:基于Linux平臺的網絡聊天軟件的設計與研究指導者: 評閱者:2015年6月南京DesignandimplementationasoftwarewhichcancommunicateonLinuxADissertationSubmittedtoNanjingInstituteofTechnologyADissertationSubmittedtoNanjingInstituteofTechnologyFortheAcademicDegreeofBachelorofScienceSupervisedbyLecturerLeiTangCollegeofNanjingInstituteofTechnologyJune2015摘要Linuxkernel由于其代碼的開源及獲取方便等優點,越來越受到廣大技術人員的青睞。Linux系統提供了完整的內核源代碼,在此代碼的基礎上可以進行自定義,從而打造屬于自己的“專屬”系統。Socket通訊使用C/S模型,Socket上TCP/IP網絡應用程序接口(API),提供了很多函數方開發人員利用。Socket通訊有兩種傳輸協議:TCP協議和UDP協議,TCP協議是面向連接的,傳輸可靠的;UDP是無連接的,傳輸不可靠的,因此本設計中選用TCP。進程和線程是不同的,進程擁有自身的程序映像和地址空間,是內核調度的基本單元,每個單獨的進程都有自己的代碼段、數據段以及堆棧,它們使用自己的虛擬地址空間,多個進程間互不影響。而線程沒有自己獨立的地址空間,創建出來的新線程和創建它的進程共享一個虛擬地址空間。這些知識點將在論文中詳細介紹。本設計主要對服務端和客戶端程序進行編寫并對其進行調試,從而實現客戶端和服務端之間的通信。總的來講,本設計使用C語言開發,通過Socket建立連接,并創建多個線程的實現多任務。關鍵詞:Linux內核;socket;進程;線程;多任務AbstractBecauseofmeritinopeningofsourcecodeandgettingconvenient,linuxkernelbecomemoreandmorepopularamongtheprogrammers.LinuxprovidesintegratedsourcecodeofLinux-kernel,peoplecanmakespecialsystembelongstothemselvesbasedonthesourcecode. SocketcommunicationuseC/Smodel,andtheAPIofTCP/IPonitprovidesplentyoffunctionsforprogrammerstouse.Socketcommunicationhastwotransferprotocols:TCPandUDP;TCPisconnectionandreliable,butUDPisconnectionlessandunreliable.So,wechooseTCPinthisdesign. Processisdifferentfromthread.Processhasitproceduremapandaddressspace,it‘sbasicunitofthekernelscheduling,eachsingleprocesshasitsowntext、dataandstack,theyusetheirsvirtualaddressspaceanddon’taffecteachother.Butthreaddoesn’thaveindependentaddressspace,itsharesthevirtualaddressspacewiththeprocesswhocreatesit.Thisknowledgewillbeintroducedinmythesis. Thisdesignrealizescommunicationbetweenclientandserverbydebugginginclientandserver’scode.Ingeneral,thisdesignusesClanguage,connectsbysocketandcreatesmultiplethreadstoimplementmultitasking.Keywords:Linux-kernel;socket;process;thread;multitask目錄第一章緒論 11.1設計背景 11.2應用概述 21.3 為什么選擇Linux 41.4論文的主要工作 5第二章Linux編程常用工具 62.1嵌入式C語言 62.2GCC工具鏈 72.2.1GCC的用法 82.2.2調試和剖析選項 82.3GDB調試器 92.3.2GDB用法 92.3.1用gdb調試GCC程序 10第三章設計的實現及主要技術 133.1實現分析 133.1.1功能分析 133.1.2設計分析 133.1.3技術分析 143.3Socket(網絡套接字) 183.3.1編程步驟 193.3.2地址及順序地址 193.3.3鏈接函數說明 223.4進程 253.4.1進程的概念 253.4.2進程的內存布局 263.4.3進程生命周期 273.5多線程 283.5.1線程的概念 283.5.2進程和線程的關系及優缺點 283.5.3數據類型及其用法 303.6線程同步 313.6.1概念 313.6.2線程同步的幾種方法:信號量、自旋鎖、互斥量 323.6.3互斥量的使用 323.7ncurrses字符界面 333.7.1ncurses簡介 333.7.2ncurses常用函數介紹 34第四章設計的編程實現 364.1聊天室Socket編程連接 364.2聊天室程序設計及界面設計 384.2.1服務器 384.2.2客戶端 384.2.3客戶端主體界面及功能展示 41第五章總結與展望 445.1全篇總結 445.2設計中遇到的問題及解決辦法 445.3設計有待改進的地方 46致謝 47參考文獻 48第48頁第一章緒論1.1設計背景 Linux是兼容POSIX的U-like操作系統,大多數重要的UNIX工具、應用程序和網絡協議都能在其上運行。Linux系統目前已經得到了很廣泛的應用。它是由芬蘭人LinuxTorvalds開發的。 Linux最初是為intel_x86平臺開發的免費操作系統,從那時開始Linux就被陸續移植到其他硬件工作平臺。在服務器方面Linux操作系統一直處于領先地位,比如大型機和超級計算機,但在桌面用戶市場只占到了1.5%。Linux也可以運行在嵌入式設備之中,這些設備主要包括移動電話、平板電腦、網絡路由、自動控制設備和電視機等等。Android是Linux操作系統在平板和智能電話中用的最為廣泛的一款操作系統構建與LinuxKernel上層。 基于代碼開源的優勢,linux具有強大市場競爭力。只要想獲得源碼,便可從官方網站上獲取LinuxKernel源代碼,有了源代碼之后就可以自定義操作系統了。在此基礎上安裝自己的應用軟件,至此一個完整的屬于自己的操作系統就誕生了。通常將這樣安裝所獲得的系統稱為Linux系統,這是因為它包含的不止一個內核。 提及Linux,那就得提到GNU和Unix。1983年9月27號,RichardStallman希望開發一套完整的開放源碼操作系統以取代Unix,從而誕生了GNU。1985年,發表GNU宣言;1989年,發表GNU(GNUGneralPublicLicense,GPL);GPL的開源思想使得我們的學習之路順暢了許多。GPL保障了Linux不僅僅是當下自由可用,而且今后經過任何修改后還是自由可用的,這點充分體現了Stallman的初衷。 Unix是于1969年在美國AT&T公司貝爾實驗室開發出來的具有多任務,多用戶的操作系統。Unix的前身叫做Multics,由BELLLabs參與研發,可是由于進度緩慢,BellLabs不得不放棄了這個計劃。在這之后就由貝爾實驗室的職員Kenthompson,DennisRitchie等繼續自行開發。在此之后的10年里,Unix在大型企業和學術界得到了普遍的應用,其不斷擴大的影響力終究還是引起了AT&T的關注,就這樣一場持續許久的版權官司開始了,直到AT&T將自己的貝爾實驗室賣給Novell接手。最初的UNIX是用PDP-7匯編語言別寫的,但因為其在系統編程方面沒有太大優勢,于是Thompson和Ritchie兩人對其加以改造,并于1971年共同發明了C語言。1973年thompson和Ritchie用C語言重寫了UNIX,UNIX第三版就這樣問世了。在當時,系統程序大多是由匯編語言編寫,thompson和Ritchie此舉是非常具有革新意義的。用C語言編寫的Unix代碼緊湊簡潔、易讀、易修改,為此后Unix的進一步發展奠定了扎實的基礎。1.2應用概述 嵌入式Linux是指運行在分布式嵌入式設備上的Linux操作系統,盡管“embedded”這一詞經常用來討論內核方面的知識,但嵌入式應用上的Linuxkernel并沒有什么特別之處。Linux內核都是一樣的,只不過在不同的嵌入式設備上需要進行不同的編譯步驟,從而讓Linux操作系統能夠在嵌入式設備上跑起來。嵌入式操作系統就是將Linux內核和其他各種軟件編譯成的能夠運行在嵌入式分布式設備中的系統。你得通過付款的方式獲得這特定的編譯工具,這些編譯工具通常指:交叉編譯器、調試器、項目管理軟件、引導鏡像等等,從而得到完整的一個運行在嵌入式設備上的操作系統。隨著網絡的不斷發展,網絡在嵌入式系統中應用十分廣泛,越來越多的嵌入式設備均采用Linux操作系統。傳統的嵌入式系統設備如:空間站、自動系統、消費電子系統、電話等等都使用Linux系統,但這些嵌入式Linux系統大體上都相同,沒有什么新穎的特色。而且這些都沒有提及到系統的結構,Linux系統的結構真正信息包括:大小、實時、網絡能力、和用戶的交互能力。Linux系統的選型非常重要,針對不同用戶的需求不一,Linux有很多發行版,在這之中又分很多類:桌面版、服務器版、企業版。桌面版針對的大多是普通用戶,有很好的桌面環境,比較適合于新手;服務器版多傾向與終端界面,沒有較好的桌面環境,在服務器開發方面有很大優勢;企業版則面向的大多是大型企業,對信息安全,網絡穩定方面都很高。Linux由于其公開的源碼及免費的操作系統,在Linuxkernel的代碼獲取及相關構建工具沒有任何限制。因此對基于Linux平臺的Socket網絡編程的研究就顯得很重要,下圖1.1為其C/S簡易模式圖和圖1.2詳細模式圖:圖1.1C/S簡易模式圖圖1.2C/S詳細模式圖編寫Socket的server和client端程序,雙方通過socket建立連接,從而完成進一步的通信。為什么選擇Linux1.代碼的質量和可依賴性。質量和可依賴性是衡量代碼優劣的重要標準。盡管在“qualitycode”眾說紛紜,但是大多數編程人員所期望的都有如下幾點:(1)模塊化:每一個功能都寫成一個模塊,這樣不僅易于讀寫,更加方便以后的移植;(2)可讀性:一段代碼應該能讓別人看的下去,這里包括變量的定義,盡量取有意義的變量名,避免inti,j這樣的定義;一段雜亂的讓人看一眼就不想往下看的代碼一定是不好的,不管功能有多牛。;(3)可擴展性:在增加新的功能時,不要對原來的代碼進行大篇幅的修改,而是只要添加對應的功能即可;(4)可配置性:可以選擇對應的功能特色進行編譯,不要的就不編譯,從而生成的程序適合自己的使用。但是配置的過程應該盡量簡單人性化。2.TCP/IP協議由網絡層的IP協議和傳輸層的TCP協議組成,是Internet最基本的協議、是Internet國際互聯網絡的基礎。Socket通信所采用的協議分為面向連接(TCP)和非面向連接(UDP)的兩種,但由于UDP效率較高但是傳輸不可靠,不能勝任復雜的網絡環境,不得不通過超時和重傳等手段來實現較高的可靠性;然而TCP在數據傳輸方面提供了完全的可靠性,因此選用TCP/IP協議更加可靠些。3.代碼的可獲得性。Linuxkernel源碼及所有的開發和編譯工具是很方便從網絡上獲取的。Linux中最重要的部分Linuxkernel分布在GPL下。其他的代碼也分布在相同的證書下,如BSD等。構成Linux的大部分代碼都是沒有限制的。當源碼的訪問受限時,開放自由軟件組織就會尋找新版本的源碼包代替原有的代碼。正是由于這一特性,Linux受到很多電話制造商的青睞,他們將其移植到自己產品中,稍作修改優化使其適合自己的產品。4.硬件支持。Linux支持不同種類的硬件平臺和設備,盡管有些驅動暫不支持Linux系統,但是對此有很大的期待。因為很多驅動都由Linuxshequ維護著,你可以毫無顧忌的使用這些驅動。各種硬件協同工作,使你的工作更加順暢。你希望在你自己電腦上編寫的程序能夠在另一架構的操作系統上正常運行,甚至有不同的設備驅動運行在不同的系統架構上。5.通信協議和軟件標準。Linux提供了廣泛的通信協議和標準的軟件支持。這使得整合Frameworks及相關軟件到Linux上變得更加容易。同時,Linux是U-like的,可以方便的將UNIX程序移植到Linux上。事實上,許多應用被綁定在一些商用Unixes中,繼而被移植到Linux中。1.4論文的主要工作第一章:緒論,大體介紹Linux操作系統發展、應用的現狀以及我們選擇Linux系統的原因;第二章:介紹Linux軟件開發中使用的編譯工具和調試工具及其簡單的使用方法;第三章:介紹設計是怎樣實現的以及在實現的過程中用到了哪些關鍵技術并對這些關鍵技術作簡要分析;第四章:介紹設計相關的主要代碼的實現過程,依次對服務器和客戶端主要流程進行介紹,必要的功能展示;第五章:設計總結與展望第二章Linux編程常用工具2.1嵌入式C語言 C語言最初是由貝爾實驗室的職員Thompson和Ritchie于1971年共同開發。其應用場景很多,如:操作系統:Linux;微控制器:汽車和航天飛機;嵌入式處理器:電話和便攜式電子設備等;DSPProcesser:數字音頻圖像處理和TV系統等。程序的生成過程如圖2.1所示:圖2.1程序的生成過程C語言能夠在編程開發領域受到如此高的青睞,主要有以下幾個方面的原因:1.通用性。因而在跨平臺開發時非常方便,C語言的這一特性吸引著廣大編程愛好者;2.執行速度快。可以指定編譯選項從而省去中間過程,以進一步提升程序的執行效率。3.可移植性。由于各個平臺的差異,當一個程序到不同平臺上,只需要從新編譯一下源代碼即可使用;4.發展快速。C語言發展較快。在上世紀80年代末期由AmericanNationalInstitude發布的ANSIC的C語言標準奠定了其發展的基石。2.2GCC工具鏈 GNU/Linux操作系統上往往使用gcc作為編譯工具。其不是一個單獨的程序,而是多個程序的組合,因而通常稱為toolchain(工具鏈)。GCC的全稱是GNUComplierCollection,是由GNUProject提供的支持多種編程語言的編譯器。GCC是GNUtoolchain中的重要組成部分,GCC作為一個工具和實例,在自由軟件的成長過程中扮演著重要的作用。同年12月,GCC擴展到能夠編譯C++,不久之后能夠支持的語言更多,如Objective-C、Objective-C++、Fortran、Java和Ada等等。 GCC被移植到多種架構的處理器,并作為一種專業軟件開發工具配置于系統中。GCC同時也集成于大多數嵌入式平臺,包括Symbian,AMCC等。作為GNU操作系統官方指定編譯器,GCC被其他U-like操作系統吸收采用,包括Linux和BSD家族。程序的編譯過程如圖2.2所示: 圖2.2源碼到可執行文件的過程2.2.1GCC的用法 對于GCC的編譯選項只要掌握一些常用的選項就可以,其他多大100多種選項有些工程師可能一輩子也不會用到。 gcc基本用法: gcc[-Wall][-O1..3][-g][-oname]file... -Wall:打開所有警告項-O:設置優化級別,O0表示關閉優化功能-g:將調試信息編譯到目標文件中-oname:指定輸出文件的名稱是namefile:被編譯(鏈接)的文件2.2.2調試和剖析選項 gcc–g:以本機格式(stabs,COFF,XCOFF,orDWARF2)生成調試信息供gdb使用。 在大多數系統上,“-g”產生的調試信息只能供GDB使用,這種調試信息在gdb中能很好的工作,而在其他調試器上不是讀取不了就是調試器異常崩潰。如果你想指定調試信息的格式,可通過如下方式: -gstabs+、-gstabs、-gxcoff+、-gxcoff、-gvms gcc允許同時使用‘-g’、‘–o’,這種使用方法將會產生令你意想不到的效果:你定義的一些變量可能不存在、程序會跑飛掉、一些語句不會被執行。不管是否提供可能的優化輸出,但可以肯定的是,這將會產生一些bug。 但是這種方法使得你能夠在和最終產品盡可能相同的情況下對代碼進行調試。應當注意,如果你同時使用這兩個編譯選項,必須清楚所寫的某些有關代碼已經在優化時被GCC作了些改變。2.3GDB調試器GDB(GNUDebuger),是GNU操作系統的標注調試器;。GDB作為GNU系統的一部分于1986年由RichardStallman編寫,是GPL下的自由軟件。使用者可以監視和修改程序的內部變量,甚至可以獨立地調用程式正常行為的函數。 GDB能對多種不同處理器架構上運行出錯的應用軟件排錯,這些處理器架構包括:Alpha、ARM、AVR、H8/300、AlteraNios/NiosII、System/370、System390、X86及其64位擴展。GDB明顯的限制是在他的運用方面,沒有較親和的圖形界面,預設只有命令行界面可用。 Gdb是為了讓你能夠“看到”另一程序執行時具體做了什么或者是另一程序崩潰時發生了什么 GDB可以做四件事情幫助你找到程序中的bug:啟動你的程序,指定任何可能影響其行為的變量或函數等;使你的程序暫停(設置斷點)或者指定特定的狀態;當你的程序終止時,檢測發生了什么;改變你程序中的事情,這樣你就可以嘗試糾正一個bug的影響以便繼續往下找另一個bug。2.3.2GDB用法 gdb的基本命令: gdb提供的各種各樣的命令有著不同的功能。從簡單到復雜,以下列出了一些常用的gdb調試命令: (1)filefilename:在gdb模式中無需退出即可通過file命令裝在你要調試的文件(gdb)filedavidReadingsymbolsfrom/home/david/Graduation_project/david...(nodebuggingsymbolsfound)...done.(2)kill:通過kill命令可以終止當前正在調試的程序(gdb)killKilltheprogrambeingdebugged?(yorn)y還有一些常用的如:n(next)、s(step)、run和q(quit)將在下節例程中詳細說明。2.3.1用gdb調試GCC程序 在終端輸入gdb回車后,會出現如下一長串內容: david@zdz:Graduation_project$gdbGNUgdb(Ubuntu/Linaro7.4-2012.04-0ubuntu2.1)7.4-2012.04Copyright(C)2012FreeSoftwareFoundation,Inc.LicenseGPLv3+:GNUGPLversion3orlater</licenses/gpl.html>Thisisfreesoftware:youarefreetochangeandredistributeit.ThereisNOWARRANTY,totheextentpermittedbylaw.Type"showcopying"and"showwarranty"fordetails.ThisGDBwasconfiguredas"x86_64-linux-gnu".Forbugreportinginstructions,pleasesee:</gdb-linaro/>.(gdb)Gdb的一些使用命令如果記不住的話可以查看官方手冊,沒必要硬記,準其自然,每次不會的時候翻翻手冊,時間長了自然就記住了下面只介紹一些常用的幾個命令:比方說,現在有一個通過gcc編譯好的可執行文件david,在程序中我定義了兩個變量x,y賦初值為5,6終端執行david@zdz:Graduation_project$./davidTheresultis:x+yis11x*yis30x/yis0那么通過gdb來看看發生了什么:david@zdz:Graduation_project$gdbdavid(gdb)我們現在告訴gdb使用窄于通常的顯示寬度:(gdb)setwidth70下面我要知到david這個程序怎樣工作的,通過閱讀源程序我大體知道工作流程,所以我可以設置斷點: (gdb)breakcountBreakpoint1at0x400579接下來運行程序:(gdb)runStartingprogram:/home/david/Graduation_project/davidTheresultis:Breakpoint1,0x0000000000400579incount()可以發現程序停在count()函數這個地方,Theresultis:這句話顯示count()函數之前都已運行完畢。現在我可以通過n(next)讓程序執行到下一行(gdb)nSinglesteppinguntilexitfromfunctioncount,whichhasnolinenumberinformation.x+yis11x*yis30x/yis00x0000000000400573inmain()還可用backtrace命令查看在棧中的什么位置,顯示每一個激活的子函數的棧結構:(gdb)backtrace#00x0000000000400573inmain() 通過輸入q(quit)退出gdb模式: (gdb)qAdebuggingsessionisactive. Inferior1[process3423]willbekilled.Quitanyway?(yorn)ydavid@zdz:Graduation_project$第三章設計的實現及主要技術3.1實現分析 本節主要對設計的功能和設計思路進行簡單的介紹3.1.1功能分析 客戶端的點點通信及文件的傳送。主要包括Server端和Client端: 1.Server端:主要負責處理用戶發送過來的消息,對用戶的系列動作進行管理(登陸、注冊、私聊、群聊)和對用戶的數據進行管理(查看用戶的賬戶信息)。 2.Client端:主要可以和其他用戶私聊及群聊,還可以向其他用戶發送文件。3.1.2設計分析 服務器對Socket的初始化:網絡協議的指定、端口的指定,具體步驟將在Socket中進行仔細介紹,等待用戶連接。Client通過socket()初始化一個socket并向server發送請求連接。當服務器接收到用戶的連接請求時,將為其開辟一個單獨的新的線程,這條線程就為此用戶服務。只有當用戶退出時才將其釋放。而服務器的主線程仍在等待其他用戶的連接。系統設計大體框圖如圖3.1所示:3.1系統設計框圖3.1.3技術分析 基于以上簡單的分析,Socket是實現C/S通信的接口,且使用TCP/IP協議。TCP協議是面向連接的、傳輸可靠的,且對網絡的適應能力較強,股本設計選用TCP,而不選用UDP。服務器要“同時”處理多個客戶的請求,就得實現多任務并發。對于單個處理器來講并發僅僅是概念上的,不是真正地同一時間處理多個任務,而是多個任務輪流依次執行,留給用戶的感覺就好像在同時處理多個任務;而對于多喝CPU,則是真正意義上的并發執行,效率總的來說也有圖單核CPU。 線程并發的使用往往會牽涉到同步的問題,mutex能解決此類問題,當然最主要的還是Scoket函數調用。 Ncurse終端字符界面大部分是由C++編寫,使用它只要將其安裝在本機上然后通過調用它的庫函數即可。3.2TCP/IP通信TCP/IP協議不同于OS模型,TCP/IP協議主要就以下幾個部分:應用層、傳輸層、數據鏈路層和網絡接口層。各層示意圖如圖3.2所示:圖3.2TCP各層示意圖對于我們編程來說,沒有必要完全掌握每一層的細節,只要知道:(1)每一層協議都是為下層向上層作轉換(2)沒有必要知道TCP的具體操作細節,也不要知道IP及數據鏈路層的具體操作細節(3)從應用程序的角度來看,僅僅把它們看成是SocketAPI就行了應用到應用之間的通信過程如圖3.3所示:圖3.3TCP/IP通信示意應用層:應用層的協議被大多數為用戶提供服務的應用所使用,通過網絡向低層交換應用數據。但這得包含一些基本的網絡支持服務,如許多路由協議和主機配置協議。例如,應用層常包含HTTP、FTP、SMTP、DHCP等協議。而且TCP/IP區分用戶協議和支持協議;支持協議提供系統服務,而用戶協議提供用戶級應用程序,比方說FTP就是用戶協議,DNS是系統協議。傳輸控制層:TCP/IP協議族中有兩個傳輸層協議:UDP和TCP。UserDatagramProtocol(UDP)是通過數據報的方式傳輸,TransmissionControlProtocol(TCP)是通過流的形式傳輸。 數據鏈路層:從應用角度來看,我們通常可以忽略數據鏈接層,因為所有通信細節在驅動程序和硬件接口中處理。數據鏈路層(Data-LinkLayer)最重要的一個特性是最大傳輸單元Maximumtransmissionunit(MTU)。 網絡層:主要包括一下任務:=1\*GB3①將數據分成足夠小的片段以便能夠通過數據鏈路層進行傳輸(如果需要的話)=2\*GB3②通過Internet指定路徑發送數據=3\*GB3③為傳輸層提供服務=4\*GB3④IPv4和IPv6=5\*GB3⑤IP以數據包的形式傳送數據=6\*GB3⑥IP是無連接的和不可依賴的。為了建立連接,TCP常通過三次握手,三次握手示意圖如圖3.3.2:在客戶機試圖連接服務器時,服務器必須首先必須打開一個端口并對其進行綁定并監聽;這通常叫做被動連接,一旦被動打開建立成功,客戶機就可以初始化一個主動連接。為了建立一個連接,三次握手機制就起作用了:SYN:Client向Server發送主動連接請求。Client設置序列號為隨機值xSYN_ACK:回應請求,server發送SYN-ACK。序列號被設置成+1,eg:x+1;此時server產生另外一個隨機值y;ACK:最終,client向服務器發送回應,接收到的序列號已經被設置成了x+1,server產生的序列號被設置成+1,eg:y+1。Client和Server都接收到了連接確認包。(1)(2)兩步為單向(去)連接產生一個可識別的連接參數;(2)(3)是為其他方向(回)產生一個可識別的連接參數。通過這些步驟,一個全雙工的通信就建立成功了。示意圖如圖3.4所示: 圖3.4三次握手示意圖3.3Socket(網絡套接字)SocketAPI,提供了許多例程和函數供程序員使用,以此開發TCP/IP網絡應用程序。就像pipe,Socket用文件描述符來表示,但是區別于Pipe的是,Socket支持在兩個進程之間甚至同一網絡中的不同機器上進行通信。Socket基本上可以理解為和其他機器進行通信;telnet,rlogin,ftp,talk等其他相似的網絡程序都是用sockets。但并不是所有機器都支持Socket。在GNU庫中,“sys/socket.h”存在于大多操作系統中,socket函數通常也都存在,但如果系統真的不支持socket,那么這些函數也就不會起作用了。當創建socket時,你必須指定使用那種通信類型及該使用那種協議去實現它。不同的通信類型定義了發送和接收數據的用戶級別,選擇一個通信類型你得考慮一下幾個方面:(1)傳輸的數據形式:bytes、packets(2)數據在傳輸過程中是否可以丟失:數據丟失程度不一樣對最終的結果影響是不一樣的;如果傳輸的數據不能丟失,那就得選用較可靠的協議。(3)通信是否是雙向的:就像打電話和發郵件的區別。在兩個程序,或許是不同的PC上進行數據傳輸得知道一下幾點:(1)為了在兩個socket間進行通信,這兩個socket必須制定相同的協議(2)每個協議都有自己的專屬定義,不能用其他協議的定義。(3)每個協議族都有一個默認的協議,你可以通過指定0作為協議號。3.3.1編程步驟 Server端:socket()、bind()、listen()、accept()、send()、recv()、close(); Server端的編程步驟大體如上,建立連接后通過不斷地創建線程為Client提供服務。Clent端:socket()、connect()、send()、recv();Client端成功連接服務器后,創建一個新的接收線程從服務器不斷接收數據顯示在不同的界面。3.3.2地址及順序地址 1、地址結構相關處理 (1)數據結構介紹sockaddr結構體有兩個成員:shortintsa_family、charsa_data[14];其原型如下: structsockaddr{unsignedshortsa_family;/*地址族*/charsa_data[14];//socket地址數據的真正長度};sockaddr_in結構體有三個成員:sin_family、sin_addr、sin_port;其原型如下:structsockaddr_in{sa_family_tsin_family;/*addressfamily*/in_port_tsin_port;/*portnumber*/structin_addrsin_addr;/*internetaddress*/};一般情況下,sockaddr_in結構體使用起來更加方便。 (2)結構字段 下表3.1列出了sa_family字段的常用值。 表3.1sa_family字段結構定義頭文件#include<netinet/in.h>Sa_familyAF_INET:IPv4InternetprotocolsAF_INET6:IPv6InternetprotocolsAF_LOCAL:Localcommunication2、數據存儲優先級(1)函數說明 不同的計算機為一個word的存儲順序使用不同的約定。有些計算機將最重要的字節放到一個字的最開始(大端),而有些則是放到一個字的最后(小端)。網絡協議為傳輸的數據規定了字節順序,這就是大家所熟知的網絡字節序。因此有時候就需要對其進行轉化。 用htons和ntohs為sin_port進行轉化;用htonl和ntohl為sin_addr進行IPv4地址的轉化。其中各個頭字母的含義如下: h:host;n:network;s:short;l:long (2)函數格式說明 下表3.2列出了這四個函數的語法格式: 表3.2htons等函數定義所需頭文件#include<arpa/inet.h>#include<netinet/in.h>(somesysytem)函數原型uint32_thtonl(uint32_thostlong);uint16_thtons(uint16_thostshort);uint32_tntohl(uint32_tnetlong);uint16_tntohs(uint16_tnetshort);函數參數hostlong:主機字節序的32位數據hostshort:主機字節序的16位數據netlong:網絡字節序的32位數據netshort:網絡字節序的16位為數據函數返回值Success:返回要轉換的字節序Error:-13、地址格式轉化(1)函數說明 用戶習慣性輸入的ip形式為:xxxx.xxxx.xxxx.xxxx或xxxx:xxxx:xxxx:xxxx,這兩種格式雖能被用戶識別,但是機器卻無法識別,故只有將其轉化成機器能識別的二進制格式。經常使用的有inet_aton、inet_addr、inet_pton、inet_ntop,其中后兩個兼容IPV6。inet_pton:將以.形式表示的十進制轉換為為二進制;inet_ntop:將二進制轉換為以.形式表示的十進制。(2)函數格式表3.3列出了inet_ntoa的語法要點:表3.3inet_ntoa定義所需頭文件#inclide<arpa/inet.h>函數原型char*inet_ntoa(structin_addrin);函數參數in:網絡字節序的地址函數返回值Success:返回地址字符串表3.4列出了inet_addr的語法要點:表3.4inet_addr語法要點所需頭文件#inclide<arpa/inet.h>函數原型in_addr_tinet_addr(constchar*cp);函數參數要轉換的字符串地址函數返回值Success:返回轉換后的二進制地址3.3.3鏈接函數說明 1、C/S連接示意圖C/S模式描述了應用程序相互協作的關系。Server為一個或多個Client提供一個功能或服務。Server通常按其服務進行分類歸檔。比方說webserver提供網頁支持二fileserver則提供文件方面的服務。一個PC機扮演的角色是客戶機還是服務機還是兩者都是,通常取決于其應用的需求。一個機器可以同時作為服務機和客戶機;比方說,當單機運行webserver和fileserver同時服務于不同的請求;與此同時,客戶機上的軟件能夠和同一臺計算機上的服務機軟件通訊。示意圖如圖3.5所示: 圖3.5Socket編程示意圖2、函數格式(1)socket()相關定義如下表3.5所示:表3.5socket函數定義原型intsocket(intdomains,inttype,intprotocol);函數說明創建一個通信socket并返回一個描述符參數說明domains指定通信地址族,即選擇什么協議用來通信。常用的幾個有AF_UNIX,AF_LOCALLocalcommunicationAF_INETIPv4InternetprotocolsAF_INET6IPv6Internetprotocolstype規定使用那種通信數據類型SOCK_STREAM提供有序、可靠、雙向,基于連接的字節流SOCK_DGRAM不可靠,無連接的數據包protocol指定domain地址族中某個特定的協議,通常情況下可以指定為0(2)bind()相關定義如下表3.6所示:表3.6bind函數定義原型intbind(intsock_fd,conststructsockaddr*addr,socklen_taddr_len)函數說明給指定sock_fd的socket分配內存地址空間參數說明sock_fd創建socket時返回的文件描述符addr結構體,用于存放ip和端口號addr_len結構體addr的大小(3)listen()相關定義如下表3.7所示:表3.7listen函數定義原型intlisten(intsock_fd,intlen)函數說明監聽client連接的信號,和accept()合同參數說明sock_fd創建socket時返回的文件描述符len指定等待隊列的最大長度(4)accept()相關定義如下表3.8所示:表3.8accept函數定義原型intaccept(intsock_fd,structsockaddr*addr,socklen_t*addr_len)函數說明響應連接等待隊列的隊頭請求,并創建新的socket,返回其文件描述符,新創建的socket并不會影響原先的socket參數說明sock_fd創建socket時返回的文件描述符addr結構體,用于存放ip和端口號addr_len結構體addr的大小(5)connect()相關定義如下表3.9所示:表3.9connect函數定義原型intconnect(intsock_fd,conststructsockaddr*addr,socklen_taddr_len)函數說明通過指定的sock_fd與socket連接進而與其地址連接參數說明sock_fd創建socket時返回的文件描述符addr結構體,用于存放ip和端口號addr_len結構體addr的大小(6)send()相關定義如下表3.10所示:表3.10send函數定義原型ssize_tsend(intsock_fd,constvoid*buf,size_tbuf_len,intflags)函數說明往指定sock_fd的socket發送消息參數說明buf發送數據存放的暫存區buf_len暫存區的大小(7)recv()相關定義如下表3.11所示:表3.11recv函數定義原型ssize_trecv(intsock_fd,void*buf,size_tbuf_len,intflags)函數說明從指定sock_fd的socket中讀取數據參數說明buf發送數據暫時存放的區域buf_len暫存區的大小(8)close()格式:close(fd)3.4進程3.4.1進程的概念(1)在說明進程前,先看一下什么是程序?程序就是一個文件,該文件包含了一系列信息,這些信息描述了如何在運行時構造一個進程。(2)Linux平臺中可執行程序類型=1\*GB3①可執行目標文件經鏈接器鏈接后可直接執行的文件。內核通常支持幾種特定形式的可執行文件。ELF格式是Linux系統中普遍使用的一種標準的可執行文件格式。=2\*GB3②可執行腳本Bash就是shell或者說是命令行語言解釋器。GNU操作系統提供不同的shell,其中包含csh、ksh,但是bash是默認的bsh。和其他GNU軟件一樣,bash是可移植的,其目前運行在幾乎所有版本的Unix及其他的操作系統上,像MS-DOS、os/2和微軟平臺。當Shell讀到輸入時,展開一系列的操作。如果檢測到有注釋時會自動忽略以‘#’開頭這一行。然后,shell將讀取到的內容分成各個部分,再將這些解析成命令和其他結構,去掉一些特定的字或者字符,執行特定的命令。下面是其執行流程:1)從文件中讀取輸入2)將讀取到的內容按引用準則分成字和運算符。這些標號被通配符分隔3)將標號解析成單一的或復合的命令4)執行各種shell展開5)執行一些必要的重定向并將這些操作符及操作數從參數列表中移除6)執行命令7)選擇性的等待命令執行完成并記錄退出狀態。(3)什么是進程 進程的專業定義,IEEEStd1003.1:“Anaddressspacewithoneormorethreadsexecutingwithinthataddressspace,andtherequiredsystemresourcesforthosethreads”。通俗的講,就是一個正在運行的程序,每個程序都有自己的執行狀態和地址空間,每個進程相互獨立,除了兩個進程發生通信,否則兩個進程沒有多大關系。(4)不同進程的區分方法(內核)每個進程都有一個屬于自己的id號叫做PID,就像我們每個人都有一個自己的身份證號一樣,PID標識著這個進程的存在。內核通過這個PID來管理各個進程的狀態和資源。一個內核產生新的PID時,內核為這個PID分配系統資源,這樣一個進程也就產生了。3.4.2進程的內存布局 進程內存分布圖如圖3.6所示:從低地址到高地址依次分布圖3.6進程內存分布3.4.3進程生命周期 =1\*GB3①創建:進程的創建是由其父進程通過folk來創建的。下圖描述了進程的創建過程:=2\*GB3②運行:同一系統中可以有多個進程同時運行,并且能夠互相通信。試想一下,一個系統上同一時刻只能運行一個進程,要開其他進程必須得先將本進程關閉掉,那是得有多么麻煩。 =3\*GB3③終止結束一個進程的運行。3.5多線程3.5.1線程的概念 線程是系統最小的調度單元,是操作系統的重要組成部分。操作系統中,線程和進程的實現是不同的,單線程通常是進程的一部分。多個線程可以在同一個進程中執行,和進程共享內存資源,這是不同進程之間所不能做到的。與進程相比,線程沒有自己獨立的地址空間,創建出來的新線程將和創建它的進程(或線程)共享一個虛擬地址空間,它們被置于同一個線程組中。每個線程都有自己獨立的棧空間。不管是服務器還是客戶機都包含信息的一收一發兩個過程。同時使用主線程去收發信息是不可取的,因為收信息是被動的,當沒有信息可收時主線程就會阻塞,從而影響信息的發送。而創建兩個進程的方法在這里也是是不可取的,因為不同的進程對應不同的端口號,但是信息的發送和接收必須使用同一端口。因此必須創建多個線程來完成不同的收發任務。3.5.2進程和線程的關系及優缺點線程在進程中的分布如圖3.7所示:圖3.7進程中的線程 使用線程的優點: =1\*GB3①較快的執行(Fasterexecution):在多核CPU上執行明顯快于單核CPU,且上下文狀態的切換比進程快;=2\*GB3②響應迅速(Responsiveness):當主線程阻塞時,其他線程仍可和用戶進行交互; =3\*GB3③對資源的需求小(Lowerresourceconsumption):用較少的系統資源服務于更多的用戶; =4\*GB3④更好的系統利用(Bettersystemutilization):能更好的利用內存的資源;=5\*GB3⑤簡單的分享和通信(Simplifiedsharingandcommunication);=6\*GB3⑥并行化(Parallelization):多個線程同時對一個任務進行操作。但是線程也有一些缺點:=1\*GB3①同步(\o"Synchronization(computerscience)"Synchronization):由于共用相同的地址空間,那么就要很好的解決競爭和其他的非正常行為。如果處理不好,會造成死鎖的情況,那么進程就會變為僵死進程;=2\*GB3②在進程中崩潰(Crashinprocess):線程的非法操作從而造成整個進程的退出。3.5.3數據類型及其用法1、函數簡要說明如下表3.12所示:表3.12線程函數定義函數功能typedefunsignedlongintpthread_t/usr/include/bits/pthreadtypes.hpthread_create()創建一個線程pthread_exit()終止當前線程pthread_join()阻塞當前線程,直到另一線程退出2、線程的創建與結束(1)新線程的創建可通過pthread_create(),定義及說明如下表3.13所示:表3.13pthread_create函數定義原型intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*args);參數說明thread指向一個pthread_t型變量,用于返回線程IDattr指定各種不同屬性,若設為NULL,表示默認屬性start_routine新線程的工作函數args傳遞給執行函數的參數返回值0表示執行成功,出錯返回錯誤編號(EAGAIN、EINVAL)(2)pthread_join(),等待一個線程執行結束(與waitpid()類似),相關定義如下表3.14所示:表3.13pthread_join函數定義原型intpthread_join(pthread_tthread,void**value_ptr)參數說明thread指定要等待哪個線程value_ptr是指向(void*)型數據的指針,用于獲得線程的退出狀態(thevaluethatwasspecifiedwhenthethreadperformedareturnorcalledpthread_exit()),如果不需要退出狀態則可將其設為NULL返回值成功返回0,錯誤返回一個非負數(3)pthread_exit()voidpthread_exit(void*value_ptr);該函數終止當前線程(callingthread),并且指定一個返回值,返回值可以在其他線程中通過調用pthread_join()獲得。綜上所述,線程在一下情況下退出:=1\*GB3①程序調用pthread_exit()=2\*GB3②調用pthread_cancle()=3\*GB3③從回調函數start_routine中返回=4\*GB3④調用exit(),或者主線程退出(終止進程中所有的線程)3.6線程同步3.6.1概念 線程同步,顧名思義就是對某一塊內存的操作的先后順序。當這塊內存中的數據是只讀的,那么其他線程都可以對其進行讀取,那就沒什么問題;或者說這塊內存中的數據不會被其他線程讀取或修改,那么也不存在同步的問題。只有當多個線程同時對某一塊內存中的數據進行修改時,多個線程都要對其進行修改,那么到底是哪一個線程先修改呢,此時就涉及到同步的問題。3.6.2線程同步的幾種方法:信號量、自旋鎖、互斥量 信號量:操作系統之中,信號量通常是一個多個進程或線程用于控制訪問權限的變量或抽象數據類型。信號量可被增加或減少,但將保證對其關鍵的是原子操作,即有多個線程試圖改變一個信號量的值時,系統將保證所有操作依次進行。自旋鎖:線程為了獲得自旋鎖,通過不斷循環檢查該鎖是否可獲得。這個鎖是出于激活狀態的,但是對當前任務并不可用,這就有點類似于忙時等待的情況。一旦獲得,當前線程將會執行,同時該鎖也會被處理直到被釋放,在一些實現中可能會自動釋放。 互斥量(mutex)從概念上講有點類似于一個初始值為1二進制信號量。互斥量被獲取之后就不能再被獲取,因此對互斥體的獲取和釋放操作常常稱為加鎖和解鎖操作。互斥量只能由獲取它的線程進行釋放,如果違反這一原則,則結果是未定義的。 互斥量就是當前線程對其正在訪問的共享資源上鎖,等其用完了,再對其進行解鎖,讓其他等待的線程使用。當一個共享資源被mutex上鎖后,其他任何線程都無法對這塊共享資源進行修改。3.6.3互斥量的使用互斥量用一個pthread_mutex_t型的變量表示。其定義如下表3.15所示:表3.15pthread_mutex_init函數定義原型intpthread_mutex_init(pthread_mutex_t*mutex,constpthread_mutexattr_t*attr);函數說明對mutex進行初始化參數說明mutex指向要初始化的互斥量attr參數可以為NULL,表示使用默認屬性互斥量不用以后,應該使用下面的函數進行銷毀:intpthread_mutex_destroy(pthread_mutex_t*mutex);互斥量的主要操作函數如下表3.16所示:表3.16互斥量主要函數說明函數原型簡要說明intpthread_mutex_lock(pthread_mutex_t*mutex)上鎖intpthread_mutex_trylock(pthread_mutex_t*mutex)上鎖intpthread_mutex_unlock(pthread_mutex_t*mutex)解鎖 各函數詳細說明:pthread_mutex_lock():將其指向的資源加鎖。如果這時互已經被鎖,則調用這個函數的線程將被阻塞,直到其成為未鎖狀態。函數返回時,表示這個互斥量已經變為已鎖狀態,同時,函數的調用者成為這個互斥量的擁有者。pthread_mutex_trylock():用于對參數指向的互斥量進行加鎖。try表明試試的意思,即就是試探一下是否可獲得鎖,如果不可獲得那就一錯誤狀態返回,該線程不阻塞。pthread_mutex_unlock():將參數指向的互斥量解鎖。解鎖的對象必須是當前線程所擁有的。如果這時互斥量是未鎖狀態或不是當前線程所擁有的,則結果未定義。3.7ncurrses字符界面3.7.1ncurses簡介 一個提供了供編程人員開發字符終端界面的接口的程序庫。是一個開發運行在終端模擬器上的類似于“GUI”的應用軟件。為了減少使用遠程終端的延遲,其對屏幕的改變進行了優化。 發展史:從curses到pcurses再到ncurses Curses:第一版是在加利福尼亞伯克利分校開發的。一開始使用的是termcap庫,這個庫通常用在其他程序中,如vi編輯器; Pcurses:PavelCurtis克隆了BellLabscurses,在1986年期間由很多人維護著; Ncurses:1991年后期pcurses庫由ZeydBen-Halim接手開發,到1993年11月ncurses1.8.1版本發布了。由EricSRaymod加入了JuergenPfeifr編寫的菜單庫。自從1996年,ncurses被ThomasE.Dickey維護著。3.7.2ncurses常用函數介紹1. 初始化ncurses:WINDOW*initscr(void);函數說明:initscr()務必在其他任何影響窗口結構的函數之前調用。它捕捉terminal的特性并初始化所有curses相關的數據結構。即將屏幕刷新一下;返回值:返回一個WINDOW類型的結構體指針;Success:返回一個指向stdscr(代表默認窗口)的指針;Error:輸出標準錯誤信息然后退出。2. 輸出格式串函數:intprintw(constchar*fmt,...);//和printf函數用法一樣。3. 刷新屏幕內容:intrefresh(void);函數描述:當一個窗口中的結構被改變時,必須調用refresh刷新一下新的內容才能顯示出來。4. 輸入一個字符:intgetch(void);函數描述:獲取用戶輸入的一個字符;函數返回值:成功返回用戶按下的鍵值,失敗返回error。5. 離開ncurses:intendwin(void);函數描述:退出ncurses時將會被調用,退回到普通模式。6. 創建窗口類函數:WINDOW*newwin(intlines,intcols,intbegin_y,intbegin_x);函數說明:創建并返回一個指向特定行數和列數的指針,開始的位置由begin_y和begin_x兩個參數決定。7. 屬性設置:intattr_on(attr_tattrs,void*opt);函數說明:attr_on/attr_off是打開或者關閉特定的屬性而不影響其他的屬性。倘若用attrset的話,前面所有的設置將會被覆蓋。8. 畫線類,方框類:intmvwvline(WINDOW*win,inty,intx,chtypech,intn);9. 輸出控制函數:intscrollok(WINDOW*win,boolbf);函數說明:允許指定的窗口滾動,就上當顯示到窗口最下面一行時,可以下上滾動顯示。10. 更新窗口類函數:inttouchwin(WINDOW*win);函數說明:使整個窗口看起來被重繪了,在接下來調用refresh()或者wrefresh()時重繪整個窗口。第四章設計的編程實現4.1聊天室Socket編程連接 1、監聽連接過程 socket函數創建Socket、bind函數對創建的Socket進行綁定、listen函數對其進行監聽。Socket()初始化socket,創建新的sockfdsockfd=socket(AF_INET,SOCK_STREAM,0);此過程涉及到ip地址的處理 saddr.sin_addr.s_addr=htonl(INADDR_ANY);bind()綁定端口號和ip地址saddr.sin_family=AF_INET;saddr.sin_port=htons(PORT);bind(sockfd,(structsockaddr*)&addr,len);listen()監聽listen(sockfd,BACKLOG); 2、發送請求 (1)獲取主機信息 (2)對socket端口初始化 socket(AF_INET,SOCK_STREAM,0) addr.sin_family=AF_INET; addr.sin_port=htons(8010);//指定一個端口號,大于3000就行,因為3000一下的端口供系統使用inet_aton("",&addr.sin_addr); (3)調用connect()請求Server連接,等待服務器響應 connect(fd,(structsockaddr*)&addr,socket_len) 3、server接受請求,進行數據通信 (1)server通過accept()接收Client的請求; (2)匹配用戶操作 1).登陸,創建登陸線程 2).注冊,創建注冊線程 (3)登陸成功,接受返回信息,一系列操作后退出連接 (4)將客戶端口socket關閉 上述簡要的步驟是server端應用程序監聽請求,在此過程中,服務器線程一直處于休眠狀態,直到有client向其發送連接請求。當server收到連接請求時,server主線程調用accept()接收請求,同時為client創建一個新的線程為其服務。然后server主線程繼續等待新用戶的連接。這樣的系列步驟也就實現了socket通訊,如圖4.1所示圖4.1單個client和server之間Socket連接4.2聊天室程序設計及界面設計4.2.1服務器 服務器端的程序寫好后,需要進行聯編,可以通過寫makefile,也可以寫一個shell腳本,將要執行的文件名按終端命令的模式寫進腳本。 本設計是通過寫shell腳本,只要在終端執行腳本即可,命令如下: ./server.sh 編譯成功后會生成一個可執行文件server,如果出錯按照錯誤提示排錯即可。 在終端執行./server,服務器啟動,一直等待用戶連接并為其服務,直到退出為止,啟動界面如圖4.2所示:圖4.2服務端啟動界面4.2.2客戶端 1、進入客戶端主界面 客戶端的編譯過程和server端一樣,同樣是通過shell腳本,在終端執行./client.sh編譯成功后會生成一個可執行文件logo,終端執行:[zdz@david]$./logo客戶端啟動,啟動界面如圖4.3所示:圖4.3客戶端啟動界面剛開始的界面上有登陸、注冊、幫助、退出四個選項=1\*GB3①Login:用戶輸入自己的賬號和密碼,如圖4.4所示:圖4.4登陸窗口界面=2\*GB3②Register:填寫名字、密碼、驗證信息及驗證答案,如圖4.5所示:圖4.5注冊界面=3\*GB3③Help:顯示幫助信心,如圖4.6所示:圖4.6幫助界面=4\*GB3④Exit:退出客戶端。登陸窗口的創建過程:=1\*GB3①初始化ncurses,即刷新終端,初始化終端背景=2\*GB3②通過調用newwin(LINES/3,COLS/2,LINES/4,COLS/4)函數創建登陸注冊窗口:LINES為整個終端的行數,COLS為整個終端的

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論