• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            chaosuper85

            C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
              118 Posts :: 0 Stories :: 3 Comments :: 0 Trackbacks
            1 前言 2 Socket編程 2.1 Socket通信機(jī)制 2.2 socket通信示例圖 2.3 Socket在不同平臺(tái)上的實(shí)現(xiàn) 2.3.1 Socket在Windows平臺(tái)中的實(shí)現(xiàn) 2.3.2 Socket在Linux/Unix平臺(tái)中的實(shí)現(xiàn) 2.3.3 可移植的啟動(dòng)和結(jié)束調(diào)用代碼 2.3.4 其它移植問題 3 多線程編程 3.1 線程與進(jìn)程的不同 3.2 線程沖突與數(shù)據(jù)保護(hù) 3.3 Win32中的線程 3.3.1 線程同步 3.3.2 創(chuàng)建線程 3.4 Linux/Unix中的線程 3.5 可移植的線程代碼 4 程序?qū)嵗? 前言 Socket編程特別是多線程編程是一個(gè)很大的課題,本文針對(duì)公司最近將要實(shí)現(xiàn)的下載版和網(wǎng)頁版的CPR和CPE兩個(gè)軟件來講解socket和多線程的可移植編程技術(shù),所涉及的是其中較基礎(chǔ)的部分,以盡量滿足當(dāng)前公司需要為準(zhǔn)。 Socket編程 Socket通信機(jī)制通過socket可實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)或廣播通信程序,兩者之間的區(qū)別不大,編程時(shí)其程序流程所用代碼幾乎相同,不同的地方在于目標(biāo)地址選擇的不同。本教材所舉實(shí)例為點(diǎn)對(duì)點(diǎn)的形式,即以客戶/服務(wù)器形式來實(shí)現(xiàn)一個(gè)socket通信程序,為描述方便,我們對(duì)兩點(diǎn)分別命名為Server和Client。Socket實(shí)現(xiàn)Server 與Client之間的通信的過程,非常象電信局的普通電話服務(wù),為了更好的理解socket,以下就以電信局的電話服務(wù)作為類比來說明socket的通信機(jī)制: * 電信局提供電話服務(wù)類似我們這的Server,普通電話用戶類似我們這的Client。 * 首先電信局必須建立一個(gè)電話總機(jī)。這就如果我們必須在Server端建立一個(gè)Socket(套接字),這一步通過調(diào)用socket()函數(shù)實(shí)現(xiàn)。 * 電信局必須給電話總機(jī)分配一個(gè)號(hào)碼,以便使用戶要撥找該號(hào)碼得到電話服務(wù),同時(shí)接入該電信局的用戶必須知道該總機(jī)的號(hào)碼。同樣,我也在Server端也要為這一套接字指定一port(端口),并且要連接該Server的Client必須知道該端口。這一步通過調(diào)用bind()函數(shù)實(shí)現(xiàn)。 * 接下來電信局必須使總機(jī)開通并使總機(jī)能夠高效地監(jiān)聽用戶撥號(hào),如果電信局所提供服務(wù)的用戶數(shù)太多,你會(huì)發(fā)現(xiàn)撥打電信局總機(jī)老是忙音,通常電信局內(nèi)部會(huì)使該總機(jī)對(duì)應(yīng)的電話號(hào)碼連到好幾個(gè)負(fù)責(zé)交換的處理中心,在一個(gè)處理中心忙于處理當(dāng)前的某個(gè)用戶時(shí),新到用戶可自動(dòng)轉(zhuǎn)到一下處理中心得到服務(wù)。同樣我們的Server端也要使自己的套接口設(shè)置成監(jiān)聽狀態(tài),這是通用listen()函數(shù)實(shí)現(xiàn)的,listen()的第二個(gè)參數(shù)是等待隊(duì)列數(shù),就如同你可以指定電信局的建立幾個(gè)負(fù)責(zé)交換的處理中心。 * 用戶知道了電信局的總機(jī)號(hào)后就可以進(jìn)行撥打請(qǐng)求得到服務(wù)。在Winsock的世界里做為Client端是要先用socket()函數(shù)建立一個(gè)套接字,然后調(diào)connect()函數(shù)進(jìn)行連接。當(dāng)然和電話一樣,如果等待隊(duì)列數(shù)滿了、與Server的線路不通或是Server沒有提供此項(xiàng)服務(wù)時(shí),連接就不會(huì)成功。 * 電信局的總機(jī)接受了這用戶撥打的電話后負(fù)責(zé)接通用戶的線路,而總機(jī)本身則再回到等待的狀態(tài)。Server也是一樣,調(diào)用accept()函數(shù)進(jìn)入監(jiān)聽處理過程,Server端的代碼即在中處暫停,一旦Server端接到申請(qǐng)后系統(tǒng)會(huì)建立一個(gè)新的套接字來對(duì)此連接做服務(wù),而原先的套接字則再回到監(jiān)聽等待的狀態(tài)。 * 當(dāng)你電話掛完了,你就可以掛上電話,彼此間也就離線了。Client和Server間的套接字的關(guān)閉也是如此;這個(gè)關(guān)閉離線的動(dòng)作,可由Client端或Server端任一方先關(guān)閉,這也與電話局的服務(wù)類似。 從以上情況可以看出在服務(wù)器端建立一個(gè)套接字,進(jìn)入監(jiān)聽狀態(tài)到接收到一個(gè)客戶端的請(qǐng)求的過程如下: socket()->bind()->listen()->accept()->recv()->send() 在客戶端則要簡(jiǎn)單得多,其調(diào)用過程如下: socket()->connect()->send()->recv() socket通信示例圖 下圖顯示的通信方式用在許多場(chǎng)合,比如HTTP協(xié)議中用的就是這種方式。FTP、TELNET等協(xié)議也與此大致相同,不同之外在于FTP、TELNET這類協(xié)議會(huì)多次重復(fù)send()和recv()的調(diào)用。 Socket在不同平臺(tái)上的實(shí)現(xiàn) Socket在Windows平臺(tái)中的實(shí)現(xiàn) Socket在Windows中的實(shí)現(xiàn)稱為Winsock,核心文件是winsock.dll(或winsock32.dll)。這個(gè)動(dòng)態(tài)鏈接庫負(fù)責(zé)提供標(biāo)準(zhǔn)的socket調(diào)用API和其它幾個(gè)特定的Windows平臺(tái)專用API,另外它還實(shí)現(xiàn)了適用于Windows消息機(jī)制的同步socket通信機(jī)制。在Winsock中所有非標(biāo)準(zhǔn)的socket調(diào)用均以WSA三個(gè)字母開頭命名。 由于Win32的各個(gè)平臺(tái)均支持線程,我采用同步socket通信機(jī)制來編寫Winsock程序不可取。一是多線程的系統(tǒng)中對(duì)同步通信有更好的解決辦法,二是同步通信機(jī)制程序沒有很好的兼容性。 Winsock中有兩個(gè)比較重要的函數(shù)就是WSAStartup()和WSACleanup(),它們的作用在于對(duì)Winsock動(dòng)態(tài)鏈接庫進(jìn)行一些初始化或清除工作。在一個(gè)進(jìn)程中,沒有調(diào)用WSAStartup()之前將無法正確調(diào)用任何其它標(biāo)準(zhǔn)的socket函數(shù)。 Socket在Linux/Unix平臺(tái)中的實(shí)現(xiàn) Socket最早是在Unix平臺(tái)上實(shí)現(xiàn)的,socket調(diào)用已是當(dāng)今Unix平臺(tái)的標(biāo)準(zhǔn)系統(tǒng)調(diào)用。Linux的網(wǎng)絡(luò)部分模塊幾乎是原封不動(dòng)的從Unix中遷移過來的,其函數(shù)的調(diào)用方式基本與Unix相同。 在Linux/Unix平臺(tái)中,如果你正在向一個(gè)套接字發(fā)信息,而遠(yuǎn)端計(jì)算機(jī)又關(guān)閉了該端口,將產(chǎn)生一個(gè)SIGPIPE信號(hào),該信號(hào)的默認(rèn)處理過程將中止當(dāng)前進(jìn)程,為使我們的socket程序能夠健壯的運(yùn)行,必須接管該信息的處理過程,一般情況使系統(tǒng)忽略該信號(hào)即可。 可移植的啟動(dòng)和結(jié)束調(diào)用代碼 根據(jù)以上兩點(diǎn)所述的socket中不同平臺(tái)中的實(shí)現(xiàn)方式,可以編寫出可移植的socket初始化和結(jié)束調(diào)用代碼如下: bool bsocket_init() //初始化socket 調(diào)用 { #ifdef _WIN32 WSADATA wsad; WORD wVersionReq; wVersionReq=MAKEWORD(1,1); //清求不低于1.1版本的Winsock if(WSAStartup(wVersionReq,&wsad)) return false; return true; #else signal(SIGPIPE, SIG_IGN); //忽略SIGPIPE信號(hào) return true; #endif } void bsocket_cleanup() //結(jié)束socket 調(diào)用 { #ifdef _WIN32 WSACleanup(); #else signal(SIGPIPE, SIG_DFL); //恢復(fù)SIGPIPE信號(hào) return; #endif } 其它移植問題 關(guān)閉socket Windows中關(guān)閉socket的調(diào)用為closesocket(socket sock_in); Linux為close(socket sock_in); Socket地址長(zhǎng)度類型 Socket的許多調(diào)用中均用到socket地址長(zhǎng)度做為參數(shù),該參數(shù)在Windows中為int類型,在Linux中為socklen_t類型。 無效的socket Windows中調(diào)用socket()和accept()時(shí),如果返回的是無效的socket,則該值為INVALID_SOCKET,Linux中為-1。 調(diào)用錯(cuò)誤返回 Windows中除上述的socket()和accept()以及幾個(gè)少數(shù)的調(diào)用外,大部分調(diào)用在出錯(cuò)時(shí)返回值為SOCKET_ERROR,Linux中為-1。 多線程編程 線程與進(jìn)程的不同 當(dāng)把一個(gè)可執(zhí)行程序裝載在內(nèi)存中后,不管你是你是準(zhǔn)備它或是已經(jīng)執(zhí)行它或是被暫停執(zhí)行,它都被稱為一個(gè)進(jìn)程。你可以稱一個(gè)進(jìn)程為一個(gè)可執(zhí)行程序在內(nèi)存中的實(shí)例。雖然一個(gè)進(jìn)程可以復(fù)制自己,也可以調(diào)用其它進(jìn)程或與其它進(jìn)程通信,全基本上進(jìn)程是由操作系統(tǒng)直接管理的資源。每一個(gè)進(jìn)程均有一個(gè)與其它進(jìn)程獨(dú)立的操作系統(tǒng)運(yùn)行環(huán)境,比如環(huán)境變量、當(dāng)前目錄等。 線程是一種可執(zhí)行體,它表示對(duì)CPU運(yùn)行時(shí)間的占有權(quán),線程也是操作系統(tǒng)中的一種資源,它的創(chuàng)建、中止最終要由操作系統(tǒng)來實(shí)現(xiàn)。但線程每一個(gè)線程更多表現(xiàn)為由一個(gè)進(jìn)程直接來管理。每一個(gè)線程均有一個(gè)與其它線程不同的硬件運(yùn)行環(huán)境,比如各CPU中通用寄存器和狀態(tài)寄存器的值。 一個(gè)進(jìn)程的創(chuàng)建需要操作系統(tǒng)做大量工作,如設(shè)置環(huán)境變量、裝裁可執(zhí)行代碼、分配進(jìn)程資源等,進(jìn)程間的切換也需做大量工作。而線程的創(chuàng)建的切換相對(duì)來說要簡(jiǎn)單得多,操作也能夠以最快的速度來實(shí)現(xiàn)不同執(zhí)行體之間的切換。 另一方面,由于所有線程共享一個(gè)進(jìn)程空間,線程間的通信變得十分簡(jiǎn)單;而進(jìn)程之間由于各自內(nèi)存相互隔離,進(jìn)程之間通信只能通過管道、發(fā)送系統(tǒng)消息或信號(hào)來實(shí)現(xiàn)。 線程沖突與數(shù)據(jù)保護(hù) 由于同一進(jìn)程中的線程由操作系統(tǒng)并發(fā)執(zhí)行。當(dāng)一個(gè)進(jìn)程中的許多線程要修改某些公用變量時(shí)就存在數(shù)據(jù)保護(hù)問題。解決這個(gè)問題的通常辦法是向操作系統(tǒng)申請(qǐng)一個(gè)線程同步對(duì)象,操作系統(tǒng)只允許同時(shí)有一個(gè)少數(shù)個(gè)線程訪問受保護(hù)的數(shù)據(jù)。 Win32中的線程 線程同步 Win32中可用于線程同步的對(duì)象有CRITICAL_SECTION, Event, Mutex, Semaphore和Waitable timer等。 其中CRITICAL_SECTION比較適于解決在單一進(jìn)程多線程的沖突問題。 與CRITICAL_SECTION有關(guān)的幾個(gè)函數(shù)為: * InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 該函數(shù)初始化一個(gè)CRITICAL_SECTION。 * DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 該函數(shù)清除一個(gè)CRITICAL_SECTION。 * EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 該函數(shù)使本線程取得CRITICAL_SECTION控制權(quán),代碼進(jìn)入保護(hù)狀態(tài)。 * LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 該函數(shù)使本線程放棄CRITICAL_SECTION控制權(quán),代碼退出保護(hù)狀態(tài)。 創(chuàng)建線程 Win32中最常用的創(chuàng)建線程的函數(shù)為CreateThread(),該函數(shù)的格式為: HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId); * lpThreadAttributes: 輸入?yún)?shù)。指定是否本次函數(shù)返回的線程句柄可以被子進(jìn)程繼承,可以為NULL。在CPR/CPE兩個(gè)產(chǎn)品下載版和網(wǎng)頁版的開發(fā)過程代碼使用NULL應(yīng)可滿足需要。 * dwStackSize : 輸入?yún)?shù)。指定指定的線程堆棧大小,可以為0,如果為0,使用默認(rèn)堆棧大小。在CPR/CPE兩個(gè)產(chǎn)品下載版和網(wǎng)頁版的開發(fā)過程代碼使用0應(yīng)可滿足需要。 * lpStartAddress : 輸入?yún)?shù)。指向線程函數(shù),該函數(shù)包含線程的執(zhí)行代碼。 * lpParameter : 輸入?yún)?shù)。指定要傳給線程的參數(shù)塊內(nèi)存地址。 * lpThreadId : 輸出參數(shù)。該參數(shù)在調(diào)用結(jié)束后將包含被創(chuàng)建線程的ID。 Linux/Unix中的線程 當(dāng)前各版本的Linux和Unix本身并不支持線程,但在Linux/Unix各版本均自帶pthread線程包及它們的開發(fā)運(yùn)行頭文件和庫文件。 與Win32類似,pthread包中用于線程同步的對(duì)象也有多種,在我們的開發(fā)應(yīng)用中,使用pthread_mutex_t即可滿足需求。與pthread_mutex_t相關(guān)的幾個(gè)函數(shù)為: * pthread_mutex_init(pthread_mutex_t * p, pthread_mutexattr_t * pa); 該函數(shù)初始化一個(gè)pthread_mutex_t,其中pa參數(shù)可以設(shè)置pthread_mutex_t的屬性。與Win32中多線程的機(jī)制稍有不同,在默認(rèn)情況下,pthread中對(duì)同一線程的多次保護(hù)請(qǐng)求會(huì)造成互鎖,以我們產(chǎn)品開發(fā)的情況來看,要修改pthread的屬性方可滿足實(shí)際需要,但pthread中設(shè)置多線程同步屬性的方法的可移植性不高。對(duì)同一線程多次申請(qǐng)保護(hù)的問題可以記錄線程ID的辦法來解決。 * pthread_mutex_destroy(pthread_mutex_t * p); 清除一個(gè)已初始化的pthread_mutex_t。 * pthread_mutex_lock(pthread_mutex_t * p); 請(qǐng)求取得pthread_mutex_t控制權(quán),進(jìn)入代碼保護(hù)。 * pthread_mutex_unlock(pthread_mutex_t * p); 放棄一個(gè)pthread_mutex_t控制權(quán),退出代碼保護(hù)。 可移植的線程代碼 (參見程序?qū)嵗? 程序?qū)嵗? #include #ifdef _WIN32 #include #define socklen_t int #else #include #include #include #include #include #include #include #include #include #include #define closesocket(_x) close(_x) #endif #ifdef _WIN32 #define THREAD_PROC WINAPI #define THREAD_RETURN DWORD #define THREAD_PARAM void * #define THREAD_ID DWORD #define CPO CRITICAL_SECTION #define CPO_Init(_x) InitializeCriticalSection(&_x) #define CPO_Dele(_x) DeleteCriticalSection(&_x) #define CPO_Enter(_x) EnterCriticalSection(&_x) #define CPO_Leave(_x) LeaveCriticalSection(&_x) #else struct CPO { pthread_mutex_t m; pthread_t t;}; #define THREAD_PROC #define THREAD_RETURN void * #define THREAD_PARAM void * #define THREAD_ID pthread_t #define CPO_Init(_x) { _x.t=0; pthread_mutex_init(&_x.m, NULL); } #define CPO_Dele(_x) { pthread_mutex_destroy(&_x.m); } #define CPO_Enter(_x) while(true) \ { \ if(_x.t==0) \ { \ pthread_mutex_lock(&_x.m); \ _x.t=pthread_self(); \ break;\ }\ if(pthread_self()==_x.t)\ break; \ pthread_mutex_lock(&_x.m); \ _x.t=pthread_self();\ break; \ } #define CPO_Leave(_x) { pthread_mutex_unlock(&_x.m); _x.t=0; } #endif typedef THREAD_RETURN THREAD_PROC THREAD_FUNCTION(THREAD_PARAM thread_param); #ifdef _WIN32 THREAD_ID bcreate_thread(THREAD_FUNCTION pfun, void * pparam) { THREAD_ID tid; if(CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pfun, (LPVOID)pparam, 0, &tid)==NULL) return (THREAD_ID)-1; return tid; } #else THREAD_ID bcreate_thread(THREAD_FUNCTION pfun, void * pparam) { THREAD_ID tid; if(pthread_create(&tid, NULL, (void * (*)(void *))pfun, pparam)<1) return (THREAD_ID)-1; pthread_detach(tid); return tid; } #endif #ifndef SOCKET #define SOCKET int #endif #ifndef SOCKET_ERROR #define SOCKET_ERROR -1 #endif #ifndef INVALID_SOCKET #define INVALID_SOCKET -1 #endif bool bsocket_init() { #ifdef _WIN32 WSADATA wsad; WORD wVersionReq; wVersionReq=MAKEWORD(1,1); if(WSAStartup(wVersionReq,&wsad)) return false; return true; #else signal(SIGPIPE, SIG_IGN); return true; #endif } void bsocket_cleanup() { #ifdef _WIN32 WSACleanup(); #else signal(SIGPIPE, SIG_DFL); return; #endif } #define WEB_SERVER_PORT 90 #define WEB_SERVER_IP "127.0.0.1" #define HTTP_HEAD "HTTP/1.0 200 OK\x0d\x0a""Content-type: text/html\x0d\x0a\x0d\x0a" #define WELCOME_HTML "\n\nWelcome to my Website!
            \n
            g_hits: %d
            \n\n\n" CPO g_cpo; int g_hits; THREAD_RETURN THREAD_PROC do_accept(THREAD_PARAM param) { SOCKET sock=((SOCKET *)param)[0]; char ptmp[2048]; recv(sock, ptmp, 2048, 0); CPO_Enter(g_cpo); int a=g_hits; sprintf(ptmp, WELCOME_HTML, a++); g_hits=a; CPO_Leave(g_cpo); send(sock, HTTP_HEAD, strlen(HTTP_HEAD), 0); send(sock, ptmp, strlen(ptmp), 0); closesocket(sock); return 0; } main(int argc,char ** argv) { char perror[1024]; SOCKET s,rs; sockaddr_in sin,rsin; socklen_t slen; bool result=false; perror[0]=0; CPO_Init(g_cpo); g_hits=0; if(!bsocket_init()) { strcpy(perror, "Can't init socket!"); goto error_out; } s=socket(PF_INET,SOCK_STREAM,0); if(s==INVALID_SOCKET) { strcpy(perror, "Can't create socket!"); goto error_out; } sin.sin_family=AF_INET; sin.sin_port=htons(WEB_SERVER_PORT); sin.sin_addr.s_addr=inet_addr(WEB_SERVER_IP); slen=sizeof(sin); if(bind(s,(sockaddr *)&sin,slen)==SOCKET_ERROR) { strcpy(perror, "Can't bind socket!"); goto error_out; } if(listen(s,5)==SOCKET_ERROR) { strcpy(perror, "Can't listen on this socket!"); goto error_out; } printf("web server running......\n"); slen=sizeof(rsin); while(true) { rs=accept(s,(sockaddr *)&rsin,&slen); if(rs==INVALID_SOCKET) { strcpy(perror, "accept() a INVALID_SOCKET!"); break; } bcreate_thread(do_accept, &rs); } result=true; error_out: if(s!=INVALID_SOCKET) closesocket(s); if(rs!=INVALID_SOCKET) closesocket(rs); if(!result) { printf(perror); printf("\n"); } CPO_Dele(g_cpo); bsocket_cleanup(); return 0; }
            posted on 2010-01-26 00:07 chaosuper 閱讀(979) 評(píng)論(0)  編輯 收藏 引用

            只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            热99re久久国超精品首页| 亚洲精品无码专区久久同性男| 一本久久精品一区二区| 老司机国内精品久久久久| 国产精品美女久久久久| 午夜精品久久久久久毛片| 久久久久亚洲精品无码网址 | 久久综合色区| 青青草原综合久久大伊人导航| 午夜精品久久久久久久无码| 久久久黄色大片| 久久久久人妻一区精品色| 欧美亚洲国产精品久久蜜芽 | 久久精品国产2020| 久久久久久久综合日本亚洲| 欧美精品福利视频一区二区三区久久久精品 | 久久综合久久鬼色| 亚洲乱码精品久久久久..| 国产欧美一区二区久久| 热久久国产欧美一区二区精品| 亚洲香蕉网久久综合影视 | 亚洲精品美女久久久久99小说| 亚洲AV日韩AV永久无码久久| 国产精品久久久福利| 亚洲性久久久影院| 久久精品免费观看| 久久综合给合久久狠狠狠97色69 | 久久精品国产亚洲麻豆| 久久人人爽人人爽人人片av麻烦 | 久久久噜噜噜久久中文福利| 性做久久久久久久久老女人| 99久久婷婷国产综合亚洲| 亚洲精品乱码久久久久66| 国内精品久久久久影院老司| 久久久久亚洲AV综合波多野结衣| 人人狠狠综合久久亚洲88| 久久久久久久97| 久久精品视频网| 婷婷久久综合九色综合绿巨人| 久久天天躁狠狠躁夜夜av浪潮| 久久久中文字幕日本|