版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
第五講
高級套接字編程任立勇電子科技大學(xué)計算機學(xué)院2/6/20231目錄高級名字與地址函數(shù)高級i/o函數(shù)高級udp套接字編程帶外數(shù)據(jù)2/6/20232getaddrinfo函數(shù)gethostbyname和gethostbyaddr函數(shù),是依賴于協(xié)議的,而且也是不可重入的。同時以前介紹的函數(shù)也不能調(diào)用一個函數(shù)解決將主機名和服務(wù)名轉(zhuǎn)變成套接字地址結(jié)構(gòu)的問題。指定主機名和服務(wù)名將足以與一個獨立于協(xié)議細節(jié)的服務(wù)建立連接。#include<sys/types.h>#include<sys/socket.h>#include<netdb.h>intgetaddrinfo(constchar*hostname,constchar*service,conststructaddrinfo*hints,structaddrinfo**result);返回:成功返回0;出錯返回非零;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是一個空指針或指向一個addrinfo結(jié)構(gòu)的指針,由調(diào)用者填寫關(guān)于它所想返回的信息類型的線索。調(diào)用者可以設(shè)置的hints結(jié)構(gòu)的成員有:ai_flags(AI_PASSIVE,AI_CANONNAME);ai_family;ai_socktype;ai_protocol。如果函數(shù)返回成功,ressult參數(shù)指向的變量將被填入一個指針,它指向一個由ai_next串起來的結(jié)構(gòu)鏈表,返回這個復(fù)合結(jié)構(gòu)有兩種方式:如果與該hostname對應(yīng)的有多個地址,將按請求的地址族(如果指定了ai_family)線索為每個地址返回一個結(jié)構(gòu);如果該服務(wù)在多種套接字類型上提供,將根據(jù)ai_socktype線索為每個套接字類型返回一個結(jié)構(gòu)。2/6/20235getaddrinfo函數(shù)舉例在沒有提供任何線索的條件下,要求domain服務(wù)查找一個有兩個IP地址的主機,其結(jié)果如右圖。ai_flagsai_familyai_socktypeai_protocolai_addrlenai_canonnameai_addrai_nextai_flagsai_familyai_socktypeai_protocolai_addrlenai_canonnameai_addrai_nextresultsAF_INETSOCK_STREAM016\0Sockaddr_in{}addrinfo{}addrinfo{}見書234頁2/6/20236getaddrinfo的參數(shù)問題getaddrinfo的參數(shù)輸入有很多種組合,許多是無效的,我們一般注意以下幾種情況:指定hostname和service,這在tcp和udp客戶程序中很常見。1)tcp客戶程序遍歷所有返回的IP地址,逐一調(diào)用socket和connect,直到連接成功或所有地址被測試過為止。2)Udp客戶程序中,由getaddrinfo填寫的套接字地址結(jié)構(gòu)被用來調(diào)用sendto。如果客戶程序知道它只處理一種類型的套接字,就應(yīng)把hints結(jié)構(gòu)中的ai_socket設(shè)為SOCK_STREAM或SOCK_DGRAM。一個典型的服務(wù)器程序只用service以及hints結(jié)構(gòu)中的AI_PASSIVE標(biāo)志,而不需要指明hostname,返回的套接字地址結(jié)構(gòu)中包含一個INADDR_ANY或IN6ADDR_ANY的IP地址,隨后可建立監(jiān)聽套接字。對于一個服務(wù)器處理多種服務(wù),我們可以遍歷由getaddrinfo返回的地址結(jié)構(gòu),為每個地址結(jié)構(gòu)創(chuàng)建一個套接字,然后用select處理多個套接字。2/6/20237getaddrinfo的缺點盡管getaddrinfo比gethostbyname與getserbyname函數(shù)的功能更多(單個函數(shù)不僅獨立于協(xié)議,而且既處理了主機名又處理了服務(wù),而且所有返回的所有信息是動態(tài)分配的),但getaddrinfo函數(shù)的hints參數(shù)仍然給調(diào)用者帶來了不少的麻煩(如需要分配一個hints,初始化為0,填上必須的字段,調(diào)用完成后,還需要遍歷鏈表逐一嘗試)。Thegetaddrinfofunctioncombinesthefunctionalityprovidedbythegetipnodebyname,getipnodebyaddr,getservbyname,andgetservbyportfunctionsintoasingleinterface.Thethread-safegetaddrinfofunctioncreatesoneormoresocketaddressstructuresthatcanbeusedbythebindandconnectsystemcallstocreateaclientoraserversocket.2/6/20238freeaddrinfo函數(shù)由getaddrinfo返回的存儲空間,包括addrinfo結(jié)構(gòu)和ai_canonname字符串,都是用malloc動態(tài)分配的。這些空間可調(diào)用freeaddrinfo釋放。#include<netdb.h>voidfreeaddrinfo(structaddrinfo*ai);ai應(yīng)指向getaddrinfo返回的第一個addrinfo結(jié)構(gòu)。在該鏈表中所有結(jié)構(gòu),以及這些結(jié)構(gòu)所指向的動態(tài)存儲空間都將被釋放。2/6/20239getaddrinfo函數(shù)例子2/6/202310getnameinfo函數(shù)這個函數(shù)與getaddrinfo互補:它以一個套接字地址為參數(shù),返回一個描述主機的字符串和描述服務(wù)的字符串。它也是獨立于協(xié)議和可重入的。該函數(shù)結(jié)合了gethostbyaddr與getservbyport兩個函數(shù)的功能。#include<netdb.h>intgetnameinfo(conststructsocckaddr*sockaddr,socklen_taddrlen,char*host,size_thostlen,char*serv,size_tservlen,intflags);返回:成功返回0;出錯返回-1。inet_ntop和getnameinfo的差別在于,前者不查DNS直接返回可輸出的IP地址,而后者通常試圖給主機和服務(wù)查到一個名字。2/6/202311getnameinfo的flags參數(shù)getnameinfo的參數(shù)flags能改變它的操作:NI_DGRAM:數(shù)據(jù)報服務(wù);NI_NAMEREQD:不能從地址反向解析到名字時返回錯誤;NI_NOFQDN:只返回FQDN的主機部分;NI_NUMERICHOST:返回主機的數(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);這兩個函數(shù)分別取代了gethostbyname和gethostbyaddr(legacy),它們只能處理IPv4地址,而且為不可重入函數(shù)。這兩個函數(shù)可以處理多種網(wǎng)絡(luò)協(xié)議族,同時其返回值的空間動態(tài)分配,因此使用完后需調(diào)用freehostent釋放。2/6/202315高級I/O函數(shù):套接字超時有三種方法給套接字上的I/O操作設(shè)置超時:使用select阻塞在等待I/O上,select內(nèi)部有一個時間限制,以此代替在read或write調(diào)用上阻塞。使用新的SO_RCVTIMEO和SO_SNDTIME套接字選項。但并不是所有的實現(xiàn)都支持這兩個選項。調(diào)用alarm,在到達指定時間時產(chǎn)生SIGALRM信號。這涉及到信號處理,而且可能與進程中其他已有的alarm調(diào)用沖突;2/6/202316用SIGALRM給connect設(shè)置超時典型情況下,connect函數(shù)的超時時間為75秒,顯得過長,我們可以利用下面的方法縮小連接建立的超時時間。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è)置的定時器還未到。2/6/202317用SIGALRM給connect設(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ù)進行三次握手。2/6/202318用SIGALRM給connect設(shè)置超時使用這種方法有幾點需要注意:這種技術(shù)可以減少connect的超時時間,但不能延長內(nèi)核中已有的超時時間。如果在調(diào)用alarm函數(shù)安裝時鐘時,以前的時鐘依然存在,則本方法或失效,或影響以前的時鐘。(即:和進程中其他已有的alarm調(diào)用沖突)另,由于大多數(shù)的sleep函數(shù)使用alarm函數(shù)實現(xiàn),因此,如果程序中有調(diào)用sleep函數(shù),本方法可能導(dǎo)致它們相互作用而出現(xiàn)不可預(yù)料的后果。2/6/202319用SIGALRM為recvfrom設(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è)置超時定時器*/ if((n=recvfrom(sockfd,recvline,MAXLINE,0,NULL,NULL))<0){ if(errno==EINTR) fprintf(stderr,"sockettimeout\n");2/6/202320用SIGALRM為recvfrom設(shè)置超時(續(xù)) else err_sys("recvfromerror"); }else{ alarm(0);/*成功接收后,需要撤銷定時器*/ recvline[n]=0; /*nullterminate*/ Fputs(recvline,stdout); } }}staticvoidsig_alrm(intsigno){ return; /*justinterrupttherecvfrom()*/}2/6/202321用select為recvfrom設(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*/}這個函數(shù)的返回值就是select的返回值;這個函數(shù)不執(zhí)行讀操作,它只是等待描述字變成可讀。因此這個函數(shù)可以用在任何類型的套接字上:tcp或udp。可以建立類似的名為writeable_timeo的函數(shù),等待一個描述字變成可寫。2/6/202322用SO_RCVTIMEO選項為recvfrom設(shè)置超時一旦為某個描述字設(shè)置了這個選項,并指定了超時值,那么這個超時對該描述字上的所有讀操作都起作用;與此類似SO_SNDTIMEO只對寫操作起作用。它們都不能對connect設(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ù)這兩個函數(shù)與read和write相似,但readv和writev可以讓我們在一個函數(shù)調(diào)用中讀或?qū)懚鄠€緩沖區(qū),這些操作被稱為分散讀和集中寫。#include<sys/uio.h>ssize_treadv(intfiledes,conststructiovec*iov,intiovcnt);ssize_twritev(intfiledes,conststructiovec*iov,intiovcnt); 返回:讀到或?qū)懗龅淖止?jié)數(shù),出錯時為-1。readv和writev函數(shù)可用于任何描述字,不僅限于套接字描述字,而且writev是一個原子操作。參數(shù)iov是一個指向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的第二個參數(shù)被說明為const,這是因為在讀取過程中,并不修改iovec結(jié)構(gòu)的成員,而只修改iov_base所指向的存儲區(qū);對于分散的緩存的個數(shù)在不同的實現(xiàn)中有不同的限制。函數(shù)readv與writev的最大好處在于:可以通過一個系統(tǒng)調(diào)用實現(xiàn)讀、寫多個緩存,因此,可以極大地減少CPU時間操作SPARC80386用戶系統(tǒng)時鐘用戶系統(tǒng)時鐘兩次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); 返回:成功時為讀入或?qū)懗龅淖止?jié)數(shù),出錯時為-1。這兩個函數(shù)是最通用的i/o函數(shù),實際上,recvmsg可以代替read,readv,recv和recvfrom。同理,各種輸出函數(shù)也可以用sendmsg取代。這兩個函數(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)說明msg_name和msg_namelen成員對于未經(jīng)連接的套接字(如udp套接字),前者指向套接字地址。對recvmsg而言,它們存放發(fā)送方的地址,對sendmsg,它們是目的方地址。對于不需要指明協(xié)議地址(如tcp套接字),msg_name應(yīng)被置成空。msg_iov和msg_iovlen成員指明輸入或輸出的緩沖區(qū)數(shù)組。msg_control和msg_controllen成員指明可選的輔助數(shù)據(jù)的位置和大小。2/6/202328structmsghdr結(jié)構(gòu)說明(續(xù))必須區(qū)別兩個標(biāo)志變量:函數(shù)中的傳值的flags參數(shù)和msghdr結(jié)構(gòu)中的msg_flags(它是以引用方式傳遞的):msg_flags只用于recvmsg。調(diào)用recvmsg時,flags參數(shù)被拷貝到msg_flags成員,而且內(nèi)核用這個值進行接收處理,它的值會根據(jù)recvmsg的結(jié)果而更新。sendmsg會忽略msg_flags成員,因為它在進行輸出時使用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ù)報是一個鏈路層的廣播或其目的IP地址為廣播地址時,將返回此標(biāo)志;MSG_MCAST:當(dāng)收到數(shù)據(jù)是鏈路層的多播時,將設(shè)置概標(biāo)志MSG_TRUNC:這個標(biāo)志在數(shù)據(jù)報被截斷時返回:內(nèi)核有比進程所分配的空間所能容納的還要多的數(shù)據(jù)待返回。MSG_CTRUNC:這個標(biāo)志在輔助數(shù)據(jù)被截斷時返回;2/6/202331msghdr的例子假定進程是對一個udp套接字調(diào)用recvmsg。給協(xié)議地址分配了16個字節(jié),輔助數(shù)據(jù)分配了20個字節(jié),初始化了三個iovec結(jié)構(gòu)元素的數(shù)組:第一個指定一個100字節(jié)的緩沖區(qū),第二個是60字節(jié)的緩沖區(qū),第三個是80字節(jié)的緩沖區(qū)。假定有一個從的端口號2000到來的170字節(jié)的udp數(shù)據(jù)報,目的地是我們的udp套接字,目的ip為5。2/6/202332對一個udp套接字調(diào)用recvmsg時的數(shù)據(jù)結(jié)構(gòu)msg_namemsg_namelenmsg_iovmsg_iovlenmsg_controlmsg_contrllenmsg_flagsmsghdr{}iov_baseiov_leniov_baseiov_leniov_baseiov_len1632001006080iovec{}2/6/202333recvmsg返回時對上圖的更新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時使用msghdr結(jié)構(gòu)中的msg_control和msg_controllen成員發(fā)送或接收輔助數(shù)據(jù)(也叫控制信息)。下面是各種輔助數(shù)據(jù)用法:協(xié)議 cms_level cmsg_type 說明IPv4 IPPROTO_IP IP_RECVDSTADDR 接收udp數(shù)據(jù)報的目的地址 IP_RECVIF 接收udp數(shù)據(jù)報的接口索引IPv6 IPPROTO_IPv6 IPv6_DSTOPTS 指定/接收目標(biāo)選項 IPv6_HOPLIMIT 指定/接收跳限 IPv6_RTHDR 指定/接收路由頭部 IPv6_PKTINFO 指定/接收分組信息Unix域 SOL_SOCKET SCM_RIGHT 發(fā)送/接收描述字 SCM_CREDS 發(fā)送/接收用戶憑證2/6/202335輔助數(shù)據(jù)由一個或多個輔助數(shù)據(jù)對象組成,每個對象由一個cmshdr開頭: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ù)對象輔助數(shù)據(jù)對象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); 成功:返回指向第一個cmsghdr結(jié)構(gòu)的指針。structcmsghdr*CMSG_NXTHDR(structmsghdr*mhdrptr,structcmsghdr*cmsgptr) 返回指向下一個cmsghdr結(jié)構(gòu)的指針;unsignedchar*CMSG_DATA(structcmsghdr*cmsgptr); 返回與cmsghdr結(jié)構(gòu)關(guān)聯(lián)的數(shù)據(jù)的第一個字節(jié);unsignedintCMSG_LEN(unsignedintlength); 返回:給定數(shù)據(jù)量下存儲在cmsg_len中的值;unsignedintCMSG_SPACE(unsignedintlength); 返回:給定數(shù)據(jù)量下一個輔助數(shù)據(jù)對象的總大??;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排隊的數(shù)據(jù)量在不讀出數(shù)據(jù)的情況下,如何知道一個套接字的接收隊列中有多少數(shù)據(jù)可讀呢?有三種方法:如果在沒有數(shù)據(jù)可讀時還有其他事情要做,可以使用非阻塞I/O。如果想檢查一下數(shù)據(jù)而使數(shù)據(jù)仍留在接收隊列中,可以使用MSG_PEEK標(biāo)志。如果想這樣做,但又不能肯定是否有數(shù)據(jù)可讀,可以把這個標(biāo)志和非阻塞接口相結(jié)合,或與MSG_DONTWAIT標(biāo)志結(jié)合使用;一些實現(xiàn)支持ioctl的FIONREAD命令。ioctl的第三個參數(shù)是一個指向整數(shù)的指針,在該整數(shù)中返回的值是套接字接收隊列中數(shù)據(jù)的字節(jié)數(shù)。對udp套接字而言,包括隊列中的所有數(shù)據(jù)報,還要包括每個數(shù)據(jù)報的發(fā)送方的IP地址和端口號。2/6/202339高級UDP套接字編程UDP是無連接服務(wù),很多情況下需要確定udp數(shù)據(jù)報的目的以及是從哪個接口接收數(shù)據(jù)報的。因為一個綁定udp端口和通配地址的套接在能在任何接口上接收單播、廣播和多播數(shù)據(jù)報;多數(shù)udp服務(wù)器程序是迭代執(zhí)行的,但是有些應(yīng)用程序需要在客戶和服務(wù)器間交換多個數(shù)據(jù)報,這是就需要服務(wù)器在某種形式上的并發(fā)。TFTP就是一個例子。2/6/202340改變的recvfrom函數(shù)要求寫一個recvfrom_flags函數(shù),除實現(xiàn)recvfrom的功能外,還要求返回:返回的msg_flags值;收到的數(shù)據(jù)報的目的地址(通過設(shè)置IP_RECVDSTADDR選項);接收數(shù)據(jù)報接口的索引(通過設(shè)置IP_RECVIF選項)。為了返回最后兩項,我們定義了如下結(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ù)報截斷標(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ù)報截斷標(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ù)報截斷標(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ù)報截斷在bsd/os環(huán)境下,當(dāng)一個到來的udp數(shù)據(jù)報長度大于應(yīng)用程序緩沖區(qū),recvmsg設(shè)置MSG_TRUNC標(biāo)志:但對超過預(yù)期長度的udp數(shù)據(jù)報,不同實現(xiàn)有不同的處理方式:丟掉超出的字節(jié)并給應(yīng)用程序返回標(biāo)志;(BSD/OS)(Linux)丟掉超出的字節(jié)但不通知應(yīng)用程序;(solaris2.5中不支持msg_flags)保留超出的字節(jié)并在隨后這個套接字上的讀操作中返回這些數(shù)據(jù)。(早期的SVR4)發(fā)現(xiàn)上述問題的方法:分配一個比應(yīng)用程序可能收到的最大數(shù)據(jù)報大1字節(jié)的緩沖區(qū),如果收到長度等于該緩沖區(qū)長度的數(shù)據(jù)報,就認為發(fā)生了錯誤。2/6/202351何時使用UDP而不是TCP對廣播或多播應(yīng)用程序必須使用UDP。此時,任何形式的期望的錯誤控制必須加入到客戶和服務(wù)器程序中。udp可以用于簡單的請求-應(yīng)答式應(yīng)用程序,但應(yīng)用程序內(nèi)部必須有檢查錯誤的功能。這至少涉及確認、超時和重傳。udp不應(yīng)該用于海量數(shù)據(jù)的傳輸(如文件傳輸)。2/6/202352并發(fā)udp服務(wù)器多數(shù)udp服務(wù)器程序是迭代執(zhí)行的,但當(dāng)處理客戶需要很長時間時,就要有一定形式的并發(fā)。在tcp應(yīng)用中,由于tcp套接字對每個連接都是唯一的,因此很容易實現(xiàn)并發(fā)。但在udp中,我們必須處理兩種不同類型的服務(wù)器:第一種是簡單的udp服務(wù)器,它讀入一個客戶請求,發(fā)送應(yīng)答,接著與這個客戶就無關(guān)了。這種情形下,服務(wù)器可以fork一個子進程去處理請求。子進程可以從得來的內(nèi)存映象中獲得客戶地址,從而將處理結(jié)果返回給客戶。2/6/202353并發(fā)udp服務(wù)器(續(xù))第二種是與客戶交換多個數(shù)據(jù)報的udp服務(wù)器。問題是客戶只知道服務(wù)器的眾所周知的端口??蛻舭l(fā)送請求的第一個數(shù)據(jù)報到達這個端口,服務(wù)器又怎能區(qū)分這是那個客戶的后續(xù)數(shù)據(jù)報還是新請求呢?這種問題的典型解決方法是讓服務(wù)器給每個客戶創(chuàng)建一個新的套接字,bind一個臨時端口到那個套接字,并且對所有的回答都用這個套接口。這要求客戶看一下服務(wù)器的第一個應(yīng)答中的端口號,并且向那個端口發(fā)送請求的后續(xù)數(shù)據(jù)報。2/6/202354并發(fā)udp服務(wù)器(續(xù))客戶服務(wù)器(父進程)端口69服務(wù)器(子進程)端口2134來自客戶的第一個數(shù)據(jù)報來自服務(wù)器的第一個應(yīng)答創(chuàng)建套接字,bind眾所周知端口(69),recvfrom,阻塞至客戶請求到達。fork,接著下一次recvfrom,…創(chuàng)建新的套接字,bind臨時端口(2134),處理客戶請求,在新套接字上與客戶交換其余的數(shù)據(jù)報獨立運行的并發(fā)udp服務(wù)器中需要的處理fork客戶與服務(wù)器間所有剩余數(shù)據(jù)報2/6/202355帶外數(shù)據(jù)許多傳輸層有帶外數(shù)據(jù)的概念(有時稱為加速數(shù)據(jù))。帶外數(shù)據(jù)在排隊等待發(fā)送的普通數(shù)據(jù)之前發(fā)送,但帶外數(shù)據(jù)是映射到現(xiàn)有的連接中的,而不是另外建立一個新的連接。(不同的傳輸層有不同的帶外數(shù)據(jù)實現(xiàn)方式,本節(jié)只介紹tcp帶外數(shù)據(jù))1N套接字發(fā)送緩沖區(qū)要發(fā)送的第一個字節(jié)要發(fā)送的最后一個字節(jié)1套接字發(fā)送緩沖區(qū)要發(fā)送的第一個字節(jié)要發(fā)送的最后一個字節(jié)NOOBTCP緊急指針包含要發(fā)送數(shù)據(jù)的套接字發(fā)送緩沖區(qū)寫入1字節(jié)的帶外數(shù)據(jù)后的套接字發(fā)送緩沖區(qū)2/6/202356發(fā)送方對帶外數(shù)據(jù)的處理一旦調(diào)用帶緊急數(shù)據(jù)標(biāo)志的發(fā)送函數(shù)(如send(fd,’a’,1,MSG_OOB)),由tcp發(fā)送的下一個分節(jié)將會在tcp頭部中設(shè)置URG標(biāo)志,并且頭部中的緊急偏移字段也將指向帶外字節(jié)后的字節(jié)。但是這個分節(jié)可以含有也可以不含有我們標(biāo)記為OOB的字節(jié),是否發(fā)送它取決于三個因素:a)套接字發(fā)送緩沖區(qū)中它前面的字節(jié)數(shù)、b)tcp發(fā)送給對方的分節(jié)長度、c)對方的通告窗口。如果tcp因流控停止了(接收者的套接字接收緩沖區(qū)滿了),緊急通知將不帶任何數(shù)據(jù)地被送出。這就是為什么應(yīng)用程序使用tcp的緊急模式的另一個原因:即便數(shù)據(jù)流因tcp的流控停止了,緊急通知也總會被發(fā)送到對方的tcp。2/6/202357接收方對帶外數(shù)據(jù)的處理當(dāng)tcp收到了一個設(shè)置了URG表示的分節(jié)時,緊急指針被檢查,看它是否指向新的帶外數(shù)據(jù)。發(fā)送者tcp有可能發(fā)送多個包含URG標(biāo)志,但緊急指針卻指向相同的數(shù)據(jù)字節(jié)的分節(jié)(通常在一小段時間內(nèi)?),這種情況相當(dāng)普遍。需要注意的是:這些分節(jié)中只有第一個分節(jié)會導(dǎo)致接收進程被通知有新的帶外數(shù)據(jù)到達;當(dāng)新緊急指針到達時,接收進程被通知。首先SIGURG信號發(fā)送給套接字的屬主,其次如果進程阻塞在select調(diào)用中,等待這個套接字描述字有一個異常條件,那么select返回;2/6/202358接收者對帶外數(shù)據(jù)的處理(續(xù))當(dāng)由緊急指針指向的實際數(shù)據(jù)字節(jié)到達接收者tcp時,這個數(shù)據(jù)字節(jié)可以被拉出帶外或繼續(xù)在線存放。正常情況下,帶外數(shù)據(jù)字節(jié)并不放入套接字接收緩沖區(qū)。相反,這個數(shù)據(jù)字節(jié)被放到這個連接的單獨的1字節(jié)帶外緩沖區(qū),進程讀出這個數(shù)據(jù)的唯一方法是調(diào)用recv、recvfrom或者recvmsg,并指定MSG_OOB標(biāo)志。但如果設(shè)置了SO_OOBINLINE選項,帶外數(shù)據(jù)留在接收緩沖區(qū),而不能通過指定MSG_OOB標(biāo)志讀取。2/6/202359處理帶外數(shù)據(jù)時可能出現(xiàn)的錯誤如果進程請求讀取帶外數(shù)據(jù)(例如指定MSG_OOB標(biāo)志),但是對方尚未發(fā)送,將會返回EINVAL;如果進程已被通知對方發(fā)送了帶外數(shù)據(jù)(例如通過SIGURG或select),進程試圖去讀它,但是那個字節(jié)還沒有到達,將會返回EWOULDBLOCK;如果進程試圖多次讀相同的帶外數(shù)據(jù),將會返回EINVAL;如果進程已經(jīng)設(shè)置了SO_OOBINLINE套接口選項,接著試圖通過指定MSG_OOB讀帶外數(shù)據(jù),將會返回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信號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利用緊急信號通知帶外數(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)上述錯誤的原因是:在Solaris下,select一直指示一個異常條件,我們讀過帶外數(shù)據(jù)后,內(nèi)核清除了1字節(jié)的帶外緩沖區(qū),所以我們不能讀。當(dāng)?shù)诙沃付∣OB標(biāo)志調(diào)用recv,返回錯誤。解決問題的辦法是:只有在讀過普通數(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; } }}這種方法也存在問題:例如,發(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,處錯為-1。每當(dāng)接收到帶外數(shù)據(jù)時,就有一個相關(guān)聯(lián)的帶外標(biāo)記,接收進程可以通過上述函數(shù)確定是否在帶外標(biāo)記上。不管接收進程在線(SO_OOBINLINE選項)或是帶外(MSG_OOB標(biāo)志)接收帶外數(shù)據(jù),帶外標(biāo)記都能使用。帶外標(biāo)記的常見用法是接收者特殊地對待帶外標(biāo)記前或帶外標(biāo)記后的數(shù)據(jù)。2/6/202376帶外標(biāo)記的特性帶外標(biāo)記總是指向剛好越過普通數(shù)據(jù)最后一個字節(jié)的地方。這意味著,如果帶外數(shù)據(jù)在線接收,并且下一個要讀的字節(jié)是被用MSG_OOB標(biāo)志發(fā)送的,sockatmark就返回真。相反,如果SO_OOBINLINE選項沒有設(shè)置,那么如果下一個字節(jié)是跟在帶外數(shù)據(jù)后發(fā)送的第一個字節(jié),sockatmark也返回真。TCP保證讀操作總是會停在帶外標(biāo)記上。例如:如果在套接字接收緩沖區(qū)中有100個字節(jié),但帶外標(biāo)記前只有5個字節(jié),進程執(zhí)行read請求100個字節(jié),則只有標(biāo)記前5個字節(jié)被讀出。這種標(biāo)記處的強制停止允許進程調(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);}注意:沒有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問題?如果將上例中接收程序的帶外數(shù)據(jù)改為帶外接收,結(jié)果將如何?2/6/202382sockatmark實現(xiàn)intsockatmark(intfd){ int flag; if(ioctl(fd,SIOCATMARK,&flag)<0) return(-1); return(flag!=0?1:0);}2/6/202383帶外數(shù)據(jù)的另外兩個特性tcp發(fā)送帶外數(shù)據(jù)的通知(它的緊急指針),即使它因流控停止了數(shù)據(jù)的發(fā)送;在帶外數(shù)據(jù)到來之前,接收進程可得到指示:發(fā)送者已經(jīng)送出了帶外數(shù)據(jù)(用SIGURG信號或通過select)。如果接收進程接著調(diào)用指定MSG_OOB的recv,而帶外數(shù)據(jù)卻尚未到達,EWOULDBLOCK錯誤就會返回。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. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024年租賃合同中的維修責(zé)任
- 研究生復(fù)試課程設(shè)計問題
- 紅色課程設(shè)計思
- 幼兒園青蛙課程設(shè)計
- 步進式運輸機課程設(shè)計
- 舞蹈身材訓(xùn)練課程設(shè)計
- 班主任工作中的困惑與解決之道
- 電子心率計數(shù)器課程設(shè)計
- 硬件課程設(shè)計 函數(shù)
- 2024年物業(yè)管理年終工作總結(jié)范文(31篇)
- 信號分析與處理-教學(xué)大綱
- 國家醫(yī)療保障疾病診斷相關(guān)分組(CHS-DRG)分組與付費技術(shù)規(guī)范(可編輯)
- 特許經(jīng)銷合同
- 吉林大學(xué)藥學(xué)導(dǎo)論期末考試高分題庫全集含答案
- 2023-2024學(xué)年河北省唐山市灤州市數(shù)學(xué)七年級第一學(xué)期期末教學(xué)質(zhì)量檢測模擬試題含解析
- 數(shù)字油畫課件
- 2023年小學(xué)五年級數(shù)學(xué)上學(xué)期期末水平測試試卷(天河區(qū))
- 中考數(shù)學(xué)計算題100道
- 高壓變頻器整流變壓器
- 集團資產(chǎn)重組實施方案
- 《新唯識論》儒佛會通思想研究
評論
0/150
提交評論