Linux-網絡通信編程_第1頁
Linux-網絡通信編程_第2頁
Linux-網絡通信編程_第3頁
Linux-網絡通信編程_第4頁
Linux-網絡通信編程_第5頁
已閱讀5頁,還剩78頁未讀 繼續免費閱讀

下載本文檔

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

文檔簡介

Linux

操作系統與程序設計軟件工程系何海濤內容1.概念:協議套接字通信2.TCPUDPFTP通信3.多線程多進程服務器SchoolofComputerSicence幾個概念進程通信:單機:進程之間交換信息(通過pipe,signal等)網絡:不同計算機(軟/硬件)之間的信息交換--最終,仍是進程通信需要解決的問題進程標識協議差錯控制、流量控制、報文順序、連接管理......解決方案TCP/IP協議+Socket編程機制SchoolofComputerSicence服務和端口服務器客戶上網電子郵件文件傳輸一個IP65536個端口SchoolofComputerSicence編寫網絡通信程序TCP/IP協議制定了通信雙方通信的細節:如數據包的格式,建立連接的形式等協議的實現在每個系統上是不一樣的,而且協議很復雜,具體實現用到了成百上千個函數和無數的結構體,即使程序員知道了協議細則,要直接調用協議中各種函數完成一個網絡通信程序是很困難的如何簡化:把協議“封裝”的簡單一點,如同打電話SchoolofComputerSicenceSocket接口socket:套接字,是一組接口,使得編寫網絡通信程序,如同打電話般簡單Socket的英文原義是“孔”或“插座”:一臺主機猶如布滿各種插座的房間,每個插座有一個編號,有的插座提供220伏交流電,有的提供110伏交流電,有的則提供有線電視節目。客戶軟件將插頭插到不同編號的插座,就可以得到不同的服務。SchoolofComputerSicencesocket編程和打電話IP地址和端口三次握手或UDPsendreceive電話用哪個運營商的電話打給誰撥號和接聽交談掛斷socket用什么協議通信和誰通信請求和接受收發信息關閉socket步驟對比SchoolofComputerSicencesocket實例網絡通信涉及到2臺電腦如果是C/S模式:一臺作為“服務器”,一臺為“客戶機”也可以對等P2P:地位平等服務器:提供服務接受客戶端的連接響應客戶端的要求給客戶端發消息客戶端:向服務器發送請求從服務器收取消息SchoolofComputerSicencesocket實例服務器程序先運行,等待客戶端請求/消息到來客戶端向服務器發送一個字符串hello服務器在終端輸出客戶端發來的字符串SchoolofComputerSicence最簡單的UDP服務器udps.c#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>intmain(intargc,char**argv){intsockfd;structsockaddr_inservaddr;charbuff[1024];intn,sinsize;

sockfd=socket(AF_INET,SOCK_DGRAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=INADDR_ANY;servaddr.sin_port=htons(9091);bind(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));sinsize=sizeof(servaddr);n=recvfrom(sockfd,buff,1024,0,NULL,&sinsize);buff[n]='\0';printf("recvmsgfromclient:%s\n",buff);close(sockfd);}SchoolofComputerSicence最簡單的UDP客戶端udpc.c#include…...intmain(intargc,char**argv){intsockfd,n;charsendline[1024];structsockaddr_inservaddr;sockfd=socket(AF_INET,SOCK_DGRAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(9091);servaddr.sin_addr.s_addr=inet_aton("");n=sizeof(structsockaddr);sendto(sockfd,"hello",5,0,(structsockaddr*)&servaddr,n);close(sockfd);exit(0);}SchoolofComputerSicence編譯運行同一臺電腦,打開2個終端分別編譯運行服務器程序和客戶端程序注意:不能不指定輸出的可執行文件名,必須使用-o參數,否則,兩個程序默認的可執行文件都是a.out,會沖突不同的計算機之間需要服務器的IP地址客戶端程序中修改IP地址使用命令ifconfig查看本機的IP地址SchoolofComputerSicence基本socketAPIsocket()創建一個套接字,#include<sys/socket.h>函數原型:intsocket(intdomain,inttype,intprotocol);參數說明domain:通信協議族,即地址族,通常是AF_INET(TCP/IP(V4))type:套接字類型SOCK_STREAM:TCP協議SOCK_DGRAM:UDP協議SOCK_RAW:原始套接字protocol:通信協議,設置為0,由內核根據指定的類型和協議族使用默認的協議返回值:成功時,返回一個大于等于0的文件描述符:可以用文件讀寫函數來操作socket失敗時,返回一個小于0的值關閉:close(socketfd)類似于關閉文件,回收資源intsockfd;sockfd=socket(AF_INET,SOCK_DGRAM,0);SchoolofComputerSicence地址的表示---sockaddr_in結構體//定義結構體變量servadd

