




已阅读5页,还剩96页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
socket I/O模型详解本文简单介绍了当前Windows支持的各种SocketI/O模型,如果你发现其中存在什么错误请务必赐教。一:select模型二:WSAAsyncSelect模型三:WSAEventSelect模型四:OverlappedI/O事件通知模型五:OverlappedI/O完成例程模型六:IOCP模型老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。这和Socket模型非常类似,下面我就以老陈接收信件为例讲解SocketI/O模型。一:select模型老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱,看是否有女儿的信。在这种情况下,下楼检查信箱然后回到楼上耽误了老陈太多的时间,以至于老陈无法做其他工作。select模型和老陈的这种情况非常相似:周而复始地去检查.如果有数据.接收/发送.。使用线程来select应该是通用的做法:procedureTListenThread.Execute;varaddr:TSockAddrIn;fd_read:TFDSet;timeout:TTimeVal;ASock,MainSock:TSocket;len,i:Integer;beginMainSock:=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);addr.sin_family:=AF_INET;addr.sin_port:=htons(5678);addr.sin_addr.S_addr:=htonl(INADDR_ANY);bind(MainSock,addr,sizeof(addr);listen(MainSock,5);while(notTerminated)dobeginFD_ZERO(fd_read);FD_SET(MainSock,fd_read);timeout.tv_sec:=0;timeout.tv_usec:=500;ifselect(0,fd_read,nil,nil,timeout)0then/至少有1个等待Accept的connectionbeginifFD_ISSET(MainSock,fd_read)thenbeginfori:=0tofd_read.fd_count-1do/注意,fd_count=64,也就是说select只能同时管理最多64个连接beginlen:=sizeof(addr);ASock:=accept(MainSock,addr,len);ifASockINVALID_SOCKETthen./为ASock创建一个新的线程,在新的线程中再不停地selectend;end;end;end;/while(notself.Terminated)shutdown(MainSock,SD_BOTH);closesocket(MainSock);end;二:WSAAsyncSelect模型后来,老陈使用了微软公司的新式信箱。这种信箱非常先进,一旦信箱里有新的信件,盖茨就会给老陈打电话:喂,大爷,你有新的信件了!从此,老陈再也不必频繁上下楼检查信箱了。微软提供的WSAAsyncSelect模型就是这个意思。WSAAsyncSelect模型是Windows下最简单易用的一种SocketI/O模型。使用这种模型时,Windows会把网络事件以消息的形势通知应用程序。首先定义一个消息标示常量:constWM_SOCKET=WM_USER+55;再在主Form的private域添加一个处理此消息的函数声明:privateprocedureWMSocket(varMsg:TMessage);messageWM_SOCKET;然后就可以使用WSAAsyncSelect了:varaddr:TSockAddr;sock:TSocket;sock:=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);addr.sin_family:=AF_INET;addr.sin_port:=htons(5678);addr.sin_addr.S_addr:=htonl(INADDR_ANY);bind(m_sock,addr,sizeof(SOCKADDR);WSAAsyncSelect(m_sock,Handle,WM_SOCKET,FD_ACCEPTorFD_CLOSE);listen(m_sock,5);.应用程序可以对收到WM_SOCKET消息进行分析,判断是哪一个socket产生了网络事件以及事件类型:procedureTfmMain.WMSocket(varMsg:TMessage);varsock:TSocket;addr:TSockAddrIn;addrlen:Integer;buf:Array0.4095ofChar;begin/Msg的WParam是产生了网络事件的socket句柄,LParam则包含了事件类型caseWSAGetSelectEvent(Msg.LParam)ofFD_ACCEPT:beginaddrlen:=sizeof(addr);sock:=accept(Msg.WParam,addr,addrlen);ifsockINVALID_SOCKETthenWSAAsyncSelect(sock,Handle,WM_SOCKET,FD_READorFD_WRITEorFD_CLOSE);end;FD_CLOSE:closesocket(Msg.WParam);FD_READ:recv(Msg.WParam,buf0,4096,0);FD_WRITE:;end;end;三:WSAEventSelect模型后来,微软的信箱非常畅销,购买微软信箱的人以百万计数.以至于盖茨每天24小时给客户打电话,累得腰酸背痛,微软改进了他们的信箱:在客户的家中添加一个附加装置,这个装置会监视客户的信箱,每当新的信件来临,此装置会发出新信件到达声,提醒老陈去收信。盖茨终于可以睡觉了。同样要使用线程:procedureTListenThread.Execute;varhEvent:WSAEvent;ret:Integer;ne:TWSANetworkEvents;sock:TSocket;adr:TSockAddrIn;sMsg:String;Index,EventTotal:DWORD;EventArray:Array0.WSA_MAXIMUM_WAIT_EVENTS-1ofWSAEVENT;begin.socket.bind.hEvent:=WSACreateEvent();WSAEventSelect(ListenSock,hEvent,FD_ACCEPTorFD_CLOSE);.listen.while(notTerminated)dobeginIndex:=WSAWaitForMultipleEvents(EventTotal,EventArray0,FALSE,WSA_INFINITE,FALSE);FillChar(ne,sizeof(ne),0);WSAEnumNetworkEvents(SockArrayIndex-WSA_WAIT_EVENT_0,EventArrayIndex-WSA_WAIT_EVENT_0,ne);if(ne.lNetworkEventsandFD_ACCEPT)0thenbeginifne.iErrorCodeFD_ACCEPT_BIT0thencontinue;ret:=sizeof(adr);sock:=accept(SockArrayIndex-WSA_WAIT_EVENT_0,adr,ret);ifEventTotalWSA_MAXIMUM_WAIT_EVENTS-1then/这里WSA_MAXIMUM_WAIT_EVENTS同样是64beginclosesocket(sock);continue;end;hEvent:=WSACreateEvent();WSAEventSelect(sock,hEvent,FD_READorFD_WRITEorFD_CLOSE);SockArrayEventTotal:=sock;EventArrayEventTotal:=hEvent;Inc(EventTotal);end;if(ne.lNetworkEventsandFD_READ)0thenbeginifne.iErrorCodeFD_READ_BIT0thencontinue;FillChar(RecvBuf0,PACK_SIZE_RECEIVE,0);ret:=recv(SockArrayIndex-WSA_WAIT_EVENT_0,RecvBuf0,PACK_SIZE_RECEIVE,0);.end;end;end;四:OverlappedI/O事件通知模型后来,微软通过调查发现,老陈不喜欢上下楼收发信件,因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术,只要用户告诉微软自己的家在几楼几号,新式信箱会把信件直接传送到用户的家中,然后告诉用户,你的信件已经放到你的家中了!老陈很高兴,因为他不必再亲自收发信件了!OverlappedI/O事件通知模型和WSAEventSelect模型在实现上非常相似,主要区别在Overlapped,Overlapped模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个WinsockI/O请求。这些提交的请求完成后,应用程序会收到通知。什么意思呢?就是说,如果你想从socket上接收数据,只需要告诉系统,由系统为你接收数据,而你需要做的只是为系统提供一个缓冲区。Listen线程和WSAEventSelect模型一模一样,Recv/Send线程则完全不同:procedureTOverlapThread.Execute;vardwTemp:DWORD;ret:Integer;Index:DWORD;begin.while(notTerminated)dobeginIndex:=WSAWaitForMultipleEvents(FLinks.Count,FLinks.Events0,FALSE,RECV_TIME_OUT,FALSE);Dec(Index,WSA_WAIT_EVENT_0);ifIndexWSA_MAXIMUM_WAIT_EVENTS-1then/超时或者其他错误continue;WSAResetEvent(FLinks.EventsIndex);WSAGetOverlappedResult(FLinks.SocketsIndex,FLinks.pOverlapsIndex,dwTemp,FALSE,FLinks.pdwFlagsIndex);ifdwTemp=0then/连接已经关闭begin.continue;endelsebeginfmMain.ListBox1.Items.Add(FLinks.pBufsIndex.buf);end;/初始化缓冲区FLinks.pdwFlagsIndex:=0;FillChar(FLinks.pOverlapsIndex,sizeof(WSAOVERLAPPED),0);FLinks.pOverlapsIndex.hEvent:=FLinks.EventsIndex;FillChar(FLinks.pBufsIndex.buf,BUFFER_SIZE,0);/递一个接收数据请求WSARecv(FLinks.SocketsIndex,FLinks.pBufsIndex,1,FLinks.pdwRecvdIndex,FLinks.pdwFlagsIndex,FLinks.pOverlapsIndex,nil);end;end;五:OverlappedI/O完成例程模型老陈接收到新的信件后,一般的程序是:打开信封-掏出信纸-阅读信件-回复信件.为了进一步减轻用户负担,微软又开发了一种新的技术:用户只要告诉微软对信件的操作步骤,微软信箱将按照这些步骤去处理信件,不再需要用户亲自拆信/阅读/回复了!老陈终于过上了小资生活!OverlappedI/O完成例程要求用户提供一个回调函数,发生新的网络事件的时候系统将执行这个函数:procedureWorkerRoutine(constdwError,cbTransferred:DWORD;constlpOverlapped:LPWSAOVERLAPPED;constdwFlags:DWORD);stdcall;然后告诉系统用WorkerRoutine函数处理接收到的数据:WSARecv(m_socket,FBuf,1,dwTemp,dwFlag,m_overlap,WorkerRoutine);然后.没有什么然后了,系统什么都给你做了!微软真实体贴!while(notTerminated)do/这就是一个Recv/Send线程要做的事情.什么都不用做啊!beginifSleepEx(RECV_TIME_OUT,True)=WAIT_IO_COMPLETIONthen/begin;endelsebegincontinue;end;end;六:IOCP模型微软信箱似乎很完美,老陈也很满意。但是在一些大公司情况却完全不同!这些大公司有数以万计的信箱,每秒钟都有数以百计的信件需要处理,以至于微软信箱经常因超负荷运转而崩溃!需要重新启动!微软不得不使出杀手锏.微软给每个大公司派了一名名叫CompletionPort的超级机器人,让这个机器人去处理那些信件!WindowsNT小组注意到这些应用程序的性能没有预料的那么高。特别的,处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的没有被挂起和等待发生什么事,Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文Context,线程就没有得到很多CPU时间来做它们的工作。大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小,但也远不是没有开销的。我们不妨设想一下:如果事先开好N个线程,让它们在那hold堵塞,然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源,也提高了线程的利用率。理论上很不错,你想我等泛泛之辈都能想出来的问题,Microsoft又怎会没有考虑到呢?-摘自nonocast的理解I/OCompletionPort先看一下IOCP模型的实现:/创建一个完成端口FCompletPort:=CreateIoCompletionPort(INVALID_HANDLE_value,0,0,0);/接受远程连接,并把这个连接的socket句柄绑定到刚才创建的IOCP上AConnect:=accept(FListenSock,addr,len);CreateIoCompletionPort(AConnect,FCompletPort,nil,0);/创建CPU数*2+2个线程fori:=1tosi.dwNumberOfProcessors*2+2dobeginAThread:=TRecvSendThread.Create(false);AThread.CompletPort:=FCompletPort;/告诉这个线程,你要去这个IOCP去访问数据end;OK,就这么简单,我们要做的就是建立一个IOCP,把远程连接的socket句柄绑定到刚才创建的IOCP上,最后创建n个线程,并告诉这n个线程到这个IOCP上去访问数据就可以了。再看一下TRecvSendThread线程都干些什么:procedureTRecvSendThread.Execute;var.beginwhile(notself.Terminated)dobegin/查询IOCP状态(数据读写操作是否完成)GetQueuedCompletionStatus(CompletPort,BytesTransd,CompletKey,POVERLAPPED(pPerIoDat),TIME_OUT);ifBytesTransd0then.;/数据读写操作完成/再投递一个读数据请求WSARecv(CompletKey,(pPerIoDat.BufData),1,BytesRecv,Flags,(pPerIoDat.Overlap),nil);end;end;读写线程只是简单地检查IOCP是否完成了我们投递的读写操作,如果完成了则再投递一个新的读写请求。应该注意到,我们创建的所有TRecvSendThread都在访问同一个IOCP(因为我们只创建了一个IOCP),并且我们没有使用临界区!难道不会产生冲突吗?不用考虑同步问题吗?呵呵,这正是IOCP的奥妙所在。IOCP不是一个普通的对象,不需要考虑线程安全问题。它会自动调配访问它的线程:如果某个socket上有一个线程A正在访问,那么线程B的访问请求会被分配到另外一个socket。这一切都是由系统自动调配的,我们无需过问。windows网络编程技术之 Winsock I/O方法select模型:select函数原型int select( int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);nfds为了保持与早期兼容,可忽略。fd_set,代表一系列特定的套接字的集合,readset用于检查可读性,writeset用于检查可写性,exceptset用于检查异常数据,具体用法如下:假定我们想测试一个套接字是否可读,必须将该套接字增加到readset中,再等待select函数完成,完成后判断该套接字是否仍然是readset集合的一部分,是,则该套接字可读。三个参数至少有一个不为null。其中的timeval的结构如下:struct timeval long tv_sec; (秒)longtv_usec; (毫秒)如select函数调用失败,返回SOCKET_ERROR.对fd_set集合进行操作,可用宏:FD_CLR(s,*set):从set中删除套接字s;FD_ISSET(s,*set):检查s是否是set集合的一员,返回true或falseFD_SET(s,*set):将套接字s加入集合setFD_ZERO(*set):将set初始化为空集合。WSAAsyncSelect模型利用这个模型,应用程序可在一个套接字上接收以Windows消息为基础的网络时间通知。使用这种模型,必须提供一个“窗口”,用于接收消息。函数原型如下:int WSAAsyncSelect( SOCKET s,HWND hwnd,unsigned int wMsg,Long lEvent);s:对应套接字hwnd:接收消息的窗体句柄wMsg:发送的消息,应该为一个比WM_USER大的值。IEvent:对应为一系列网络事件的组合,包括:FD_READ,FD_WRITE,FD_ACCEPT,FD_CONNECT,FD_CLOSE.如:WSAAsyncSelect(s,hwnd,WM_SOCKET,FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE)在调用WSAAsyncSelect后,即在hwnd对应的窗体回调函数中对网络事件(消息)进行处理:LRESULT CALLBACK WindowProc( HWND hWnd, UINTuMsg, WPARAM wParam, LPARAM lParam);其中wParam指定在其上面发生了一个网络事件的套接字。lParam低字节指定了已经发生的网络事件,而lParam的高字节包含了可能出现的错误代码。在网络事件消息到达的时候,先调用WSAGETSELECTERROR宏返回高字节包含的错误信息。若没有错误,则调用WSAGETSELECTEVENT宏返回lParam低字节部分。WSAEventSelect 模型和WSAAsyncSelect模型不同的是,这种模型中网络事件会投递至一个事件对象句柄,而非窗口句柄。所以要求我们针对每一个套接字都要建立一个事件对象。用WSACreateEvent函数,定义如下:WSAEVENT WSACreateEvent(void);然后调用WSAEventSelect函数,定义如下:int WSAEventSelect( SOCKET s; WSAEVENT hEventObject, long lNetworkEvents);鉴于这个模型的复杂性,还是用代码说话吧:#include #include #include #define PORT 5150#define DATA_BUFSIZE 8192typedef struct _SOCKET_INFORMATION CHAR BufferDATA_BUFSIZE; WSABUF DataBuf; SOCKET Socket; DWORD BytesSEND; DWORD BytesRECV; SOCKET_INFORMATION, * LPSOCKET_INFORMATION;BOOL CreateSocketInformation(SOCKET s);void FreeSocketInformation(DWORD Event);DWORD EventTotal = 0;WSAEVENT EventArrayWSA_MAXIMUM_WAIT_EVENTS;LPSOCKET_INFORMATION SocketArrayWSA_MAXIMUM_WAIT_EVENTS;void main(void) SOCKET Listen; SOCKET Accept; SOCKADDR_IN InternetAddr; DWORD Event; WSANETWORKEVENTS NetworkEvents; WSADATA wsaData; DWORD Ret; DWORD Flags; DWORD RecvBytes; DWORD SendBytes; if (Ret = WSAStartup(0x0202, &wsaData) != 0) /初始化winsock printf(WSAStartup() failed with error %dn, Ret); return; if (Listen = socket (AF_INET, SOCK_STREAM, 0) = INVALID_SOCKET)/创建Listen套接字 printf(socket() failed with error %dn, WSAGetLastError(); return; CreateSocketInformation(Listen); /创建一个_SOCKET_INFORMATION结构实体 if (WSAEventSelect(Listen, EventArrayEventTotal - 1, FD_ACCEPT|FD_CLOSE) = SOCKET_ERROR) printf(WSAEventSelect() failed with error %dn, WSAGetLastError(); return; InternetAddr.sin_family = AF_INET; InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY); InternetAddr.sin_port = htons(PORT); if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr) = SOCKET_ERROR) printf(bind() failed with error %dn, WSAGetLastError(); return; if (listen(Listen, 5) printf(listen() failed with error %dn, WSAGetLastError(); return; while(TRUE) / Wait for one of the sockets to receive I/O notification and if (Event = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE) = WSA_WAIT_FAILED) printf(WSAWaitForMultipleEvents failed with error %dn, WSAGetLastError(); return; /通过SocketArrayEvent - WSA_WAIT_EVENT_0获得事件是在哪个socket上发生/通过WSAEnumNetworkEvents获得发生了什么类型的网络事件 if (WSAEnumNetworkEvents(SocketArrayEvent - WSA_WAIT_EVENT_0-Socket, EventArrayEvent - WSA_WAIT_EVENT_0, &NetworkEvents) = SOCKET_ERROR)/必须减去预定义值,才能得到实际的索引位置 printf(WSAEnumNetworkEvents failed with error %dn, WSAGetLastError(); return; if (NetworkEvents.lNetworkEvents & FD_ACCEPT) if (NetworkEvents.iErrorCodeFD_ACCEPT_BIT != 0) printf(FD_ACCEPT failed with error %dn, NetworkEvents.iErrorCodeFD_ACCEPT_BIT); break; if (Accept = accept(SocketArrayEvent - WSA_WAIT_EVENT_0-Socket, NULL, NULL) = INVALID_SOCKET) printf(accept() failed with error %dn, WSAGetLastError(); break; if (EventTotal WSA_MAXIMUM_WAIT_EVENTS) printf(Too many connections - closing socket.n); closesocket(Accept); break; CreateSocketInformation(Accept);/一旦有套接字accepted了,就创建事件对象和LPSOCKET_INFORMATION 结构 if (WSAEventSelect(Accept, EventArrayEventTotal - 1, FD_READ|FD_WRITE|FD_CLOSE) = SOCKET_ERROR) /继续对accepted 的 套接字调用WSAEventSelect printf(WSAEventSelect() failed with error %dn, WSAGetLastError(); return; printf(Socket %d connectedn, Accept); / Try to read and write data to and from the data buffer if read and write events occur. if (NetworkEvents.lNetworkEvents & FD_READ | NetworkEvents.lNetworkEvents & FD_WRITE) if (NetworkEvents.lNetworkEvents & FD_READ & NetworkEvents.iErrorCodeFD_READ_BIT != 0) printf(FD_READ failed with error %dn, NetworkEvents.iErrorCodeFD_READ_BIT); break; if (NetworkEvents.lNetworkEvents & FD_WRITE & NetworkEvents.iErrorCodeFD_WRITE_BIT != 0) printf(FD_WRITE failed with error %dn, NetworkEvents.iErrorCodeFD_WRITE_BIT); break; LPSOCKET_INFORMATION SocketInfo = SocketArrayEvent - WSA_WAIT_EVENT_0; / Read data only if the receive buffer is empty. if (SocketInfo-BytesRECV = 0) SocketInfo-DataBuf.buf = SocketInfo-Buffer; SocketInfo-DataBuf.len = DATA_BUFSIZE; Flags = 0; if (WSARecv(SocketInfo-Socket, &(SocketInfo-DataBuf), 1, &RecvBytes, &Flags, NULL, NULL) = SOCKET_ERROR) if (WSAGetLastError() != WSAEWOULDBLOCK) printf(WSARecv() failed with error %dn, WSAGetLastError(); FreeSocketInformation(Event - WSA_WAIT_EVENT_0); return; else SocketInfo-BytesRECV = RecvBytes; / Write buffer data if it is available. if (SocketInfo-BytesRECV SocketInfo-BytesSEND) SocketInfo-DataBuf.buf = SocketInfo-Buffer + SocketInfo-BytesSEND; SocketInfo-DataBuf.len = SocketInfo-BytesRECV - SocketInfo-BytesSEND; if (WSASend(SocketInfo-Socket, &(SocketInfo-DataBuf), 1, &SendBytes, 0, NULL, NULL) = SOCKET_ERROR) if (WSAGetLastError() != WSAEWOULDBLOCK) printf(WSASend() failed with error %dn, WSAGetLastError(); FreeSocketInformation(Event - WSA_WAIT_EVENT_0); return; / A WSAEWOULDBLOCK error has occured. An FD_WRITE event will be posted / when more buffer space becomes available else SocketInfo-BytesSEND += SendBytes; if (SocketInfo-BytesSEND = SocketInfo-BytesRECV) SocketInfo-BytesSEND = 0; SocketInfo-BytesRECV = 0; if (NetworkEvents.lNetworkEvents & FD_CLOSE) if (NetworkEvents.iErrorCodeFD_CLOSE_BIT != 0) printf(FD_CLOSE failed with error %dn, NetworkEvents.iErrorCodeFD_CLOSE_BIT); break; printf(Closing socket information %dn, SocketArrayEvent - WSA_WAIT_EVENT_0-Socket); FreeSocketInformation(Event - WSA_WAIT_EVENT_0); return;BOOL CreateSocketInformation(SOCKET s) LPSOCKET_INFORMATION SI; if (EventArrayEventTotal = WSACreateEvent() = WSA_INVALID_EVENT) printf(WSACreateEvent() failed with error %dn, WSAGetLastError(); return FALSE; if (SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR, sizeof(SOCKET_INFORMATION) = NULL)
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度离婚协议书中赡养费支付协议范本
- 二零二五年度出口货运代理多国贸易服务协议
- 二零二五版环保技术研发合作合同示范文本
- 二零二五年度文化主题店铺特许经营合同样本
- 二零二五年度水利工程测绘技术服务合同
- 2025版绿色施工技术培训施工队合作协议
- 2025版夫妻共同生活准则不离家协议
- 2025版生物科技企业场岗位保密协议实施细则
- 二零二五年度电商数据分析与报告专员劳动合同标准
- 二零二五年度家电产品进出口贸易合作协议书
- 出租车车辆GPS定位承包合同范本
- 2025年四川省眉山市【辅警协警】笔试真题(含答案)
- 城市污水处理厂运行承诺及保障措施
- 焊接机器人教学培训课件
- 肝脓肿病人护理
- 膝关节炎的康复治疗讲课件
- 2025食品安全考试题库及答案
- 福建省厦门中烟益升华滤嘴棒有限责任公司招聘笔试题库2025
- 浙江隆宸现代农业科技有限公司年产4500吨双孢蘑菇技改项目环评报告
- 《城镇房屋租赁合同(示范文本)》(GF-2025-2614)
- 2025上半年广西现代物流集团社会招聘校园招聘149人笔试参考题库附带答案详解
评论
0/150
提交评论