socket模型有哪几种,socket开发原理
本文简要介绍了Windows目前支持的各种Socket I/O模型。如果你发现其中有错误,请给我们提意见。
1.选择型号
二:WSAAsyncSelect模型
三:WSAEventSelect模型
4.重叠I/O事件通知模型
五:重叠I/O完成例程模型
六:IOCP模式
老陈有个女儿在外地工作,不能经常回来。老陈写信联系她。他们的信将由邮递员送到他们的邮箱。
这与套接字模型非常相似。我以老陈的收信为例来解释一下Socket I/O模型吧~ ~ ~
1.选择型号
陈渴望看到女儿的来信。以至于他每隔10分钟就下楼查看邮箱,看看有没有女儿的来信~ ~ ~ ~
在这种情况下,‘下楼查看邮箱’再上楼,耽误了陈先生太多时间,其他工作都做不了。
选择模型和陈老师的这种情况很像:反复检查.如果有数据.接收/发送它。
使用线程进行选择应该是一种常见的做法:
过程TListenThread。执行;
定义变量
addr:TSockAddrIn;
FD _ read:tfd set;
超时:TTimeVal
阿索克,
main sock:t socket;
len,I:整数;
开始
main stock:=socket(AF _ INET,SOCK_STREAM,IP proto _ TCP);
addr . sin _ family:=AF _ INET;
addr . sin _ port:=htons(5678);
addr.sin_addr。s _ addr:=htonl(in addr _ ANY);
bind(main stock,@addr,sizeof(addr));
听(MainSock,5);
while(未终止)do
开始
FD _ ZERO(FD _ read);
FD _ SET(main stock,FD _ read);
time out . TV _ sec:=0;
time out . TV _ usec:=500;
If (0,@ fd _ read,nil,nil,@ time out)0则//至少有一个连接等待接受
开始
如果FD _ ISSET(main stock,fd_read)则
开始
对于I:=0到FD _ read . FD _ count-1do//注意fd_count=64,这意味着select最多只能同时管理64个连接。
开始
len:=sizeof(addr);
ASock:=accept(main stock,addr,len);
如果一个套接字无效,那么
.//为ASock创建一个新线程,并在新线程中保持选择。
结束;
结束;
结束;
结束;//while(不是self。已终止)
关机(MainSock,SD _ BOTH);
closesocket(主锁);
结束;
二:WSAAsyncSelect模型
后来陈先生用了微软的新邮箱。这种邮箱很高级。一旦邮箱里有新的信件,盖茨就会给陈先生打电话:嘿,爷爷,你有新的信件!从此,陈先生再也不用频繁的上下楼查看邮箱了,牙齿也不疼了。看看它,蓝天.不是,微软~ ~ ~ ~ ~ ~ ~
微软提供的WSAAsyncSelect模型就是这个意思。
WSAAsyncSelect模型是Windows下最简单易用的Socket I/O模型。使用这种模式时,Windows会以消息的形式通知应用程序网络事件。
首先,定义一个消息标记常量:
const WM _ SOCKET=WM _ USER 55
添加一个函数声明以在主窗体的私有域中处理该消息:
私人的
过程WM socket(var Msg:t message);消息WM _ SOCKET
然后可以使用WSAAsyncSelect:
定义变量
addr:TSockAddr;
袜子:TSocket
sock :=socket( AF_INET,SOCK_STREAM,IP proto _ TCP);
addr . sin _ family:=AF _ INET;
addr . sin _ port:=htons(5678);
addr.sin_addr。s _ addr:=htonl(in addr _ ANY);
bind( m_sock,@addr,sizeof(SOCKADDR));
WSAAsyncSelect( m_sock,Handle,WM_SOCKET,FD_ACCEPT或FD _ CLOSE);
听(m_sock,5);
.
应用程序可以分析接收到WM_SOCKET消息,以确定哪个套接字生成了网络事件和事件类型:
过程TfmMain。WM socket(var Msg:t message);
定义变量
袜子:TSocket
addr:TSockAddrIn;
addrlen:整数;
buf : Array [0.4095]的字符;
开始
//Msg的WParam是生成网络事件的套接字句柄,而LParam包含事件类型。
案例WSAGetSelectEvent(消息。LParam)的
FD _接受:
开始
addrlen:=sizeof(addr);
sock :=接受(Msg。WParam,addr,addrlen);
如果sock INVALID_SOCKET那么
WSAAsyncSelect( sock,Handle,WM_SOCKET,FD_READ或FD_WRITE或FD _ CLOSE);
结束;
FD_CLOSE : closesocket( Msg。WParam);
FD_READ : recv( Msg。WParam,buf[0],4096,0);
FD _ WRITE:
结束;
结束;
三:WSAEventSelect模型
后来,微软的信箱非常畅销,购买微软信箱的人以百万计数.以至于盖茨每天24小时给客户打电话,累得腰酸背痛,喝蚁力神都不好使~~~~~~
微软改进了他们的信箱:在客户的家中添加一个附加装置,这个装置会监视客户的信箱,每当新的信件来临,此装置会发出新信件到达声,提醒老陈去收信。盖茨终于可以睡觉了。
同样要使用线程:
过程听线程.执行;
定义变量
何发泄:wsa事件;
ret:整数;
ne:TWSANetworkEvents;
袜子:TSocket
ADR:TSockAddrIn;
sMsg:String;
索引,
事件总数:DWORD
EventArray:数组[0.WSAEVENT的WSA _ MAXIMUM _ WAIT _ EVENTS-1];
开始
.窝.约束.
he vent:=WSACreateEvent();
WSAEventSelect( ListenSock,hEvent,FD_ACCEPT或FD _ CLOSE);
.听.
而(未终止)做
开始
index:=WSAWaitForMultipleEvents(事件总数,@EventArray[0],FALSE,WSA_INFINITE,FALSE);
FillChar( ne,sizeof(ne),0);
WSAEnumNetworkEvents(sock数组[Index-WSA _ WAIT _ EVENT _ 0],事件数组[Index-WSA _ WAIT _ EVENT _ 0],@ ne);
如果(东北网络事件和FD _接受)为0,则
开始
如果ne.iErrorCode[FD_ACCEPT_BIT]为0,则
继续;
ret:=sizeof(ADR);
sock:=accept(SockArray[Index-WSA _ WAIT _ EVENT _ 0],adr,ret);
如果事件总数WSA _ MAXIMUM _ WAIT _ EVENTS-1,则//这里WSA _最大_等待_事件同样是64
开始
闭合插座(袜子);
继续;
结束;
he vent:=WSACreateEvent();
WSAEventSelect( sock,hEvent,FD_READ或FD_WRITE或FD _ CLOSE);
sockar ray[事件总数]:=sock;
事件数组[事件总数]:=事件;
公司(事件总数);
结束;
如果(东北网络事件和FD_READ)为0,则
开始
如果ne.iErrorCode[FD_READ_BIT]为0,则
继续;
FillChar( RecvBuf[0],PACK_SIZE_RECEIVE,0);
ret:=recv(SockArray[Index-WSA _ WAIT _ EVENT _ 0],RecvBuf[0],PACK_SIZE_RECEIVE,0);
.
结束;
结束;
结束;
四:重叠的输入输出事件通知模型
后来,微软通过调查发现,老陈不喜欢上下楼收发信件,因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术,只要用户告诉微软自己的家在几楼几号,新式信箱会把信件直接传送到用户的家中,然后告诉用户,你的信件已经放到你的家中了!老陈很高兴,因为他不必再亲自收发信件了!
重叠输入输出事件通知模型和事件选择模型在实现上非常相似,主要区别在重叠的,重叠的模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个网络编程接口输入输出请求。这些提交的请求完成后,应用程序会收到通知。什么意思呢?就是说,如果你想从窝上接收数据,只需要告诉系统,由系统为你接收数据,而你需要做的只是为系统提供一个缓冲区~~~~~
听线程和事件选择模型一模一样,接收/发送线程则完全不同:
TOverlapThread过程。执行;
定义变量
dw temp:DWORD;
ret:整数;
索引:DWORD
开始
.
而(未终止)做
开始
index:=WSAWaitForMultipleEvents(FLinks ."数数吧,"弗林克斯。事件[0],FALSE,RECV_TIME_OUT,FALSE);
Dec( Index,WSA _ WAIT _ EVENT _ 0);
如果索引WSA_MAXIMUM_WAIT_EVENTS-1则//超时或者其他错误
继续;
WSAResetEvent( FLinks .事件[索引]);
WSAGetOverlappedResult( FLinks .sockets[index],flinks.poverlaps[index],@dwtemp,false,flinks.pdwflags[index]^);
如果dwTemp=0,则//连接已经关闭
开始
.
继续;
结束其他
开始
fmMain .列表框1。项目。add(flinks.pbufs[index]^.buf);
结束;
//初始化缓冲区
flinks.pdwflags[index]^:=0;
填充char(flinks.poverlaps[index]^,sizeof(wsaooverlapped),0);
FLinks.pOverlaps[Index]^.hEvent :=FLinks .事件[索引];
菲尔查尔语(FLinks.pBufs[Index]^.buf^,buffer_size,0);
//递一个接收数据请求
WSARecv( FLinks .sockets[index],flinks.pbufs[index],1,flinks.pdwrecvd[index]^,flinks.pdwflags[index]^,flinks.poverlaps[index],nil);
结束;
结束;
五:重叠的输入输出完成例程模型
陈收到一封新信后,一般的程序是:打开信封——拿出信纸——读信——回信.为了进一步减轻用户的负担,微软开发了一项新技术:用户只需要告诉微软信件的操作步骤,微软邮箱就会按照这些步骤来处理信件,用户不再需要亲自打开/阅读/回复信件!陈终于过上了小资产阶级的生活!
重叠I/O完成例程要求用户提供一个回调函数,当新的网络事件发生时,该函数将由系统执行:
过程WorkerRoutine( const dwError,CB transfered:DWORD;常数
LP overlapped:lpwsaooverlapped;const dw flags:DWORD);stdcall
然后告诉系统使用WorkerRoutine函数来处理接收到的数据:
WSARecv( m_socket,@FBuf,1,dwTemp,dwFlag,@m_overlap,worker routine);
然后.没什么可遵循的,系统已经为你做好了一切!微软真体贴!
While(未终止)do//这是Recv/Send线程必须做的事情.没事干!
开始
如果SleepEx( RECV_TIME_OUT,True )=WAIT_IO_COMPLETION,则//
开始
;
结束else
开始
继续;
结束;
结束;
六:IOCP模式
微软邮箱好像很完美,老陈野很满意。但是在一些大公司,情况就完全不一样了!这些大公司有几万个邮箱,每秒钟需要处理几百封信,以至于微软邮箱经常因为超负荷而崩溃!需要重启!微软不得不打出杀手锏。
微软给每个大公司都发了一个名为‘完成端口’的超级机器人,让这个机器人处理那些信件!
Windows NT团队注意到这些应用程序的性能没有预期的高。特别是,处理许多并发的客户请求意味着许多线程同时在系统中运行。因为所有这些线程都是可运行的[而不是挂起并等待某些事情发生],所以微软意识到NT内核花了太多时间来转换运行线程的[上下文],线程没有获得大量CPU时间来完成工作。每个人可能还会觉得并行模型的瓶颈在于它为每个客户请求创建了一个新线程。创建一个线程比创建一个进程要便宜,但是它远不是免费的。我们来设想一下:如果提前打开N个线程,让它们在那里hold[ block],然后就可以把所有用户的请求发布到一个消息队列中。然后n个线程从消息队列中逐个取出消息并进行处理。您可以避免为每个用户请求启动一个线程。既减少了线程的资源,又提高了线程的利用率。理论上很好。微软怎么能不考虑我这种普通人能想出来的问题?-摘自nonocast的《理解I/O Completion Port》
让我们先来看看IOCP模式的实施情况:
//创建完成端口
FCompletPort:=CreateIoCompletionPort(INVALID _ HANDLE _ VALUE,0,0,0);
//接受远程连接,并将该连接的套接字句柄绑定到刚刚创建的IOCP。
AConnect :=accept( FListenSock,addr,len);
CreateIoCompletionPort(a connect,FCompletPort,nil,0);
//创建CPU数量*2的2个线程
对于i:=1到si.dwNumberOfProcessors * 2 2 do
开始
AThread :=TRecvSendThread。创建(假);
阿斯瑞德。CompletPort:=FCompletPort;//告诉这个线程你要去这个IOCP访问数据
结束;
好吧,就这么简单。我们要做的就是构建一个IOCP,将远程连接的socket句柄绑定到刚刚创建的IOCP上,最后创建N个线程,告诉这N个线程访问这个IOCP中的数据。
再看一下TRecvSendThread线程做了什么:
过程TRecvSendThread。执行;
定义变量
.
开始
而(不是自我。终止)do
开始
//查询IOCP状态(数据读写操作是否完成)
GetQueuedCompletionStatus(complet port,BytesTransd,CompletKey,POVERLAPPED(pPerIoDat),TIME _ OUT);
如果BytesTransd为0,则
.//数据读写操作完成
//发布另一个读取数据请求。
wsarecv(@(pperiodat^.康普莱基BufData),1,BytesRecv,标志,@(pPerIoDat^.重叠),无);
结束;
结束;
读写线程只是检查IOCP是否完成了我们提交的读写操作,如果是,就发送一个新的读写请求。
需要注意的是,我们创建的所有TRecvSendThread都在访问同一个IOCP(因为我们只创建了一个IOCP),而且我们没有使用临界区!不会有冲突吗?不用考虑同步吗?
呵呵,这就是IOCP P的秘密,IOCP不是普通对象,不用考虑线程安全。它会自动分配访问它的线程:如果一个线程A正在访问一个套接字,线程B的访问请求将被分配给另一个套接字。这些都是系统自动分配的,不用问了。