structsockaddr_inservaddr;//初始化變量

memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=INADDR_ANY;

servaddr.sin_port=htons(9091);程序中,需要用一個數據結構保存地址信息,包括:端口號和IP地址等原本地址表示用的結構體是:sockaddr,但因為這個結構體使用不方便,人們又重新創建了個sockaddr_in結構體來代替sockaddrSchoolofComputerSicencesockaddr_in結構體structsockaddr_in{

shortintsin_family;/*地址族*/

unsignedshortintsin_port;/*端口號*/

structin_addrsin_addr;/*IP地址*/

unsignedcharsin_zero[8];/*湊數,為了使sockaddr_in和sockaddr長度相同*/};in_addr結構體:存放IP地址structin_addr{unsignedlongs_addr;//32-bit無符號長整形};即:ip地址本身是一個數,但平時使用的是字符串,如"6",把字符串賦值給需要轉換,使用inet_aton函數(internetasctonum)如:inet_aton("")本機IP地址的賦值操作為:

結構體變量.sin_addr.s_addr=inet_aton("");或

sin_addr=INADDR_ANY;表示填入本機IP地址SchoolofComputerSicence初始化結構體memset:清零當sin_addr=INADDR_ANY時,填入本機IP地址端口號:除了系統保留的(1~1024),可以自己指定端口(1025~65535),但需要轉換字節順序(用htons函數)//定義結構體變量servadd

structsockaddr_inservaddr;//初始化變量

memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=INADDR_ANY;

servaddr.sin_port=htons(9091);SchoolofComputerSicence字節順序不同的CPU有不同的字節順序類型,這些字節順序類型指的是整數在內存中保存的順序,即主機字節順序(HBO,HostByteOrder)常見的有兩種:大端模式(big-endian):地址的高位存儲值的低位,如部分MIPS,POWERPC機器小端模式(little-endian):地址的低位存儲值的低位,如Intelx86機器以unsigned

int

value

=

0x12345678為例其值的低位和高位分別是?低位高位x86電腦上:addraddr+1addr+2addr+378563412大端電腦上:addraddr+1addr+2addr+312345678網絡上有各種各樣的機器,為保證解析正確性和可移植性,要統一順序。host-to-network:hton():把主機順序轉換為網絡順序(大端順序)network-to-host:ntoh():收到數據把時把網絡字節轉換為主機根據轉換的數類型:short,long,共4個函數:htons,htonl,ntohs,ntohlSchoolofComputerSicence指定服務器端口號(1).HTTP協議代理服務器常用端口號:80/8080/3128/8081/9080(2).SOCKS代理協議服務器常用端口號:1080(3).FTP(文件傳輸)協議代理服務器常用端口號:21(4).Telnet(遠程登錄)協議代理服務器常用端口:23(5).SMTP/POP3:25/110小于256的端口作為保留端口通常自己指定的端口號可以大于1024structsockaddr_in

servaddr;memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=inet_aton("");

servaddr.sin_port=htons(9091);SchoolofComputerSicence綁定bindLinux下一切皆文件發送和接受網絡數據,也是通過讀寫文件完成這里的文件,指的是"socket"。為了實現網絡通信的目標,還需要把socket文件和IP地址,端口號等綁定(關聯)起來。WriteSOCKET文件主機端口端口端口網絡SchoolofComputerSicencebind()函數intbind(intsockfd,structsockaddr*my_addr,socklen_taddrlen);參數說明sockfd:調用socket返回的文件描述符my_addr:保存地址信息(IP地址和端口)addrlen:設置為sizeof(structsockaddr)返回值成功時,返回0失敗時,返回-1(如端口被占用)intsockfd;

structsockaddr_inservaddr;sockfd=socket(AF_INET,SOCK_DGRAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=inet_aton("");servaddr.sin_port=htons(9091);

bind(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));servaddr的類型是sockaddr_in,不是sockaddr*類型,做一個強制類型轉換SchoolofComputerSicence接收網絡信息一切就緒,等待從網絡來的消息(讀socket文件)intrecvfrom(intsockfd,void*buf,intlen,unsignedintflags,structsockaddr*from,int*fromlen);sockfd:將要從其接收數據的套接字buf:存放消息接收后的緩沖區len:buf所指緩沖區的容量flags:接收數據的一些參數,為0則為默認from:保存數據來源(ip,端口),如不需保存可以設為NULLfromlen:from的長度地址(注意是指針)成功執行時,返回接收到的字節數。另一端已關閉則返回0。失敗返回-1。recvfrom默認是阻塞型structsockaddr_inservaddr;charbuff[1024];sinsize=sizeof(servaddr);

n=recvfrom(sockfd,buff,1024,0,NULL,&sinsize);SchoolofComputerSicenceRecvfrom應用舉例執行到recvfrom,程序暫停,等待從端口過來消息。有信息到來,把消息存放到緩沖區,并解析包,把源地址和端口存放到相應變量中,然后繼續往下執行如果一次到來的消息緩沖區放不下,則丟棄多余的包charbuff[4096];structsockaddr_inservaddr;//服務器地址信息structsockaddr_inclientaddr;//客戶端地址,用來保存從哪發過來的size=sizeof(sockaddr);n=recvfrom(sockfd,buff,4096,0,(structsockaddr*)&clientaddr,&size);buff[n]='\0';printf("recvmsgfromclient:%s\n",buff);printf("消息來自于IP:%s\n",inet_ntoa(clientaddr.sin_addr));

……在64位系統上,要用#include<arpa/inet.h>,inet_ntoa才能正常運行SchoolofComputerSicenceIP地址轉換將點分十進制字符串轉換成長整型數inet_addr("")inet_aton("")intinet_pton(intaf,constchar*src,void*dst);inet_pton(AF_INET,ip,&servaddr.sin_addr);第一個參數af是地址族,轉換后存在dst中將長整型IP地址轉換成點分字符串char*inet_ntoa(structin_addraddr)如inet_ntoa(clientaddr.sin_addr)constchar*inet_ntop(intaf,constvoid*src,char*dst,socklen_tlen);把src指向的in_addr數字地址轉換為字符串,len:dst的長度推薦使用inet_pton和inet_ntop,其他函數為不可重入函數SchoolofComputerSicence完善UDP服務器自己動手完善服務器1.輸出客戶端信息2.給recvfrom加上循環,使服務器能一直接受客戶端連接好習慣是不要省略函數返回值的判斷,否則出錯時不容易定位......#include<arpa/inet.h>intmain(intargc,char**argv){intsockfd;structsockaddr_inservaddr,clientAddr;charbuff[1024];intn,sinsize;sockfd=socket(AF_INET,SOCK_DGRAM,0);

//判斷socket是否創建成功memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=inet_aton("");servaddr.sin_port=htons(9091);bind(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));

//判斷bind是否綁定成功sinsize=sizeof(servaddr);

//修改recvfrom,填入客戶端地址

//n=recvfrom(sockfd,buff,1024,0,NULL,&sinsize);buff[n]='\0';

//輸出客戶端IP地址,和端口號(clientAddr.sin_port)SchoolofComputerSicenceUDP客戶端發送數據的步驟建立socket發送數據sendto:給出服務器/目標端的IP地址和端口號,以及要發送數據的首地址說明:不需要綁定bind,在發送數據時系統自動隨機選擇一個端口發送數據SchoolofComputerSicencesendto發送數據函數函數原型intsendto(intsockfd,constvoid*msg,intlen,unsignedintflags,conststructsockaddr*to,inttolen);參數解釋sockfd:同recvfrom,獲得的socket文件句柄msg:要發送數據的指針len:數據長度flags:一些參數to:發送的目的地tolen:to結構體的長度返回值成功時,返回實際發送的數據的字節數失敗時,返回-1SchoolofComputerSicence完善UDP客戶端修改程序:1.從鍵盤輸入字符串發送2.給sendto加上循環,可以循環發送(*)給main函數帶參數,可以給不同IP的服務器發送#include…...intmain(intargc,char**argv){intsockfd,n;charsendline[1024];structsockaddr_inservaddr;sockfd=socket(AF_INET,SOCK_DGRAM,0);

//判斷socket是否創建成功memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(9091);servaddr.sin_addr.s_addr=inet_addr("");n=sizeof(structsockaddr);

//從鍵盤輸入一個字符串發送

//sendto(sockfd,.....,...,0,(structsockaddr*)&servaddr,n);

close(sockfd);exit(0);}SchoolofComputerSicence幾點注意事項字符串定義和輸入charstr[80]輸入使用fgets(str,80,stdin),在linux下用gets會有警告,因gets不安全main函數參數使用argc:參數個數,命令本身算一個參數

