




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第五講
高級(jí)套接字編程任立勇電子科技大學(xué)計(jì)算機(jī)學(xué)院2/6/20231目錄高級(jí)名字與地址函數(shù)高級(jí)i/o函數(shù)高級(jí)udp套接字編程帶外數(shù)據(jù)2/6/20232getaddrinfo函數(shù)gethostbyname和gethostbyaddr函數(shù),是依賴于協(xié)議的,而且也是不可重入的。同時(shí)以前介紹的函數(shù)也不能調(diào)用一個(gè)函數(shù)解決將主機(jī)名和服務(wù)名轉(zhuǎn)變成套接字地址結(jié)構(gòu)的問(wèn)題。指定主機(jī)名和服務(wù)名將足以與一個(gè)獨(dú)立于協(xié)議細(xì)節(jié)的服務(wù)建立連接。#include<sys/types.h>#include<sys/socket.h>#include<netdb.h>intgetaddrinfo(constchar*hostname,constchar*service,conststructaddrinfo*hints,structaddrinfo**result);返回:成功返回0;出錯(cuò)返回非零;2/6/20233addrinfo結(jié)構(gòu)structaddrinfo{ int ai_flags; /*AI_PASSIVE,AI_CANONNAME*/ int ai_family; /*AF_xxx*/ int ai_socktype; /*SOCK_xxx*/ int ai_protocol; /*0orIPPROTO_xxxforIPv4andIPv6*/ size_t ai_addrlen; /*lengthofai_addr*/ char * ai_canonname;/*ptrtocanonicalnameforhost*/ structsockaddr *ai_addr; /*ptrtosocketaddr.struct*/ structaddrinfo *ai_next; /*ptrtonextstructrurelinkedlist*/};2/6/20234getaddrinfo函數(shù)(cont.)hints是一個(gè)空指針或指向一個(gè)addrinfo結(jié)構(gòu)的指針,由調(diào)用者填寫(xiě)關(guān)于它所想返回的信息類型的線索。調(diào)用者可以設(shè)置的hints結(jié)構(gòu)的成員有:ai_flags(AI_PASSIVE,AI_CANONNAME);ai_family;ai_socktype;ai_protocol。如果函數(shù)返回成功,ressult參數(shù)指向的變量將被填入一個(gè)指針,它指向一個(gè)由ai_next串起來(lái)的結(jié)構(gòu)鏈表,返回這個(gè)復(fù)合結(jié)構(gòu)有兩種方式:如果與該hostname對(duì)應(yīng)的有多個(gè)地址,將按請(qǐng)求的地址族(如果指定了ai_family)線索為每個(gè)地址返回一個(gè)結(jié)構(gòu);如果該服務(wù)在多種套接字類型上提供,將根據(jù)ai_socktype線索為每個(gè)套接字類型返回一個(gè)結(jié)構(gòu)。2/6/20235getaddrinfo函數(shù)舉例在沒(méi)有提供任何線索的條件下,要求domain服務(wù)查找一個(gè)有兩個(gè)IP地址的主機(jī),其結(jié)果如右圖。ai_flagsai_familyai_socktypeai_protocolai_addrlenai_canonnameai_addrai_nextai_flagsai_familyai_socktypeai_protocolai_addrlenai_canonnameai_addrai_nextresultsAF_INETSOCK_STREAM016\0Sockaddr_in{}addrinfo{}addrinfo{}見(jiàn)書(shū)234頁(yè)2/6/20236getaddrinfo的參數(shù)問(wèn)題getaddrinfo的參數(shù)輸入有很多種組合,許多是無(wú)效的,我們一般注意以下幾種情況:指定hostname和service,這在tcp和udp客戶程序中很常見(jiàn)。1)tcp客戶程序遍歷所有返回的IP地址,逐一調(diào)用socket和connect,直到連接成功或所有地址被測(cè)試過(guò)為止。2)Udp客戶程序中,由getaddrinfo填寫(xiě)的套接字地址結(jié)構(gòu)被用來(lái)調(diào)用sendto。如果客戶程序知道它只處理一種類型的套接字,就應(yīng)把hints結(jié)構(gòu)中的ai_socket設(shè)為SOCK_STREAM或SOCK_DGRAM。一個(gè)典型的服務(wù)器程序只用service以及hints結(jié)構(gòu)中的AI_PASSIVE標(biāo)志,而不需要指明hostname,返回的套接字地址結(jié)構(gòu)中包含一個(gè)INADDR_ANY或IN6ADDR_ANY的IP地址,隨后可建立監(jiān)聽(tīng)套接字。對(duì)于一個(gè)服務(wù)器處理多種服務(wù),我們可以遍歷由getaddrinfo返回的地址結(jié)構(gòu),為每個(gè)地址結(jié)構(gòu)創(chuàng)建一個(gè)套接字,然后用select處理多個(gè)套接字。2/6/20237getaddrinfo的缺點(diǎn)盡管getaddrinfo比gethostbyname與getserbyname函數(shù)的功能更多(單個(gè)函數(shù)不僅獨(dú)立于協(xié)議,而且既處理了主機(jī)名又處理了服務(wù),而且所有返回的所有信息是動(dòng)態(tài)分配的),但getaddrinfo函數(shù)的hints參數(shù)仍然給調(diào)用者帶來(lái)了不少的麻煩(如需要分配一個(gè)hints,初始化為0,填上必須的字段,調(diào)用完成后,還需要遍歷鏈表逐一嘗試)。Thegetaddrinfofunctioncombinesthefunctionalityprovidedbythegetipnodebyname,getipnodebyaddr,getservbyname,andgetservbyportfunctionsintoasingleinterface.Thethread-safegetaddrinfofunctioncreatesoneormoresocketaddressstructuresthatcanbeusedbythebindandconnectsystemcallstocreateaclientoraserversocket.2/6/20238freeaddrinfo函數(shù)由getaddrinfo返回的存儲(chǔ)空間,包括addrinfo結(jié)構(gòu)和ai_canonname字符串,都是用malloc動(dòng)態(tài)分配的。這些空間可調(diào)用freeaddrinfo釋放。#include<netdb.h>voidfreeaddrinfo(structaddrinfo*ai);ai應(yīng)指向getaddrinfo返回的第一個(gè)addrinfo結(jié)構(gòu)。在該鏈表中所有結(jié)構(gòu),以及這些結(jié)構(gòu)所指向的動(dòng)態(tài)存儲(chǔ)空間都將被釋放。2/6/20239getaddrinfo函數(shù)例子2/6/202310getnameinfo函數(shù)這個(gè)函數(shù)與getaddrinfo互補(bǔ):它以一個(gè)套接字地址為參數(shù),返回一個(gè)描述主機(jī)的字符串和描述服務(wù)的字符串。它也是獨(dú)立于協(xié)議和可重入的。該函數(shù)結(jié)合了gethostbyaddr與getservbyport兩個(gè)函數(shù)的功能。#include<netdb.h>intgetnameinfo(conststructsocckaddr*sockaddr,socklen_taddrlen,char*host,size_thostlen,char*serv,size_tservlen,intflags);返回:成功返回0;出錯(cuò)返回-1。inet_ntop和getnameinfo的差別在于,前者不查DNS直接返回可輸出的IP地址,而后者通常試圖給主機(jī)和服務(wù)查到一個(gè)名字。2/6/202311getnameinfo的flags參數(shù)getnameinfo的參數(shù)flags能改變它的操作:NI_DGRAM:數(shù)據(jù)報(bào)服務(wù);NI_NAMEREQD:不能從地址反向解析到名字時(shí)返回錯(cuò)誤;NI_NOFQDN:只返回FQDN的主機(jī)部分;NI_NUMERICHOST:返回主機(jī)的數(shù)值格式串;NI_NUMERISERV:返回服務(wù)的數(shù)值格式串2/6/202312getnameinfo函數(shù)的例子structsockaddr_insa;charname[NI_MAXHOST],serv[NI_MAXSERV];sa.sin_family=AF_INET;sa.sin_port=80;inet_aton("73",&sa.sin_addr);getnameinfo((structsockaddr*)&sa,sizeof(structsockaddr),name,NI_MAXHOST,serv,NI_MAXSERV,0);printf("Hostnameis%s.Servernameis%s.\n",name,serv);2/6/202313getnameinfo函數(shù)的例子2/6/202314getipnodebyname&getipnodebyaddrstructhostent*getipnodebyname(constchar*name,intaf,intflags,int*error_num);structhostent*getipnodebyaddr(constvoid*addr,size_tlen,intaf,int*error_num);這兩個(gè)函數(shù)分別取代了gethostbyname和gethostbyaddr(legacy),它們只能處理IPv4地址,而且為不可重入函數(shù)。這兩個(gè)函數(shù)可以處理多種網(wǎng)絡(luò)協(xié)議族,同時(shí)其返回值的空間動(dòng)態(tài)分配,因此使用完后需調(diào)用freehostent釋放。2/6/202315高級(jí)I/O函數(shù):套接字超時(shí)有三種方法給套接字上的I/O操作設(shè)置超時(shí):使用select阻塞在等待I/O上,select內(nèi)部有一個(gè)時(shí)間限制,以此代替在read或write調(diào)用上阻塞。使用新的SO_RCVTIMEO和SO_SNDTIME套接字選項(xiàng)。但并不是所有的實(shí)現(xiàn)都支持這兩個(gè)選項(xiàng)。調(diào)用alarm,在到達(dá)指定時(shí)間時(shí)產(chǎn)生SIGALRM信號(hào)。這涉及到信號(hào)處理,而且可能與進(jìn)程中其他已有的alarm調(diào)用沖突;2/6/202316用SIGALRM給connect設(shè)置超時(shí)典型情況下,connect函數(shù)的超時(shí)時(shí)間為75秒,顯得過(guò)長(zhǎng),我們可以利用下面的方法縮小連接建立的超時(shí)時(shí)間。staticvoid connect_alarm(int);intconnect_timeo(intsockfd,constSA*saptr,socklen_tsalen,intnsec){ Sigfunc *sigfunc; int n; sigfunc=Signal(SIGALRM,connect_alarm); if(alarm(nsec)!=0) err_msg("connect_timeo:alarmwasalreadyset");以前設(shè)置的定時(shí)器還未到。2/6/202317用SIGALRM給connect設(shè)置超時(shí) if((n=connect(sockfd,(structsockaddr*)saptr,salen))<0){ close(sockfd); if(errno==EINTR) errno=ETIMEDOUT; } alarm(0); /*turnoffthealarm*/ Signal(SIGALRM,sigfunc); /*restoreprevioussignalhandler*/ return(n);}staticvoidconnect_alarm(intsigno){ return; /*justinterrupttheconnect()*/}關(guān)閉套接字的目的是防止內(nèi)核繼續(xù)進(jìn)行三次握手。2/6/202318用SIGALRM給connect設(shè)置超時(shí)使用這種方法有幾點(diǎn)需要注意:這種技術(shù)可以減少connect的超時(shí)時(shí)間,但不能延長(zhǎng)內(nèi)核中已有的超時(shí)時(shí)間。如果在調(diào)用alarm函數(shù)安裝時(shí)鐘時(shí),以前的時(shí)鐘依然存在,則本方法或失效,或影響以前的時(shí)鐘。(即:和進(jìn)程中其他已有的alarm調(diào)用沖突)另,由于大多數(shù)的sleep函數(shù)使用alarm函數(shù)實(shí)現(xiàn),因此,如果程序中有調(diào)用sleep函數(shù),本方法可能導(dǎo)致它們相互作用而出現(xiàn)不可預(yù)料的后果。2/6/202319用SIGALRM為recvfrom設(shè)置超時(shí)staticvoid sig_alrm(int);voiddg_cli(FILE*fp,intsockfd,constSA*pservaddr,socklen_tservlen){ int n; char sendline[MAXLINE],recvline[MAXLINE+1]; Signal(SIGALRM,sig_alrm); while(fgets(sendline,MAXLINE,fp)!=NULL){ sendto(sockfd,sendline,strlen(sendline),0,pservaddr,servlen); alarm(5);/*設(shè)置超時(shí)定時(shí)器*/ if((n=recvfrom(sockfd,recvline,MAXLINE,0,NULL,NULL))<0){ if(errno==EINTR) fprintf(stderr,"sockettimeout\n");2/6/202320用SIGALRM為recvfrom設(shè)置超時(shí)(續(xù)) else err_sys("recvfromerror"); }else{ alarm(0);/*成功接收后,需要撤銷(xiāo)定時(shí)器*/ recvline[n]=0; /*nullterminate*/ Fputs(recvline,stdout); } }}staticvoidsig_alrm(intsigno){ return; /*justinterrupttherecvfrom()*/}2/6/202321用select為recvfrom設(shè)置超時(shí)intreadable_timeo(intfd,intsec){ fd_set rset; structtimeval tv; FD_ZERO(&rset); FD_SET(fd,&rset); tv.tv_sec=sec; tv.tv_usec=0; return(select(fd+1,&rset,NULL,NULL,&tv));/*>0ifdescriptorisreadable*/}這個(gè)函數(shù)的返回值就是select的返回值;這個(gè)函數(shù)不執(zhí)行讀操作,它只是等待描述字變成可讀。因此這個(gè)函數(shù)可以用在任何類型的套接字上:tcp或udp。可以建立類似的名為writeable_timeo的函數(shù),等待一個(gè)描述字變成可寫(xiě)。2/6/202322用SO_RCVTIMEO選項(xiàng)為recvfrom設(shè)置超時(shí)一旦為某個(gè)描述字設(shè)置了這個(gè)選項(xiàng),并指定了超時(shí)值,那么這個(gè)超時(shí)對(duì)該描述字上的所有讀操作都起作用;與此類似SO_SNDTIMEO只對(duì)寫(xiě)操作起作用。它們都不能對(duì)connect設(shè)置超時(shí)。intsetread_timeo(intfd,intsec){ structtimevaltv; tv.tv_sec=sec; tv.tv_usec=0; return(setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)));}該函數(shù)的返回值是setsockopt函數(shù)的返回值;該函數(shù)可以用于tcp或udp套接字。2/6/202323readv和writev函數(shù)這兩個(gè)函數(shù)與read和write相似,但readv和writev可以讓我們?cè)谝粋€(gè)函數(shù)調(diào)用中讀或?qū)懚鄠€(gè)緩沖區(qū),這些操作被稱為分散讀和集中寫(xiě)。#include<sys/uio.h>ssize_treadv(intfiledes,conststructiovec*iov,intiovcnt);ssize_twritev(intfiledes,conststructiovec*iov,intiovcnt); 返回:讀到或?qū)懗龅淖止?jié)數(shù),出錯(cuò)時(shí)為-1。readv和writev函數(shù)可用于任何描述字,不僅限于套接字描述字,而且writev是一個(gè)原子操作。參數(shù)iov是一個(gè)指向iovec結(jié)構(gòu)的數(shù)組的指針:structioven{ void *iov_base; /*startingaddressofbuffer*/ size_t iov_len; /*sizeofbuffer*/}2/6/202324readv和writev函數(shù)(續(xù))len0len1……lenNvector[0].iov_basevector[0].iov_lenvector[1].iov_basevector[1].iov_lenvector[count-1].iov_basevector[count-1].iov_len緩存0len0緩存1len1緩存NlenN2/6/202325readv和writev函數(shù)(續(xù))注意:readv的第二個(gè)參數(shù)被說(shuō)明為const,這是因?yàn)樵谧x取過(guò)程中,并不修改iovec結(jié)構(gòu)的成員,而只修改iov_base所指向的存儲(chǔ)區(qū);對(duì)于分散的緩存的個(gè)數(shù)在不同的實(shí)現(xiàn)中有不同的限制。函數(shù)readv與writev的最大好處在于:可以通過(guò)一個(gè)系統(tǒng)調(diào)用實(shí)現(xiàn)讀、寫(xiě)多個(gè)緩存,因此,可以極大地減少CPU時(shí)間操作SPARC80386用戶系統(tǒng)時(shí)鐘用戶系統(tǒng)時(shí)鐘兩次write0.513.113.7緩存復(fù)制,然后一次write0.54.48.1一次writev0.34.68.22/6/202326recvmsg和sendmsg函數(shù)#include<sys/socket.h>/*<bits/socket.h>inlinux*/ssize_trecvmsg(intsockfd,structmsghdr*msg,intflags);ssize_tsendmsg(intsockfd,structmsghdr*msg,intflags); 返回:成功時(shí)為讀入或?qū)懗龅淖止?jié)數(shù),出錯(cuò)時(shí)為-1。這兩個(gè)函數(shù)是最通用的i/o函數(shù),實(shí)際上,recvmsg可以代替read,readv,recv和recvfrom。同理,各種輸出函數(shù)也可以用sendmsg取代。這兩個(gè)函數(shù)把大部分參數(shù)封裝在如下結(jié)構(gòu)structmsghdr{ void *msg_name; /*protocoladdress*/ socklen_t msg_namelen; /*sizeofprotocoladdress*/ structiovec *msg_iov; /*scatter/gatherarray*/ size_t msg_iovlen; /*#elementsinmsg_iov*/ void *msg_control; /*ancillarydata;mustbealignedfora cmsghdrstructrue*/ socklen_len msg_controllen; /*lengthofancillarydata*/ int msg_flags; /*flagsreturnedbyrecvmsg*/}2/6/202327structmsghdr結(jié)構(gòu)說(shuō)明msg_name和msg_namelen成員對(duì)于未經(jīng)連接的套接字(如udp套接字),前者指向套接字地址。對(duì)recvmsg而言,它們存放發(fā)送方的地址,對(duì)sendmsg,它們是目的方地址。對(duì)于不需要指明協(xié)議地址(如tcp套接字),msg_name應(yīng)被置成空。msg_iov和msg_iovlen成員指明輸入或輸出的緩沖區(qū)數(shù)組。msg_control和msg_controllen成員指明可選的輔助數(shù)據(jù)的位置和大小。2/6/202328structmsghdr結(jié)構(gòu)說(shuō)明(續(xù))必須區(qū)別兩個(gè)標(biāo)志變量:函數(shù)中的傳值的flags參數(shù)和msghdr結(jié)構(gòu)中的msg_flags(它是以引用方式傳遞的):msg_flags只用于recvmsg。調(diào)用recvmsg時(shí),flags參數(shù)被拷貝到msg_flags成員,而且內(nèi)核用這個(gè)值進(jìn)行接收處理,它的值會(huì)根據(jù)recvmsg的結(jié)果而更新。sendmsg會(huì)忽略msg_flags成員,因?yàn)樗谶M(jìn)行輸出時(shí)使用flags參數(shù)。2/6/202329各種I/O函數(shù)輸入和輸出標(biāo)志
標(biāo)志MSG_DONTROUTE *MSG_DONTWAIT * *MSG_PEEK *MSG_WAITALL *MSG_EOR * *MSG_OOB * * *MSG_BCAST *MSG_MCAST *MSG_TRUNC *MSG_CTRUNC *在sendflagssendtoflagssendmsgflags中檢查在recvflagsrecvfromflagsrecvmsgflags中檢查在recvmsgmsg_flags中返回2/6/202330新增標(biāo)志MSG_BCAST:當(dāng)收到的數(shù)據(jù)報(bào)是一個(gè)鏈路層的廣播或其目的IP地址為廣播地址時(shí),將返回此標(biāo)志;MSG_MCAST:當(dāng)收到數(shù)據(jù)是鏈路層的多播時(shí),將設(shè)置概標(biāo)志MSG_TRUNC:這個(gè)標(biāo)志在數(shù)據(jù)報(bào)被截?cái)鄷r(shí)返回:內(nèi)核有比進(jìn)程所分配的空間所能容納的還要多的數(shù)據(jù)待返回。MSG_CTRUNC:這個(gè)標(biāo)志在輔助數(shù)據(jù)被截?cái)鄷r(shí)返回;2/6/202331msghdr的例子假定進(jìn)程是對(duì)一個(gè)udp套接字調(diào)用recvmsg。給協(xié)議地址分配了16個(gè)字節(jié),輔助數(shù)據(jù)分配了20個(gè)字節(jié),初始化了三個(gè)iovec結(jié)構(gòu)元素的數(shù)組:第一個(gè)指定一個(gè)100字節(jié)的緩沖區(qū),第二個(gè)是60字節(jié)的緩沖區(qū),第三個(gè)是80字節(jié)的緩沖區(qū)。假定有一個(gè)從的端口號(hào)2000到來(lái)的170字節(jié)的udp數(shù)據(jù)報(bào),目的地是我們的udp套接字,目的ip為5。2/6/202332對(duì)一個(gè)udp套接字調(diào)用recvmsg時(shí)的數(shù)據(jù)結(jié)構(gòu)msg_namemsg_namelenmsg_iovmsg_iovlenmsg_controlmsg_contrllenmsg_flagsmsghdr{}iov_baseiov_leniov_baseiov_leniov_baseiov_len1632001006080iovec{}2/6/202333recvmsg返回時(shí)對(duì)上圖的更新msg_namemsg_namelenmsg_iovmsg_iovlenmsg_controlmsg_contrllenmsg_flagsmsghdr{}iov_baseiov_leniov_baseiov_leniov_baseiov_len1631601006080sockaddr_in{}Ioven{}[]cmsghdr{}16,AF_INET,2000,cmsglencmsg_levelcmsg_type16IPPROTO_IPIP_RECVDSGADDR52/6/202334輔助數(shù)據(jù)可以在sendmsg和recvmsg時(shí)使用msghdr結(jié)構(gòu)中的msg_control和msg_controllen成員發(fā)送或接收輔助數(shù)據(jù)(也叫控制信息)。下面是各種輔助數(shù)據(jù)用法:協(xié)議 cms_level cmsg_type 說(shuō)明IPv4 IPPROTO_IP IP_RECVDSTADDR 接收udp數(shù)據(jù)報(bào)的目的地址 IP_RECVIF 接收udp數(shù)據(jù)報(bào)的接口索引IPv6 IPPROTO_IPv6 IPv6_DSTOPTS 指定/接收目標(biāo)選項(xiàng) IPv6_HOPLIMIT 指定/接收跳限 IPv6_RTHDR 指定/接收路由頭部 IPv6_PKTINFO 指定/接收分組信息Unix域 SOL_SOCKET SCM_RIGHT 發(fā)送/接收描述字 SCM_CREDS 發(fā)送/接收用戶憑證2/6/202335輔助數(shù)據(jù)由一個(gè)或多個(gè)輔助數(shù)據(jù)對(duì)象組成,每個(gè)對(duì)象由一個(gè)cmshdr開(kāi)頭:structcmsghdr{ socklen_t cmsg_len; int cmsg_level; int cmsg_type;/*follwedbyunsignedcharcmsg_data[]*/};輔助數(shù)據(jù)(續(xù))數(shù)據(jù)cmsglencmsg_levelcmsg_type填充字節(jié)填充字節(jié)數(shù)據(jù)cmsglencmsg_levelcmsg_type填充字節(jié)cmsghdrcmsghdr輔助數(shù)據(jù)對(duì)象輔助數(shù)據(jù)對(duì)象msg_controllencsg_lenCMSG_LEN()csg_lenCMSG_LEN()2/6/202336常用的輔助數(shù)據(jù)宏操作#include<sys/socket.h>#include<sys/param.h>structcmsghdr*CMSG_FIRSTHDR(structmsghdr*mhdrptr); 成功:返回指向第一個(gè)cmsghdr結(jié)構(gòu)的指針。structcmsghdr*CMSG_NXTHDR(structmsghdr*mhdrptr,structcmsghdr*cmsgptr) 返回指向下一個(gè)cmsghdr結(jié)構(gòu)的指針;unsignedchar*CMSG_DATA(structcmsghdr*cmsgptr); 返回與cmsghdr結(jié)構(gòu)關(guān)聯(lián)的數(shù)據(jù)的第一個(gè)字節(jié);unsignedintCMSG_LEN(unsignedintlength); 返回:給定數(shù)據(jù)量下存儲(chǔ)在cmsg_len中的值;unsignedintCMSG_SPACE(unsignedintlength); 返回:給定數(shù)據(jù)量下一個(gè)輔助數(shù)據(jù)對(duì)象的總大小;2/6/202337輔助數(shù)據(jù)的用法structmsghdr msg;structcmsghdr *cmsgptr;/*fillinmsgstructure*//*callrecvmsg*/for(cmsgptr=CMSG_FIRSTHDR(&msg);cmsgptr!=NULL;cmsgptr=CMSG_NEXT(&msg,cmsgptr)){ if(cmsgptr->cmsg_level==…&&cmsgptr->cmsg_type==…) { u_char *ptr; ptr=CMSG_DATA(cmsgptr); /*processdatapointedtobyptr*/ }}2/6/202338排隊(duì)的數(shù)據(jù)量在不讀出數(shù)據(jù)的情況下,如何知道一個(gè)套接字的接收隊(duì)列中有多少數(shù)據(jù)可讀呢?有三種方法:如果在沒(méi)有數(shù)據(jù)可讀時(shí)還有其他事情要做,可以使用非阻塞I/O。如果想檢查一下數(shù)據(jù)而使數(shù)據(jù)仍留在接收隊(duì)列中,可以使用MSG_PEEK標(biāo)志。如果想這樣做,但又不能肯定是否有數(shù)據(jù)可讀,可以把這個(gè)標(biāo)志和非阻塞接口相結(jié)合,或與MSG_DONTWAIT標(biāo)志結(jié)合使用;一些實(shí)現(xiàn)支持ioctl的FIONREAD命令。ioctl的第三個(gè)參數(shù)是一個(gè)指向整數(shù)的指針,在該整數(shù)中返回的值是套接字接收隊(duì)列中數(shù)據(jù)的字節(jié)數(shù)。對(duì)udp套接字而言,包括隊(duì)列中的所有數(shù)據(jù)報(bào),還要包括每個(gè)數(shù)據(jù)報(bào)的發(fā)送方的IP地址和端口號(hào)。2/6/202339高級(jí)UDP套接字編程UDP是無(wú)連接服務(wù),很多情況下需要確定udp數(shù)據(jù)報(bào)的目的以及是從哪個(gè)接口接收數(shù)據(jù)報(bào)的。因?yàn)橐粋€(gè)綁定udp端口和通配地址的套接在能在任何接口上接收單播、廣播和多播數(shù)據(jù)報(bào);多數(shù)udp服務(wù)器程序是迭代執(zhí)行的,但是有些應(yīng)用程序需要在客戶和服務(wù)器間交換多個(gè)數(shù)據(jù)報(bào),這是就需要服務(wù)器在某種形式上的并發(fā)。TFTP就是一個(gè)例子。2/6/202340改變的recvfrom函數(shù)要求寫(xiě)一個(gè)recvfrom_flags函數(shù),除實(shí)現(xiàn)recvfrom的功能外,還要求返回:返回的msg_flags值;收到的數(shù)據(jù)報(bào)的目的地址(通過(guò)設(shè)置IP_RECVDSTADDR選項(xiàng));接收數(shù)據(jù)報(bào)接口的索引(通過(guò)設(shè)置IP_RECVIF選項(xiàng))。為了返回最后兩項(xiàng),我們定義了如下結(jié)構(gòu):structin_pktinfo{ structin_addr ipi_addr; /*destinationIPv4address*/ int ipi_ifindex; /*receivedinterfaceindex*/}2/6/202341recvfrom_flags函數(shù)#include <sys/param.h> /*ALIGNmacroforCMSG_NXTHDR()macro*/#ifdef HAVE_SOCKADDR_DL_STRUCT#include <net/if_dl.h>#endifssize_trecvfrom_flags(intfd,void*ptr,size_tnbytes,int*flagsp, SA*sa,socklen_t*salenptr,structin_pktinfo*pktp){ structmsghdr msg; structiovec iov[1]; ssize_t n;#ifdef HAVE_MSGHDR_MSG_CONTROL structcmsghdr *cmptr;2/6/202342recvfrom_flags函數(shù)(續(xù)) union{ structcmsghdr cm; char control[CMSG_SPACE(sizeof(structin_addr))+ CMSG_SPACE(sizeof(structsockaddr_dl))]; }control_un; msg.msg_control=control_un.control; msg.msg_controllen=sizeof(control_un.control); msg.msg_flags=0;#else bzero(&msg,sizeof(msg)); /*makecertainmsg_accrightslen=0*/#endif msg.msg_name=sa; msg.msg_namelen=*salenptr; iov[0].iov_base=ptr; iov[0].iov_len=nbytes;2/6/202343recvfrom_flags函數(shù)(續(xù)) msg.msg_iov=iov; msg.msg_iovlen=1; if((n=recvmsg(fd,&msg,*flagsp))<0) return(n); *salenptr=msg.msg_namelen; /*passbackresults*/ if(pktp) bzero(pktp,sizeof(structin_pktinfo)); /*,i/f=0*//*endrecvfrom_flags1*//*includerecvfrom_flags2*/#ifndef HAVE_MSGHDR_MSG_CONTROL *flagsp=0; /*passbackresults*/ return(n);2/6/202344recvfrom_flags函數(shù)(續(xù))#else *flagsp=msg.msg_flags; /*passbackresults*/ if(msg.msg_controllen<sizeof(structcmsghdr)|| (msg.msg_flags&MSG_CTRUNC)||pktp==NULL) return(n); for(cmptr=CMSG_FIRSTHDR(&msg);cmptr!=NULL; cmptr=CMSG_NXTHDR(&msg,cmptr)){#ifdef IP_RECVDSTADDR if(cmptr->cmsg_level==IPPROTO_IP&& cmptr->cmsg_type==IP_RECVDSTADDR){ memcpy(&pktp->ipi_addr,CMSG_DATA(cmptr), sizeof(structin_addr)); continue; }#endif2/6/202345recvfrom_flags函數(shù)(續(xù))#ifdef IP_RECVIF if(cmptr->cmsg_level==IPPROTO_IP&& cmptr->cmsg_type==IP_RECVIF){ structsockaddr_dl *sdl; sdl=(structsockaddr_dl*)CMSG_DATA(cmptr); pktp->ipi_ifindex=sdl->sdl_index; continue; }#endif err_quit("unknownancillarydata,len=%d,level=%d,type=%d", cmptr->cmsg_len,cmptr->cmsg_level,cmptr->cmsg_type); } return(n);#endif /*HAVE_MSGHDR_MSG_CONTROL*/}/*endrecvfrom_flags2*/2/6/202346輸出目的IP地址和數(shù)據(jù)報(bào)截?cái)鄻?biāo)志#define MAXLINE 20 /*toseedatagramtruncation*/voiddg_echo(intsockfd,SA*pcliaddr,socklen_tclilen){ int flags; constint on=1; socklen_t len; ssize_t n; char mesg[MAXLINE],str[INET6_ADDRSTRLEN],ifname[IFNAMSIZ]; structin_addr in_zero; structin_pktinfo pktinfo;#ifdef IP_RECVDSTADDR if(setsockopt(sockfd,IPPROTO_IP,IP_RECVDSTADDR,&on,sizeof(on))<0) err_ret("setsockoptofIP_RECVDSTADDR");#endif2/6/202347輸出目的IP地址和數(shù)據(jù)報(bào)截?cái)鄻?biāo)志#ifdef IP_RECVIF if(setsockopt(sockfd,IPPROTO_IP,IP_RECVIF,&on,sizeof(on))<0) err_ret("setsockoptofIP_RECVIF");#endif bzero(&in_zero,sizeof(structin_addr)); /*all0IPv4address*/ for(;;){ len=clilen; flags=0; n=recvfrom_flags(sockfd,mesg,MAXLINE,&flags,pcliaddr,&len,&pktinfo); printf("%d-bytedatagramfrom%s",n,Sock_ntop(pcliaddr,len)); if(memcmp(&pktinfo.ipi_addr,&in_zero,sizeof(in_zero))!=0) printf(",to%s",Inet_ntop(AF_INET,&pktinfo.ipi_addr,str,sizeof(str))); if(pktinfo.ipi_ifindex>0) printf(",recvi/f=%s",If_indextoname(pktinfo.ipi_ifindex,ifname));2/6/202348輸出目的IP地址和數(shù)據(jù)報(bào)截?cái)鄻?biāo)志#ifdef MSG_TRUNC if(flags&MSG_TRUNC) printf("(datagramtruncated)");#endif#ifdef MSG_CTRUNC if(flags&MSG_CTRUNC) printf("(controlinfotruncated)");#endif#ifdef MSG_BCAST if(flags&MSG_BCAST) printf("(broadcast)");#endif#ifdef MSG_MCAST if(flags&MSG_MCAST) printf("(multicast)");#endif printf("\n"); Sendto(sockfd,mesg,n,0,pcliaddr,len); }}2/6/202349FreeBSD系統(tǒng)支持(Linux系統(tǒng)和Solaris都不支持)2/6/202350數(shù)據(jù)報(bào)截?cái)嘣赽sd/os環(huán)境下,當(dāng)一個(gè)到來(lái)的udp數(shù)據(jù)報(bào)長(zhǎng)度大于應(yīng)用程序緩沖區(qū),recvmsg設(shè)置MSG_TRUNC標(biāo)志:但對(duì)超過(guò)預(yù)期長(zhǎng)度的udp數(shù)據(jù)報(bào),不同實(shí)現(xiàn)有不同的處理方式:丟掉超出的字節(jié)并給應(yīng)用程序返回標(biāo)志;(BSD/OS)(Linux)丟掉超出的字節(jié)但不通知應(yīng)用程序;(solaris2.5中不支持msg_flags)保留超出的字節(jié)并在隨后這個(gè)套接字上的讀操作中返回這些數(shù)據(jù)。(早期的SVR4)發(fā)現(xiàn)上述問(wèn)題的方法:分配一個(gè)比應(yīng)用程序可能收到的最大數(shù)據(jù)報(bào)大1字節(jié)的緩沖區(qū),如果收到長(zhǎng)度等于該緩沖區(qū)長(zhǎng)度的數(shù)據(jù)報(bào),就認(rèn)為發(fā)生了錯(cuò)誤。2/6/202351何時(shí)使用UDP而不是TCP對(duì)廣播或多播應(yīng)用程序必須使用UDP。此時(shí),任何形式的期望的錯(cuò)誤控制必須加入到客戶和服務(wù)器程序中。udp可以用于簡(jiǎn)單的請(qǐng)求-應(yīng)答式應(yīng)用程序,但應(yīng)用程序內(nèi)部必須有檢查錯(cuò)誤的功能。這至少涉及確認(rèn)、超時(shí)和重傳。udp不應(yīng)該用于海量數(shù)據(jù)的傳輸(如文件傳輸)。2/6/202352并發(fā)udp服務(wù)器多數(shù)udp服務(wù)器程序是迭代執(zhí)行的,但當(dāng)處理客戶需要很長(zhǎng)時(shí)間時(shí),就要有一定形式的并發(fā)。在tcp應(yīng)用中,由于tcp套接字對(duì)每個(gè)連接都是唯一的,因此很容易實(shí)現(xiàn)并發(fā)。但在udp中,我們必須處理兩種不同類型的服務(wù)器:第一種是簡(jiǎn)單的udp服務(wù)器,它讀入一個(gè)客戶請(qǐng)求,發(fā)送應(yīng)答,接著與這個(gè)客戶就無(wú)關(guān)了。這種情形下,服務(wù)器可以fork一個(gè)子進(jìn)程去處理請(qǐng)求。子進(jìn)程可以從得來(lái)的內(nèi)存映象中獲得客戶地址,從而將處理結(jié)果返回給客戶。2/6/202353并發(fā)udp服務(wù)器(續(xù))第二種是與客戶交換多個(gè)數(shù)據(jù)報(bào)的udp服務(wù)器。問(wèn)題是客戶只知道服務(wù)器的眾所周知的端口。客戶發(fā)送請(qǐng)求的第一個(gè)數(shù)據(jù)報(bào)到達(dá)這個(gè)端口,服務(wù)器又怎能區(qū)分這是那個(gè)客戶的后續(xù)數(shù)據(jù)報(bào)還是新請(qǐng)求呢?這種問(wèn)題的典型解決方法是讓服務(wù)器給每個(gè)客戶創(chuàng)建一個(gè)新的套接字,bind一個(gè)臨時(shí)端口到那個(gè)套接字,并且對(duì)所有的回答都用這個(gè)套接口。這要求客戶看一下服務(wù)器的第一個(gè)應(yīng)答中的端口號(hào),并且向那個(gè)端口發(fā)送請(qǐng)求的后續(xù)數(shù)據(jù)報(bào)。2/6/202354并發(fā)udp服務(wù)器(續(xù))客戶服務(wù)器(父進(jìn)程)端口69服務(wù)器(子進(jìn)程)端口2134來(lái)自客戶的第一個(gè)數(shù)據(jù)報(bào)來(lái)自服務(wù)器的第一個(gè)應(yīng)答創(chuàng)建套接字,bind眾所周知端口(69),recvfrom,阻塞至客戶請(qǐng)求到達(dá)。fork,接著下一次recvfrom,…創(chuàng)建新的套接字,bind臨時(shí)端口(2134),處理客戶請(qǐng)求,在新套接字上與客戶交換其余的數(shù)據(jù)報(bào)獨(dú)立運(yùn)行的并發(fā)udp服務(wù)器中需要的處理fork客戶與服務(wù)器間所有剩余數(shù)據(jù)報(bào)2/6/202355帶外數(shù)據(jù)許多傳輸層有帶外數(shù)據(jù)的概念(有時(shí)稱為加速數(shù)據(jù))。帶外數(shù)據(jù)在排隊(duì)等待發(fā)送的普通數(shù)據(jù)之前發(fā)送,但帶外數(shù)據(jù)是映射到現(xiàn)有的連接中的,而不是另外建立一個(gè)新的連接。(不同的傳輸層有不同的帶外數(shù)據(jù)實(shí)現(xiàn)方式,本節(jié)只介紹tcp帶外數(shù)據(jù))1N套接字發(fā)送緩沖區(qū)要發(fā)送的第一個(gè)字節(jié)要發(fā)送的最后一個(gè)字節(jié)1套接字發(fā)送緩沖區(qū)要發(fā)送的第一個(gè)字節(jié)要發(fā)送的最后一個(gè)字節(jié)NOOBTCP緊急指針包含要發(fā)送數(shù)據(jù)的套接字發(fā)送緩沖區(qū)寫(xiě)入1字節(jié)的帶外數(shù)據(jù)后的套接字發(fā)送緩沖區(qū)2/6/202356發(fā)送方對(duì)帶外數(shù)據(jù)的處理一旦調(diào)用帶緊急數(shù)據(jù)標(biāo)志的發(fā)送函數(shù)(如send(fd,’a’,1,MSG_OOB)),由tcp發(fā)送的下一個(gè)分節(jié)將會(huì)在tcp頭部中設(shè)置URG標(biāo)志,并且頭部中的緊急偏移字段也將指向帶外字節(jié)后的字節(jié)。但是這個(gè)分節(jié)可以含有也可以不含有我們標(biāo)記為OOB的字節(jié),是否發(fā)送它取決于三個(gè)因素:a)套接字發(fā)送緩沖區(qū)中它前面的字節(jié)數(shù)、b)tcp發(fā)送給對(duì)方的分節(jié)長(zhǎng)度、c)對(duì)方的通告窗口。如果tcp因流控停止了(接收者的套接字接收緩沖區(qū)滿了),緊急通知將不帶任何數(shù)據(jù)地被送出。這就是為什么應(yīng)用程序使用tcp的緊急模式的另一個(gè)原因:即便數(shù)據(jù)流因tcp的流控停止了,緊急通知也總會(huì)被發(fā)送到對(duì)方的tcp。2/6/202357接收方對(duì)帶外數(shù)據(jù)的處理當(dāng)tcp收到了一個(gè)設(shè)置了URG表示的分節(jié)時(shí),緊急指針被檢查,看它是否指向新的帶外數(shù)據(jù)。發(fā)送者tcp有可能發(fā)送多個(gè)包含URG標(biāo)志,但緊急指針卻指向相同的數(shù)據(jù)字節(jié)的分節(jié)(通常在一小段時(shí)間內(nèi)?),這種情況相當(dāng)普遍。需要注意的是:這些分節(jié)中只有第一個(gè)分節(jié)會(huì)導(dǎo)致接收進(jìn)程被通知有新的帶外數(shù)據(jù)到達(dá);當(dāng)新緊急指針到達(dá)時(shí),接收進(jìn)程被通知。首先SIGURG信號(hào)發(fā)送給套接字的屬主,其次如果進(jìn)程阻塞在select調(diào)用中,等待這個(gè)套接字描述字有一個(gè)異常條件,那么select返回;2/6/202358接收者對(duì)帶外數(shù)據(jù)的處理(續(xù))當(dāng)由緊急指針指向的實(shí)際數(shù)據(jù)字節(jié)到達(dá)接收者tcp時(shí),這個(gè)數(shù)據(jù)字節(jié)可以被拉出帶外或繼續(xù)在線存放。正常情況下,帶外數(shù)據(jù)字節(jié)并不放入套接字接收緩沖區(qū)。相反,這個(gè)數(shù)據(jù)字節(jié)被放到這個(gè)連接的單獨(dú)的1字節(jié)帶外緩沖區(qū),進(jìn)程讀出這個(gè)數(shù)據(jù)的唯一方法是調(diào)用recv、recvfrom或者recvmsg,并指定MSG_OOB標(biāo)志。但如果設(shè)置了SO_OOBINLINE選項(xiàng),帶外數(shù)據(jù)留在接收緩沖區(qū),而不能通過(guò)指定MSG_OOB標(biāo)志讀取。2/6/202359處理帶外數(shù)據(jù)時(shí)可能出現(xiàn)的錯(cuò)誤如果進(jìn)程請(qǐng)求讀取帶外數(shù)據(jù)(例如指定MSG_OOB標(biāo)志),但是對(duì)方尚未發(fā)送,將會(huì)返回EINVAL;如果進(jìn)程已被通知對(duì)方發(fā)送了帶外數(shù)據(jù)(例如通過(guò)SIGURG或select),進(jìn)程試圖去讀它,但是那個(gè)字節(jié)還沒(méi)有到達(dá),將會(huì)返回EWOULDBLOCK;如果進(jìn)程試圖多次讀相同的帶外數(shù)據(jù),將會(huì)返回EINVAL;如果進(jìn)程已經(jīng)設(shè)置了SO_OOBINLINE套接口選項(xiàng),接著試圖通過(guò)指定MSG_OOB讀帶外數(shù)據(jù),將會(huì)返回EINVAL;2/6/202360使用帶外數(shù)據(jù)的例子(發(fā)送方)intmain(intargc,char**argv){ int sockfd; if(argc!=3) err_quit("usage:tcpsend01<host><port#>"); sockfd=Tcp_connect(argv[1],argv[2]); Write(sockfd,"123",3); printf("wrote3bytesofnormaldata\n"); sleep(1); Send(sockfd,"4",1,MSG_OOB); printf("wrote1byteofOOBdata\n"); sleep(1);2/6/202361使用帶外數(shù)據(jù)的例子(發(fā)送方) Write(sockfd,"56",2); printf("wrote2bytesofnormaldata\n"); sleep(1); Send(sockfd,"7",1,MSG_OOB); printf("wrote1byteofOOBdata\n"); sleep(1); Write(sockfd,"89",2); printf("wrote2bytesofnormaldata\n"); sleep(1); exit(0);}2/6/202362使用帶外數(shù)據(jù)的例子(接收方)int listenfd,connfd;void sig_urg(int);intmain(intargc,char**argv){ int n; char buff[100]; if(argc==2) listenfd=Tcp_listen(NULL,argv[1],NULL); elseif(argc==3) listenfd=Tcp_listen(argv[1],argv[2],NULL); else err_quit("usage:tcprecv01[<host>]<port#>"); connfd=Accept(listenfd,NULL,NULL); Signal(SIGURG,sig_urg);
Fcntl(connfd,F_SETOWN,getpid());使用SIGURG信號(hào)2/6/202363 for(;;){ if((n=Read(connfd,buff,sizeof(buff)-1))==0){ printf("receivedEOF\n"); exit(0); } buff[n]=0; /*nullterminate*/ printf("read%dbytes:%s\n",n,buff); }}voidsig_urg(intsigno){ int n; char buff[100]; printf("SIGURGreceived\n"); n=Recv(connfd,buff,sizeof(buff)-1,MSG_OOB); buff[n]=0; /*nullterminate*/ printf("read%dOOBbyte:%s\n",n,buff);}2/6/202364利用緊急信號(hào)通知帶外數(shù)據(jù)2/6/202365使用select讀取帶外數(shù)據(jù)(接收方)intmain(intargc,char**argv){ int listenfd,connfd,n; char buff[100]; fd_set rset,xset; if(argc==2) listenfd=Tcp_listen(NULL,argv[1],NULL); elseif(argc==3) listenfd=Tcp_listen(argv[1],argv[2],NULL); else err_quit("usage:tcprecv02[<host>]<port#>"); connfd=Accept(listenfd,NULL,NULL);使用select函數(shù)2/6/202366使用select讀取帶外數(shù)據(jù)(接收方) FD_ZERO(&rset); FD_ZERO(&xset); for(;;){ FD_SET(connfd,&rset); FD_SET(connfd,&xset); Select(connfd+1,&rset,NULL,&xset,NULL); if(FD_ISSET(connfd,&xset)){ if((n=Recv(connfd,buff,sizeof(buff)-1,MSG_OOB))<0){ perror(“ReadOOBerror.”); exit(1); } buff[n]=0; /*nullterminate*/ printf("read%dOOBbyte:%s\n",n,buff); }2/6/202367使用select讀取帶外數(shù)據(jù)(接收方) if(FD_ISSET(connfd,&rset)){ if((n=Read(connfd,buff,sizeof(buff)-1))==0){ printf("receivedEOF\n"); exit(0); } buff[n]=0; /*nullterminate*/ printf("read%dbytes:%s\n",n,buff); } }}2/6/202368Linux2/6/202369Solaris2/6/202370使用select讀取帶外數(shù)據(jù)(結(jié)果)$tcprecv8888read3bytes:123read1OOB:4recverror:Invalidargument出現(xiàn)上述錯(cuò)誤的原因是:在Solaris下,select一直指示一個(gè)異常條件,我們讀過(guò)帶外數(shù)據(jù)后,內(nèi)核清除了1字節(jié)的帶外緩沖區(qū),所以我們不能讀。當(dāng)?shù)诙沃付∣OB標(biāo)志調(diào)用recv,返回錯(cuò)誤。解決問(wèn)題的辦法是:只有在讀過(guò)普通數(shù)據(jù)后,才去select異常條件。2/6/202371使用select讀取帶外數(shù)據(jù)(修訂)intmain(intargc,char**argv){ int listenfd,connfd,n,justreadoob=0; char buff[100]; fd_set rset,xset; if(argc==2) listenfd=Tcp_listen(NULL,argv[1],NULL); elseif(argc==3) listenfd=Tcp_listen(argv[1],argv[2],NULL); else err_quit("usage:tcprecv03[<host>]<port#>"); connfd=Accept(listenfd,NULL,NULL);2/6/202372使用select讀取帶外數(shù)據(jù)(修訂) FD_ZERO(&rset); FD_ZERO(&xset); for(;;){ FD_SET(connfd,&rset);
if(justreadoob==0) FD_SET(connfd,&xset); Select(connfd+1,&rset,NULL,&xset,NULL); if(FD_ISSET(connfd,&xset)){ n=Recv(connfd,buff,sizeof(buff)-1,MSG_OOB); buff[n]=0; /*nullterminate*/ printf("read%dOOBbyte:%s\n",n,buff); justreadoob=1; FD_CLR(connfd,&xset); }2/6/202373使用select讀取帶外數(shù)據(jù)(修訂) if(FD_ISSET(connfd,&rset)){ if((n=Read(connfd,buff,sizeof(buff)-1))==0){ printf("receivedEOF\n"); exit(0); } buff[n]=0; /*nullterminate*/ printf("read%dbytes:%s\n",n,buff);
justreadoob=0; } }}這種方法也存在問(wèn)題:例如,發(fā)送方如果連續(xù)發(fā)送兩次帶外數(shù)據(jù)呢?2/6/202374修訂后Solaris2/6/202375帶外標(biāo)記#include<sys/socket.h>intsockatmark(intsockfd); 返回值:如果在帶外標(biāo)記上為1,不在帶外標(biāo)記上為0,處錯(cuò)為-1。每當(dāng)接收到帶外數(shù)據(jù)時(shí),就有一個(gè)相關(guān)聯(lián)的帶外標(biāo)記,接收進(jìn)程可以通過(guò)上述函數(shù)確定是否在帶外標(biāo)記上。不管接收進(jìn)程在線(SO_OOBINLINE選項(xiàng))或是帶外(MSG_OOB標(biāo)志)接收帶外數(shù)據(jù),帶外標(biāo)記都能使用。帶外標(biāo)記的常見(jiàn)用法是接收者特殊地對(duì)待帶外標(biāo)記前或帶外標(biāo)記后的數(shù)據(jù)。2/6/202376帶外標(biāo)記的特性帶外標(biāo)記總是指向剛好越過(guò)普通數(shù)據(jù)最后一個(gè)字節(jié)的地方。這意味著,如果帶外數(shù)據(jù)在線接收,并且下一個(gè)要讀的字節(jié)是被用MSG_OOB標(biāo)志發(fā)送的,sockatmark就返回真。相反,如果SO_OOBINLINE選項(xiàng)沒(méi)有設(shè)置,那么如果下一個(gè)字節(jié)是跟在帶外數(shù)據(jù)后發(fā)送的第一個(gè)字節(jié),sockatmark也返回真。TCP保證讀操作總是會(huì)停在帶外標(biāo)記上。例如:如果在套接字接收緩沖區(qū)中有100個(gè)字節(jié),但帶外標(biāo)記前只有5個(gè)字節(jié),進(jìn)程執(zhí)行read請(qǐng)求100個(gè)字節(jié),則只有標(biāo)記前5個(gè)字節(jié)被讀出。這種標(biāo)記處的強(qiáng)制停止允許進(jìn)程調(diào)用sockatmark確定是否緩沖區(qū)指針在標(biāo)記處。2/6/202377例子程序(發(fā)送程序)intmain(intargc,char**argv){ int sockfd; if(argc!=3) err_quit("usage:tcpsend04<host><port#>"); sockfd=Tcp_connect(argv[1],argv[2]); write(sockfd,"123",3); printf("wrote3bytesofnormaldata\n"); send(sockfd,"4",1,MSG_OOB); printf("wrote1byteofOOBdata\n"); write(sockfd,"56",2); printf("wrote2byteofnormaldata\n"); exit(0);}注意:沒(méi)有sleep為什么?2/6/202378例子程序(接收程序)intmain(intargc,char**argv){ int listenfd,connfd,n,on=1; char buff[100]; if(argc==3) listenfd=Tcp_listen(argv[1],argv[2],NULL); else err_quit("usage:tcprecv04[<host>]<port#>");
Setsockopt(listenfd,SOL_SOCKET,SO_OOBINLINE,&on,sizeof(on)); connfd=Accept(listenfd,NULL,NULL); sleep(5); for(;;){ if(Sockatmark(connfd)) printf("atOOBmark\n");為什么要睡眠?2/6/202379例子程序(接收程序續(xù)) if((n=Read(connfd,buff,sizeof(buff)-1))==0){ printf("receivedEOF\n"); exit(0); } buff[n]=0; /*nullterminate*/ printf("read%dbytes:%s\n",n,buff); }}2/6/202380Linux-12/6/202381問(wèn)題?如果將上例中接收程序的帶外數(shù)據(jù)改為帶外接收,結(jié)果將如何?2/6/202382sockatmark實(shí)現(xiàn)intsockatmark(intfd){ int flag; if(ioctl(fd,SIOCATMARK,&flag)<0) return(-1); return(flag!=0?1:0);}2/6/202383帶外數(shù)據(jù)的另外兩個(gè)特性tcp發(fā)送帶外數(shù)據(jù)的通知(它的緊急指針),即使它因流控停止了數(shù)據(jù)的發(fā)送;在帶外數(shù)據(jù)到來(lái)之前,接收進(jìn)程可得到指示:發(fā)送者已經(jīng)送出了帶外數(shù)據(jù)(用SIGURG信號(hào)或通過(guò)select)。如果接收進(jìn)程接著調(diào)用指定MSG_OOB的recv,而帶外數(shù)據(jù)卻尚未到達(dá),EWOULDBLOCK錯(cuò)誤就會(huì)返回。2/6/202384例子程序(發(fā)送方)intmain(intargc,char**argv){ int sockfd,size; char buff[16384]; if(argc!=3) err_quit("usage:tcpsend04<host><port#>"); sockfd=Tcp_connect(argv[1],argv[2]); size=32768;
Setsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&size,sizeof(size));
Write(sockfd,buff,16384); printf("wrote16384bytesofnormaldata\n");
sleep(5);什么原因?2/6/202385例子程序(發(fā)送方)(續(xù)) Send(sockf
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 醫(yī)療手術(shù)合同范本
- 合開(kāi)店合同范本
- 衛(wèi)生間裝修工人合同范本
- 進(jìn)廠入職合同范本
- 合伙投資合同范本范本
- 個(gè)人之間擔(dān)保合同范本
- 合法會(huì)員合同范例
- 運(yùn)行總工績(jī)效合同范本
- 2025年常溫遠(yuǎn)紅外陶瓷及制品項(xiàng)目建議書(shū)
- 廚房人員用工合同范本
- 交通法律與交通事故處理培訓(xùn)課程與法律解析
- 廣西版四年級(jí)下冊(cè)美術(shù)教案
- 《換熱器及換熱原理》課件
- 兒童權(quán)利公約演示文稿課件
- UPVC排水管技術(shù)標(biāo)準(zhǔn)
- MSA-測(cè)量系統(tǒng)分析模板
- 血透室公休座談水腫的護(hù)理
- 急診預(yù)檢分診專家共識(shí)課件
- 廣州市海珠區(qū)事業(yè)單位考試歷年真題
- 2023年山西省太原市迎澤區(qū)校園招考聘用教師筆試題庫(kù)含答案詳解
- 2023中職27 嬰幼兒保育 賽題 模塊三 嬰幼兒早期學(xué)習(xí)支持(賽項(xiàng)賽題)
評(píng)論
0/150
提交評(píng)論