在select_modwatch中调用WSAAsyncSelect注册窗口消息,然后再消息回调函数select_wndproc直接返回0,这样消息回调就不会处理这些消息。进而可以在select_waitevent
中通过调用 ::GetMessage获取要处理的消息。
注意 :在select_waitevent一旦有消息处理,则调用WSAAsyncSelect(req->er_handle, sMsgWindow, 0, 0)停止获取消息。等待select_modwatch再次被调用后才能继续获取消息 。
class Socket : public EventContext,在构造函数中,this->SetTask(notifytask);设置了任务task,从而可以再将来进行传信(signal)操作
Socket::Socket(Task *notifytask, UInt32 inSocketType)
: EventContext(EventContext::kInvalidFileDesc, sEventThread),
fState(inSocketType),
fLocalAddrStrPtr(NULL),
fLocalDNSStrPtr(NULL),
fPortStr(fPortBuffer, kPortBufSizeInBytes)
{
fLocalAddr.sin_addr.s_addr = 0;
fLocalAddr.sin_port = 0;
fDestAddr.sin_addr.s_addr = 0;
fDestAddr.sin_port = 0;
this->SetTask(notifytask);
#if SOCKET_DEBUG
fLocalAddrStr.Set(fLocalAddrBuffer,sizeof(fLocalAddrBuffer));
#endif
}
EventThread类都是私有变量,所以不会独立创建该对象。他的一个友元类是EventContext,而class Socket : public EventContext派生类。在Socket 的Initialize函数中,创建了 sEventThread = new EventThread(),函数StartThread负责启动线程。
注意:Socket::Initialize 是静态成员函数,因此所有派生于Socket的对象都公用一个线程,也就是说,EventThread线程监听所有socket对象发来的消息。
class Socket : public EventContext
{
public:
enum
{
// Pass this in on socket constructors to specify whether the
// socket should be non-blocking or blocking
kNonBlockingSocketType = 1
};
// This class provides a global event thread.
static void Initialize() { sEventThread = new EventThread(); }
static void StartThread() { sEventThread->Start(); }
static EventThread* GetEventThread() { return sEventThread; }
而在 void EventThread::Entry()内部,会不断的监听所有在fRefTable变量中保存的套接字关联的窗口,是否有消息发生。如果有消息发生,则立即获取并调用
fTask->Signal(Task::kReadEvent),将task任务放到线程池中。
void EventThread::Entry()
{
struct eventreq theCurrentEvent;
::memset( &theCurrentEvent, '\0', sizeof(theCurrentEvent) );
while (true)
{
int theErrno = EINTR;
while (theErrno == EINTR)
{
#if MACOSXEVENTQUEUE
int theReturnValue = waitevent(&theCurrentEvent, NULL);
#else
int theReturnValue = select_waitevent(&theCurrentEvent, NULL);
#endif
//Sort of a hack. In the POSIX version of the server, waitevent can return
//an actual POSIX errorcode.
if (theReturnValue >= 0)
theErrno = theReturnValue;
else
theErrno = OSThread::GetErrno();
}
AssertV(theErrno == 0, theErrno);
//ok, there's data waiting on this socket. Send a wakeup.
if (theCurrentEvent.er_data != NULL)
{
//The cookie in this event is an ObjectID. Resolve that objectID into
//a pointer.
StrPtrLen idStr((char*)&theCurrentEvent.er_data, sizeof(theCurrentEvent.er_data));
OSRef* ref = fRefTable.Resolve(&idStr);
if (ref != NULL)
{
EventContext* theContext = (EventContext*)ref->GetObject();
#if DEBUG
theContext->fModwatched = false;
#endif
theContext->ProcessEvent(theCurrentEvent.er_eventbits);
fRefTable.Release(ref);
}
}
在EventContext::RequestEvent中,调用select_watchevent注册窗口消息,同时将消息id注册到线程的fRefTable中。
fRef.Set(fUniqueIDStr, this);
fEventThread->fRefTable.Register(&fRef);
在线程的void EventThread::Entry()中,不断的调用select_waitevent获取消息,在收到消息后,使其引用加1.
StrPtrLen idStr((char*)&theCurrentEvent.er_data, sizeof(theCurrentEvent.er_data));
OSRef* ref = fRefTable.Resolve(&idStr);
并调用 EventContext* theContext = (EventContext*)ref->GetObject();
通过ProcessEvent对相关消息进行处理。
theContext->ProcessEvent(theCurrentEvent.er_eventbits);
其内部调用fTask->Signal(Task::kReadEvent)对消息进行处理。
注意:EventContext内部有成员变量:Task* fTask;
处理完后,调用Release使引用减1.
fRefTable.Release(ref);
struct eventreq {
int er_type;
#define EV_FD 1 // file descriptor
int er_handle; //socket 句柄
void *er_data; //消息id
int er_rcnt;
int er_wcnt;
int er_ecnt;
int er_eventbits; //事件位EV_RE|EV_WR|EV_EX|EV_RM
#define EV_RE 1
#define EV_WR 2
#define EV_EX 4
#define EV_RM 8
};
//
// You have to create a window to get socket events? What's up with that?
static HWND sMsgWindow = NULL;
//
LRESULT CALLBACK select_wndproc(HWND inWIndow, UINT inMsg, WPARAM inParam, LPARAM inOtherParam);
int select_watchevent(struct eventreq *req, int which)
{
return select_modwatch(req, which);
}
int select_modwatch(struct eventreq *req, int which)
{
//
// If our WSAAsyncSelect window is not constructed yet, wait
// until it is construected. The window gets constructed when the server
// is done starting up, so this should only happen when select_modwatch
// is being called as the server is starting up.
while (sMsgWindow == NULL)
OSThread::Sleep(10);
// Convert EV_RE and EV_WR to the proper WSA event codes.
// WSA event codes are more specific than what POSIX provides, so
// just wait on any kind of read related event for EV_RE, same for EV_WR
long theEvent = 0;
if (which & EV_RE)
theEvent |= FD_READ | FD_ACCEPT | FD_CLOSE;
if (which & EV_WR)
theEvent |= FD_WRITE | FD_CONNECT;
// This is a little bit of a hack, because we are assuming that the caller
// is actually putting a UInt32 in the void*, not a void*, and we are also
// assuming caller is not using the 0 - WM_USER range of values, but
// both of these things are true in the EventContext.cpp code, and this
// mechanism of passing around cookies is just too convienent to ignore.
unsigned int theMsg = (unsigned int)(req->er_data);
return ::WSAAsyncSelect(req->er_handle, sMsgWindow, theMsg, theEvent);
}
int select_waitevent(struct eventreq *req, void* /*onlyForMacOSX*/)
{
if (sMsgWindow == NULL)
{
//
// This is the first time we've called this function. Do our
// window initialization now.
// We basically just want the simplest window possible.
WNDCLASSEX theWndClass;
theWndClass.cbSize = sizeof(theWndClass);
theWndClass.style = 0;
theWndClass.lpfnWndProc = &select_wndproc;
theWndClass.cbClsExtra = 0;
theWndClass.cbWndExtra = 0;
theWndClass.hInstance = NULL;
theWndClass.hIcon = NULL;
theWndClass.hCursor = NULL;
theWndClass.hbrBackground = NULL;
theWndClass.lpszMenuName = NULL;
theWndClass.lpszClassName = "DarwinStreamingServerWindow";
theWndClass.hIconSm = NULL;
ATOM theWndAtom = ::RegisterClassEx(&theWndClass);
Assert(theWndAtom != NULL);
if (theWndAtom == NULL)
::exit(-1); // Poor error recovery, but this should never happen.
sMsgWindow = ::CreateWindow( "DarwinStreamingServerWindow", // Window class name
"DarwinStreamingServerWindow", // Window title bar
WS_POPUP, // Window style ( a popup doesn't need a parent )
0, // x pos
0, // y pos
CW_USEDEFAULT, // default width
CW_USEDEFAULT, // default height
NULL, // No parent
NULL, // No menu handle
NULL, // Ignored on WinNT
NULL); // data for message proc. Who cares?
Assert(sMsgWindow != NULL);
if (sMsgWindow == NULL)
::exit(-1);
}
MSG theMessage;
//
// Get a message for my goofy window. 0, 0 indicates that we
// want any message for that window.
//
// Convienently, this function blocks until there is a message, so it works
// much like waitevent would on Mac OS X.
UInt32 theErr = ::GetMessage(&theMessage, sMsgWindow, 0, 0);
if (theErr > 0)
{
UInt32 theSelectErr = WSAGETSELECTERROR(theMessage.lParam);
UInt32 theEvent = WSAGETSELECTEVENT(theMessage.lParam);
req->er_handle = theMessage.wParam; // the wParam is the FD
req->er_eventbits = EV_RE; // WSA events & socket events don't map...
// but the server state machines never care
// what the event is anyway.
// we use the message # as our way of passing around the user data.
req->er_data = (void*)(theMessage.message);
//
// We should prevent this socket from getting events until modwatch is called.
(void)::WSAAsyncSelect(req->er_handle, sMsgWindow, 0, 0);
return 0;
}
else
{
//
// Do we ever get WM_QUIT messages? Can there ever be an error?
Assert(0);
return EINTR;
}
}
LRESULT CALLBACK select_wndproc(HWND /*inWIndow*/, UINT inMsg, WPARAM /*inParam*/, LPARAM /*inOtherParam*/)
{
// If we don't return true for this message, window creation will not proceed
if (inMsg == WM_NCCREATE)
return TRUE;
// All other messages we can ignore and return 0
//消息返回值为0,则表示所有的消息都没有被处理,可在select_waitevent中调用GetMessage获取消息
return 0;
}
/*
*
* @APPLE_LICENSE_HEADER_START@
*
* Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*
*/
/*
File: EventContext.h
Contains: An event context provides the intelligence to take an event
generated from a UNIX file descriptor (usually EV_RE or EV_WR)
and signal a Task.
*/
#ifndef __EVENT_CONTEXT_H__
#define __EVENT_CONTEXT_H__
#include "OSThread.h"
#include "Task.h"
#include "OSRef.h"
#include "ev.h"
//enable to trace event context execution and the task associated with the context
#define EVENTCONTEXT_DEBUG 0
class EventThread;
class EventContext
{
public:
//
// Constructor. Pass in the EventThread you would like to receive
// events for this context, and the fd that this context applies to
EventContext(int inFileDesc, EventThread* inThread);
virtual ~EventContext() { if (fAutoCleanup) this->Cleanup(); }
//
// InitNonBlocking
//
// Sets inFileDesc to be non-blocking. Once this is called, the
// EventContext object "owns" the file descriptor, and will close it
// when Cleanup is called. This is necessary because of some weird
// select() behavior. DON'T CALL CLOSE ON THE FD ONCE THIS IS CALLED!!!!
void InitNonBlocking(int inFileDesc);
//
// Cleanup. Will be called by the destructor, but can be called earlier
void Cleanup();
//
// Arms this EventContext. Pass in the events you would like to receive
void RequestEvent(int theMask = EV_RE);
//r
// Provide the task you would like to be notified
void SetTask(Task* inTask)
{
fTask = inTask;
if (EVENTCONTEXT_DEBUG)
{
if (fTask== NULL)
qtss_printf("EventContext::SetTask context=%lu task= NULL\n", (UInt32) this);
else
qtss_printf("EventContext::SetTask context=%lu task= %lu name=%s\n",(UInt32) this,(UInt32) fTask, fTask->fTaskName);
}
}
// when the HTTP Proxy tunnels takes over a TCPSocket, we need to maintain this context too
void SnarfEventContext( EventContext &fromContext );
// Don't cleanup this socket automatically
void DontAutoCleanup() { fAutoCleanup = false; }
// Direct access to the FD is not recommended, but is needed for modules
// that want to use the Socket classes and need to request events on the fd.
int GetSocketFD() { return fFileDesc; }
enum
{
kInvalidFileDesc = -1 //int
};
protected:
//
// ProcessEvent
//
// When an event occurs on this file descriptor, this function
// will get called. Default behavior is to Signal the associated
// task, but that behavior may be altered / overridden.
//
// Currently, we always generate a Task::kReadEvent
virtual void ProcessEvent(int /*eventBits*/)
{
if (EVENTCONTEXT_DEBUG)
{
if (fTask== NULL)
qtss_printf("EventContext::ProcessEvent context=%lu task=NULL\n",(UInt32) this);
else
qtss_printf("EventContext::ProcessEvent context=%lu task=%lu TaskName=%s\n",(UInt32)this,(UInt32) fTask, fTask->fTaskName);
}
if (fTask != NULL)
fTask->Signal(Task::kReadEvent);
}
int fFileDesc;
private:
struct eventreq fEventReq;
OSRef fRef;
PointerSizedInt fUniqueID;
StrPtrLen fUniqueIDStr;
EventThread* fEventThread;
Bool16 fWatchEventCalled;
int fEventBits;
Bool16 fAutoCleanup;
Task* fTask;
#if DEBUG
Bool16 fModwatched;
#endif
static unsigned int sUniqueID;
friend class EventThread;
};
class EventThread : public OSThread
{
public:
EventThread() : OSThread() {}
virtual ~EventThread() {}
private:
virtual void Entry();
OSRefTable fRefTable;
friend class EventContext;
};
#endif //__EVENT_CONTEXT_H__
/*
*
* @APPLE_LICENSE_HEADER_START@
*
* Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*
*/
/*
File: EventContext.cpp
Contains: Impelments object in .h file
*/
#include "EventContext.h"
#include "OSThread.h"
#include "atomic.h"
#include <fcntl.h>
#include <errno.h>
#ifndef __Win32__
#include <unistd.h>
#endif
#if MACOSXEVENTQUEUE
#include "tempcalls.h" //includes MacOS X prototypes of event queue functions
#endif
#define EVENT_CONTEXT_DEBUG 0
#if EVENT_CONTEXT_DEBUG
#include "OS.h"
#endif
#ifdef __Win32__
unsigned int EventContext::sUniqueID = WM_USER; // See commentary in RequestEvent
#else
unsigned int EventContext::sUniqueID = 1;
#endif
EventContext::EventContext(int inFileDesc, EventThread* inThread)
: fFileDesc(inFileDesc),
fUniqueID(0),
fUniqueIDStr((char*)&fUniqueID, sizeof(fUniqueID)),
fEventThread(inThread),
fWatchEventCalled(false),
fAutoCleanup(true)
{}
void EventContext::InitNonBlocking(int inFileDesc)
{
fFileDesc = inFileDesc;
#ifdef __Win32__
u_long one = 1;
int err = ::ioctlsocket(fFileDesc, FIONBIO, &one);
#else
int flag = ::fcntl(fFileDesc, F_GETFL, 0);
int err = ::fcntl(fFileDesc, F_SETFL, flag | O_NONBLOCK);
#endif
AssertV(err == 0, OSThread::GetErrno());
}
void EventContext::Cleanup()
{
int err = 0;
if (fFileDesc != kInvalidFileDesc)
{
//if this object is registered in the table, unregister it now
if (fUniqueID > 0)
{
fEventThread->fRefTable.UnRegister(&fRef);
#if !MACOSXEVENTQUEUE
select_removeevent(fFileDesc);//The eventqueue / select shim requires this
#ifdef __Win32__
err = ::closesocket(fFileDesc);
#endif
#else
//On Linux (possibly other UNIX implementations) you MUST NOT close the fd before
//removing the fd from the select mask, and having the select function wake up
//to register this fact. If you close the fd first, bad things may happen, like
//the socket not getting unbound from the port & IP addr.
//
//So, what we do is have the select thread itself call close. This is triggered
//by calling removeevent.
err = ::close(fFileDesc);
#endif
}
else
#ifdef __Win32__
err = ::closesocket(fFileDesc);
#else
err = ::close(fFileDesc);
#endif
}
fFileDesc = kInvalidFileDesc;
fUniqueID = 0;
AssertV(err == 0, OSThread::GetErrno());//we don't really care if there was an error, but it's nice to know
}
void EventContext::SnarfEventContext( EventContext &fromContext )
{
//+ show that we called watchevent
// copy the unique id
// set our fUniqueIDStr to the unique id
// copy the eventreq
// find the old event object
// show us as the object in the fRefTable
// we take the OSRef from the old context, point it at our context
//
//TODO - this whole operation causes a race condition for Event posting
// way up the chain we need to disable event posting
// or copy the posted events afer this op completes
fromContext.fFileDesc = kInvalidFileDesc;
fWatchEventCalled = fromContext.fWatchEventCalled;
fUniqueID = fromContext.fUniqueID;
fUniqueIDStr.Set((char*)&fUniqueID, sizeof(fUniqueID)),
::memcpy( &fEventReq, &fromContext.fEventReq, sizeof( struct eventreq ) );
fRef.Set( fUniqueIDStr, this );
fEventThread->fRefTable.Swap(&fRef);
fEventThread->fRefTable.UnRegister(&fromContext.fRef);
}
void EventContext::RequestEvent(int theMask)
{
#if DEBUG
fModwatched = true;
#endif
//
// The first time this function gets called, we're supposed to
// call watchevent. Each subsequent time, call modwatch. That's
// the way the MacOS X event queue works.
if (fWatchEventCalled)
{
fEventReq.er_eventbits = theMask;
#if MACOSXEVENTQUEUE
if (modwatch(&fEventReq, theMask) != 0)
#else
if (select_modwatch(&fEventReq, theMask) != 0)
#endif
AssertV(false, OSThread::GetErrno());
}
else
{
//allocate a Unique ID for this socket, and add it to the ref table
#ifdef __Win32__
//
// Kind of a hack. On Win32, the way that we pass around the unique ID is
// by making it the message ID of our Win32 message (see win32ev.cpp).
// Messages must be >= WM_USER. Hence this code to restrict the numberspace
// of our UniqueIDs.
if (!compare_and_store(8192, WM_USER, &sUniqueID)) // Fix 2466667: message IDs above a
fUniqueID = (PointerSizedInt)atomic_add(&sUniqueID, 1); // level are ignored, so wrap at 8192
else
fUniqueID = (PointerSizedInt)WM_USER;
#else
if (!compare_and_store(10000000, 1, &sUniqueID))
fUniqueID = (PointerSizedInt)atomic_add(&sUniqueID, 1);
else
fUniqueID = 1;
#endif
fRef.Set(fUniqueIDStr, this);
fEventThread->fRefTable.Register(&fRef);
//fill out the eventreq data structure
::memset( &fEventReq, '\0', sizeof(fEventReq));
fEventReq.er_type = EV_FD;
fEventReq.er_handle = fFileDesc;
fEventReq.er_eventbits = theMask;
fEventReq.er_data = (void*)fUniqueID;
fWatchEventCalled = true;
#if MACOSXEVENTQUEUE
if (watchevent(&fEventReq, theMask) != 0)
#else
if (select_watchevent(&fEventReq, theMask) != 0)
#endif
//this should never fail, but if it does, cleanup.
AssertV(false, OSThread::GetErrno());
}
}
void EventThread::Entry()
{
struct eventreq theCurrentEvent;
::memset( &theCurrentEvent, '\0', sizeof(theCurrentEvent) );
while (true)
{
int theErrno = EINTR;
while (theErrno == EINTR)
{
#if MACOSXEVENTQUEUE
int theReturnValue = waitevent(&theCurrentEvent, NULL);
#else
int theReturnValue = select_waitevent(&theCurrentEvent, NULL);
#endif
//Sort of a hack. In the POSIX version of the server, waitevent can return
//an actual POSIX errorcode.
if (theReturnValue >= 0)
theErrno = theReturnValue;
else
theErrno = OSThread::GetErrno();
}
AssertV(theErrno == 0, theErrno);
//ok, there's data waiting on this socket. Send a wakeup.
if (theCurrentEvent.er_data != NULL)
{
//The cookie in this event is an ObjectID. Resolve that objectID into
//a pointer.
StrPtrLen idStr((char*)&theCurrentEvent.er_data, sizeof(theCurrentEvent.er_data));
OSRef* ref = fRefTable.Resolve(&idStr);
if (ref != NULL)
{
EventContext* theContext = (EventContext*)ref->GetObject();
#if DEBUG
theContext->fModwatched = false;
#endif
theContext->ProcessEvent(theCurrentEvent.er_eventbits);
fRefTable.Release(ref);
}
}
#if EVENT_CONTEXT_DEBUG
SInt64 yieldStart = OS::Milliseconds();
#endif
this->ThreadYield();
#if EVENT_CONTEXT_DEBUG
SInt64 yieldDur = OS::Milliseconds() - yieldStart;
static SInt64 numZeroYields;
if ( yieldDur > 1 )
{
qtss_printf( "EventThread time in OSTHread::Yield %i, numZeroYields %i\n", (long)yieldDur, (long)numZeroYields );
numZeroYields = 0;
}
else
numZeroYields++;
#endif
}
}