servaddr.sin_addr.s_addr=inet_addr(argv[1]);argv[1]代表第一個參數,如./a.out

其中的“”就是argv[1]SchoolofComputerSicenceUDP實現一個“小”文件傳輸文件傳輸和消息發送原理一樣服務器端:收到信息,寫入文件FILE*fout=fopen("test_rec","wb");n=recvfrom(.......);fwrite(buff,1,n,fout)//把n個字節的buff寫入到fout中....fclose(fout);//關閉文件客戶端:從小文件中讀取數據---abc.txt要少于1024字節FILE*fin=fopen("abc.txt","rb");//打開文件abc.txtt=fread(buff,1,1024,fin);//從文件中讀數據到buff中sendto(....,buff,t....);//發送t個字節到服務器fclose(fin)SchoolofComputerSicence傳輸大文件循環發送:while((t=fread(buff,1,1024,fin))>0)當文件沒結束(讀的字節數>0)時,循環發送循環接收:當客戶端結束傳送時,服務器端應結束接收。以下服務器端結束的條件可行嗎?自行驗證,若不可行,請提出辦法while(n>0){n=recvfrom(.......);fwrite(buff,1,n,fout)}........SchoolofComputerSicence思考UDP協議傳輸文件有何缺點?SchoolofComputerSicence練習:寫一個聊天程序即可以收,又可以發一個程序,不分客戶端和服務器端,都是一樣的。運行在2臺電腦上,可以聊天,如編譯的程序名為talk:./talk5表示和某個IP的電腦聊天如果是同一臺電腦上的兩個終端,無法用一個程序完成(因為不能都綁定同一個端口),只需要修改下端口號重新編譯運行即可思路:用多進程----一個進程用于發送,一個進程用于接收多線程也類似SchoolofComputerSicence*sendfile#include<sys/sendfile.h>ssize_tsendfile(intout_fd,intin_fd,off_t*offset,size_tcount);將一個本地文件通過socket發送出去通常的做法是:打開文件fd和一個socket,然后循環地從文件fd中read數據,并將讀取的數據send到socket中。這樣,每次讀寫我們都需要兩次系統調用,并且數據會被從內核拷貝到用戶空間(read),再從用戶空間拷貝到內核(send)sendfile就將整個發送過程封裝在一個系統調用中,避免了多次系統調用,避免了數據在內核空間和用戶空間之間的大量拷貝(零拷貝機制)。可以通過sendfile提高效率SchoolofComputerSicence聊天程序結構初始化同前UDP程序和udps,udpc一樣,但不綁定端口(是為了讓接收和發送用不同的端口)fork子進程:發送,同前可以有bind,也可以沒有父進程:接收注意:把bind放到父進程中來調試運行udps.c和udpc.c,在可以循環接收和循環發送的基礎上,對其中某個程序進行修改(添加fork)即可,注意pid=fork()的位置,對structsockaddr_in變量賦值的代碼父子進程可以共用,不用再寫一遍intpid;.....pid=fork();if(pid==0){while(1){

input...

sendto....

}}else{bind....while(1){

recvfrom.....printf

}}SchoolofComputerSicenceUDPUserDatagramProtocol,用戶數據包協議,提供面向事務的簡單不可靠信息傳送服務UDP有不提供數據包分組、組裝和不能對數據包進行排序的缺點,也就是說,當報文發送之后,是無法得知其是否安全完整到達的;同樣,服務器也無法知道客戶端的狀態在網絡質量較差的環境下,UDP協議數據包丟失會比較嚴重。但是由于UDP的特性:它不屬于連接型協議,因而具有資源消耗小,處理速度快的優點,所以通常音頻、視頻和普通數據在傳送時使用UDP較多SchoolofComputerSicenceTCP通信使用UDP協議發送數據,程序中直接用sendto發送,沒有確認服務器已經就緒TCP:在通信之前需要建立連接(三次握手)服務器流程:1.socket:使用SOCKET_STREAM,其他同UDPsocket(AF_INET,SOCKET_STREAM,0)2.bind:同UDP3.listen:監聽,等待客戶端發送連接請求4.accept:如果條件允許,接受請求,向客戶端發送響應5.recv:接受數據(和recvfrom類似)6.處理收到的信息SchoolofComputerSicenceTCP服務器的listen函數函數原型intlisten(intsockfd,intbacklog);參數說明sockfd:調用socket返回的文件描述符backlog:accept應答之前,允許在進入隊列中等待的連接數目,出錯時返回-1(此數目=未完成連接客戶端數+已完成連接的客戶端數),如果兩個隊列都是滿的,tcp就忽略客戶端的同步SYN信號(但不發送RST信號,否則會導致客戶端connect出錯。忽略,客戶端超時會重發)返回值成功時,返回0失敗時,返回-1說明服務器使用listen后,套接字從CLOSED狀態變為LISTEN狀態,可以接受連接在使用listen()之前,需要調用bind()綁定到需要的端口三次握手是內核負責完成的(由客戶端發起)SchoolofComputerSicenceTCP的accept函數建立套接字連接,從建立的連接隊列中取一個處理,默認是阻塞型的(如果沒有已完成三次握手的隊列,則等待)函數原型intaccept(intsockfd,structvoid*addr,socklen_t*addrlen);參數說明sockfd:正在監聽端口的套接字文件描述符(調用socket()函數生成的)addr:指向本地數據結構sockaddr_in的指針,當連接成功時,會填入連入的遠程主機(客戶端)地址信息addrlen:設置為sizeof(structsockaddr_in)的變量的地址返回值:成功:返回已連接的socket描述字失敗:返回-1SchoolofComputerSicence服務器的最大連接數是否是由listen(intsockfd,intbacklog)中的backlog決定?比如backlog為5,是否表示最多5個客戶端連接到服務器?SchoolofComputerSicenceaccept的兩個socket文件描述符一個服務器通常通常僅僅只創建一個監聽socket描述字,它在該服務器的生命周期內一直存在。內核為每個由服務器進程接受的客戶連接創建了一個已連接socket描述字,當服務器完成了對某個客戶的服務,相應的已連接socket描述字就被關閉。intmain(){intsockfd1,sockfd2;structsockaddr_inraddr;/*客戶端地址信息*/

sockfd1=socket(AF_INET,SOCK_STREAM,0); ……s=sizeof(structsockaddr_in);

while(1){

sockfd2=accept(sockfd,(structsockaddr*)&raddr,&s); ……recv(sockfd2….)使用accept返回的socket//處理完成后關閉sockfd2}

SchoolofComputerSicencerecv函數功能通過socket接收數據函數原型ssize_trecv(intsockfd,void*buf,size_tlen,intflags);參數說明sockfd:要讀的連接SOCKET描述符(accept函數的返回值)buf:要讀的信息的緩沖區len:緩沖的最大長度flags:一般設置為0返回值成功時,返回實際接收到的數據的字節數失敗時,返回-1。如果正常關閉了連接,返回為0和recvfrom類似,由于是TCP,已經建立了連接信息,所以不必再寫客戶端的IP地址和端口號SchoolofComputerSicence簡單的TCP服務器tcps.c#main(){intlistenfd,connfd;structsockaddr_inservaddr,clientaddr;charbuff[4096];intn;intsinsize;

.......close(connfd);close(listenfd);}listenfd=socket(AF_INET,SOCK_STREAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=htonl(INADDR_ANY);servaddr.sin_port=8888;bind(listenfd,(structsockaddr*)&servaddr,sizeof(servaddr));listen(listenfd,10);sinsize=sizeof(clientaddr);connfd=accept(listenfd,(structsockaddr*)&clientaddr,&sinsize);n=recv(connfd,buff,4096,0);buff[n]='\0';printf("recvmsgfromclient:%s\n",buff);SchoolofComputerSicenceTCP客戶端普通流程1.建立socket,同服務器2.connect和服務器連接3.send發送消息(或接受消息)4.關閉SchoolofComputerSicenceTCP客戶端connect函數功能建立套接字連接#include<sys/socket.h>函數原型intconnect(intsockfd,conststructsockaddr*serv_addr,socklen_taddrlen);參數說明sockfd:調用socket返回的文件描述符serv_addr:遠程主機IP地址和端口addrlen:設置為sizeof(structsockaddr)返回值成功時,返回0。因為是阻塞模式,可能超時,具體時間由內核設置決定。失敗時,返回負數,具體的錯誤代碼存放在errorno中SchoolofComputerSicenceTCP客戶端send函數通過socket發送數據函數原型ssize_tsend(intsockfd,constvoid*buf,size_tlen,intflags);參數說明sockfd:發送數據的套接字描述符msg:指向發送數據的指針len:數據長度flags:一般設置為0返回值成功時,返回實際發送的數據的字節數失敗時,返回-1。返回SOCKET_ERROR表示網絡斷開了。SchoolofComputerSicencesend函數send先比較要發送數據的長度nbytes和套接字sockfd的發送緩沖區的長度buf,如果nbytes>buf該函數返回SOCKET_ERROR系統提供的socket緩沖區大小為8K,可以設置為大一些,尤其在傳輸實時視頻時注意:并不是send把套接字的發送緩沖區中的數據傳到連接的另一端的,而是協議傳送的,send僅僅是把buf中的數據copy到套接字sockfd的發送緩沖區的剩余空間里send函數把buff中的數據成功copy到sockfd的改善緩沖區的剩余空間后它就返回了,但是此時這些數據并不一定馬上被傳到連接的另一端。如果協議在后續的傳送過程中出現網絡錯誤的話,那么下一個socket函數就會返回SOCKET_ERROR。每一個除send的socket函數在執行的最開始總要先等待套接字的發送緩沖區中的數據被協議傳遞完畢才能繼續,如果在等待時出現網絡錯誤那么該socket函數就返回SOCKET_ERRORSchoolofComputerSicence簡單的tcp客戶端intmain(intargc,char**argv){intsockfd,n;structsockaddr_inservaddr;sockfd=socket(AF_INET,SOCK_STREAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=8888;servaddr.sin_addr.s_addr=inet_addr("");connect(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));send(sockfd,"hello",5,0);close(sockfd);exit(0);}SchoolofComputerSicence練習完善TCP服務器端和客戶端程序服務器端和客戶端都改為循環模式注意循環位置思考UDP和TCP的效率,用途,區別和聯系

socket(...);

bind(...);

listen(...);

while(1){

accept(...);

{

recv(...);

process(...);

}

close(...);

}SchoolofComputerSicence綜合實例:UDP實現收發消息程序功能:客戶端可以向服務器發送命令如發送:TIME時,服務器返回服務器的時間給客戶端,客戶端顯示如發送:DATA時,服務器返回字符串“HELLO”給客戶端顯示如發送:END時,服務器結束服務器循環接收命令客戶端循環發送命令使用UDP實現SchoolofComputerSicence程序框架-服務器初始化socket,地址,端口等綁定套接字while(字符串buff!=“END”){緩沖區buff清零recvfrom接收數據如果收到數據>0如果數據是TIME給字符串賦值為當前時間:sprintf(buff,"%s",....)發送給客戶端sendto如果數據是DATA給字符串賦值為HELLO發送給客戶端關閉套接字,結束SchoolofComputerSicence程序框架-客戶端初始化socket,地址,端口等while(字符串buff!=“END”){提示輸入字符串,并讀入發送給服務器sendto清空接受緩沖區(字符串)接收數據recvfrom如果接收的數據>0打印收到的數據關閉套接字,退出SchoolofComputerSicence改為TCP協議實現-服務器端如果用TCP實現收發,過程和UDP類似:初始化socket,地址,端口等,綁定套接字監聽本地端口listenwhile(字符串buff!=“END”){接收客戶端的連接accept(....),得到連接套接字socketConrecv接收數據如果收到數據>0如果數據是TIME給字符串賦值為當前時間:sprintf(buff,"%s",....)發送給客戶端sendto關閉連接套接字socketCon

關閉套接字,結束注意:accept在循環體內每來一個客戶端,產生一個新的socket處理完客戶連接后,關閉socket再處理下一個客戶連接SchoolofComputerSicence改為TCP協議實現-客戶端初始化socket,地址,端口等while(字符串buff!=“END”){提示輸入字符串,并讀入發送給服務器send清空接受緩沖區(字符串)接收數據recv如果接收的數據>0打印收到的數據關閉套接字,退出連接服務器connect(....)??根據服務器的處理情況服務器如果關閉,則重連SchoolofComputerSicence擴展練習:編寫“聊天”程序:即可以接收,也可以發送如:使用UDP,則程序是對等的,不分客戶端服務器端編寫一個ftp程序能夠傳輸文件和傳遞消息一樣,從文件中讀數據,然后發送,直到文件結束用TCP還是UDP?SchoolofComputerSicence一個簡單的Web服務器Web服務器原理客戶端是“瀏覽器”,當在瀏覽器輸入網址如":8080/index.html",瀏覽器會向指定IP的服務器的8080端口發送請求,具體請求類似于GET/index.htmlHTTP/1.1Host::8848User-Agent:Mozilla/5.0(X11;U;Linuxi686;zh-CN;rv:)Gecko/20060313Fedora/-9Firefox/pango-textAccept:text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5Accept-Language:zh-cn,zh;q=0.5Accept-Encoding:gzip,deflateAccept-Charset:gb2312,utf-8;q=0.7,*;q=0.7Keep-Alive:300Connection:keep-aliveSchoolofComputerSicenceWeb服務器原理服務器收到請求,分析請求,根據客戶端的要求響應,即向瀏覽器發送一些信息,包括:狀態頭,響應頭,實體等,如HTTP/1.1200OKCache-Control:privateContent-Type:text/html;charset=UTF-8Content-Encoding:gzipServer:GWS/2.1Content-Length:1851Date:Sat,14Oct200611:33:39GMT<html><head>.....瀏覽器收到響應信息,根據信息進行解析,將html顯示出來SchoolofComputerSicenceweb服務器模擬的簡化1、忽略瀏覽器的具體請求當瀏覽器請求時,給瀏覽器返回一個指定的html文件,如/var/www/index.html2、響應頭簡化可以只要狀態和類型,如sprintf(buf,"HTTP/1.0200OK\r\n");sprintf(buf,"%sContent-type:%s\r\n\r\n",buf,"text/html");3、單進程單線程完成SchoolofComputerSicenceweb服務器框架總體思路基于TCP協議的服務器1.初始化服務器:綁定端口,監聽2.無限等待,并響應while(1)接受連接接收數據讀index.html文件把響應頭+index.html文件發送給客戶端關閉連接套接字SchoolofComputerSicence服務器模型總體可分為C/S和P2PC/S:客戶端軟件向服務器發出請求,服務器然后對客戶端請求做出響應,在這種情況下,如果客戶端越多,此時服務器的壓力就越大P2P技術實現的每臺計算機既是客戶端,也是服務器,他們的功能都是對等的(BT、電驢、迅雷、QQ、MSN和PPlive等都是基于P2P方式實現的軟件)用戶之間傳輸多,網絡負擔將加重主機之間很難發現(配備發現服務器或索引服務器)SchoolofComputerSicence服務器基本框架I/O處理單元等待接受客戶連接,可以是一個專門的接入服務器,實現負載均衡邏輯處理單元進程或線程,分析處理數據,然后發給IO或客戶端網絡存儲單元(可選)如果需要,可以是獨立的數據庫,緩存或文件服務器請求隊列(*)各個邏輯單元的抽象,如IO處理,通知某邏輯處理單元;多個邏輯處理單元訪問存儲,需要同步SchoolofComputerSicence簡單的TCP并發服務器模型--多進程進程緩沖池:預先開一些進程來處理可能到來的連接//main函數

s=socket(...);

bind(s,...);

listen(s,...);

//處理客戶端的連接

for(i=0;i<預指定進程數;i++){

pid[i]=fork(); if(pid[i]==0) 處理連接:handle(s)}close(s)//處理連接函數voidhandle(ints){

while(1){news=accept(s,.....);.....接收recv(news,....)處理....close(news);}客戶端1accept()recv()處理數據客戶端2accept()recv()處理數據服務器子進程服務器子進程accept()recv()處理數據服務器子進程SchoolofComputerSicence另一種并發模型統一accept當客戶端連接請求到來時,臨時fork子進程處理將連接請求和業務處理分離//main函數

s=socket(...);

bind(s,...);

listen(s,...);

//處理客戶端的連接

while(1){

s_c=accept(s,.....); if(fork()==0) 處理連接:handle(s_c)elseclose(s_c);}close(s)//處理函數voidhandle(ints_c){.....接收recv(s_c,....)處理....close(s);}這種模型很容易改為“多線程”模型創建線程pthread_create(..handle...)

線程函數SchoolofComputerSicence多線程并發服務器-線程池模型和多進程一樣,但因為線程是共享資源(socket文件句柄),為防止多個線程競爭,必須互斥使用互斥區只需要保護accept//main函數

s=socket(...);

bind(s,...);

listen(s,...);

//處理客戶端的連接

for(i=0;i<預指定線程數;i++)

創建線程//線程函數void*handle(void*arg){

while(1){pthread_mutex_lock(&MU);s_c=accept(s,.....);

pthread_mutex_unlock(&MU);.....接收recv(s_c,....)處理....close(s_c);}SchoolofComputerSicenceTCP并發服務器--多線程示例線程比進程更節省資源功能描述客戶端多線程:向服務器發送從標準輸入得到的字符在另一個線程中將從服務器端返回的字符顯示到標準輸出服務器端多線程將客戶端發來的數據原樣返回給客戶端,每一個客戶在服務器上對應一個線程SchoolofComputerSicenceTCP多線程-客戶端static intsockfd;//線程:負責鍵盤輸入和發送void*cRecv(void*arg){intrs=0;charstr[80];while(1){scanf("%s",str);send(sockfd,str,80,0);}}intmain(intargc,char**argv){intn,rs;charstr[80],buf[4096];pthread_ttid;sockfd=socket(AF_INET,SOCK_STREAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(8888);servaddr.sin_addr.s_addr=inet_addr("");connect(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));pthread_create(&tid,NULL,cRecv,NULL);while(1){rs=recv(sockfd,buff,4096,0);if(rs>0){buff[rs]='\0';printf("recvmsgfromserver:%s\n",buff);}}}tcpcMulthread.cSchoolofComputerSicenceTCP多線程-服務器端線程:循環接收來自客戶端的信息并原樣發回去void*handle(void*arg){intsockClient=*((int*)arg);intn=0;charbuff[4096];while(1){n=recv(sockClient,buff,4096,0);if(n>0){buff[n]='\0';printf("recvmsgfromclient:%s\n",buff);send(sockClient,buff,4096,0);}}}tcpsMulthread.cSchoolofComputerSicenceTCP多線程-服務器端統一的accept函數,來了新的客戶端則開啟新線程intmain(){intn,sinsize,listenfd,connfd;structsockaddr_inservaddr,clientaddr;charbuff[4096];pthread_tpid;listenfd=socket(AF_INET,SOCK_STREAM,0);memset(&servaddr,0,sizeof(servaddr));servaddr.......bind(listenfd,(structsockaddr*)&servaddr,sizeof(servaddr));listen(listenfd,50);while(1){connfd=accept(listenfd,(structsockaddr*)&clientaddr,&sinsize);if(connfd>0){pthread_create(&pid,NULL,handle,(void*)&connfd);}}}SchoolofComputerSicence綜合練習寫一個簡單的網絡猜數游戲客戶端:運行程序,輸入服務器IP地址,連接服務器,接收并顯示服務器的信息:服務器會提示讓用戶輸入一個數,并根據用戶輸入數的大小提示猜的數字大了還是小了,直到猜對為止服務器:當客戶端連接時,產生一個隨機數,并根據用戶的輸入發送適當的信息幾點問題:UDP和TCP你選哪種協議來完成?隨機數的問題:多個用戶連接,是用同一個隨機數,還是不同的?若不同的,當新的用戶連接時,不要把前面產生的隨機數給覆蓋了為增加游戲的趣味性,服務器應保存客戶的哪些信息?如何保存?SchoolofComputerSicence小結前面TCP/UDP中使用send/recv系列函數(I/O函數)來發、收信息,因為socket是文件,因此,可以改為使用write/read函數像文件一樣進行操作,如read(fd,buff,1024)connect(),accept(),send(),recv()等阻塞情況讀/recv:緩沖區沒有數據,線程就一直睡眠直到數據來(數據到來由內核通知:數據先到內核,然后用讀copy到應用層來)寫/send:socket緩沖區沒足夠的空間accept:沒有連接請求connect:服務器沒應答之前(至少等待到服務器的一次往返時間)阻塞模式套接字簡單,易于實現,適用于并發量小(客戶端數目少),連續傳輸大數據量的情況下,如FTP;缺點但是當同時處理大量套接字時,采用多線程,系統開銷大SchoolofComputerSicence阻塞模式的工作方式請求服務器客戶1新線程線程:和客戶1聯系recv...send..請求客戶2因為阻塞,線程不能馬上完成;線程在處理完客戶端請求后結束新線程......當很多個線程時,系統負擔很大(Linux中1個線程棧默認是8M)SchoolofComputerSicence非阻塞模式的工作方式請求服務器客戶1處理請求recv...send..請求客戶2因為非阻塞,沒收到數據不等待,繼續往下執行......客戶n輪詢polling:如果把每個請求處理看成一個線程的話,這個線程很快就結束了SchoolofComputerSicence多路復用模式如何實現非阻塞IO模式sockfd=socket(AF_INET,SOCK_STREAM,0);fcntl(sockfd,

F_SETFL,

O_NONBLOCK);

將socket設置為非阻塞模式真正的輪詢是不實際的(why?)輪詢:當有事件發生時通知內核依次檢測所有連接的socket(包括讀,寫),如果某個socket可以操作了(可以讀了,或可以寫了),通知用戶linux提供select函數int

select(int

nfds,fd_set

*readfds,fd_set

*writefds,

fd_set

*except

fds,struct

timeval

*timeout)

函數參數:要讀的socket集合,要寫的socket集合,....,超時時間調用select函數時,進程會一直阻塞直到以下的一種情況發生.

1

溫馨提示

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

評論

0/150

提交評論