README.fdwatch
上传用户:tany51
上传日期:2013-06-12
资源大小:1397k
文件大小:8k
源码类别:

MySQL数据库

开发平台:

Visual C++

  1. PvPGN Abstract socket event notification API ie fdwatch
  2.     v.01 (C) 2003 Dizzy <dizzy@roedu.net>
  3. 1. The problem:
  4.     I wanted to design and implement a general API to have PvPGN inspect
  5. socket status (read/write availability) in the best possible way that the
  6. host OS may offer (select()/poll()/kqueue()/epoll()/etc..).
  7.     Also the old PvPGN code to do such things was doing them in a very 
  8. slow way, especially if the system was using poll() (which was the default
  9. with most Unices). When the server started to have lots of active connections 
  10. the CPU used in PvPGN to inspect and handle them was increasing very much (the
  11. code complexity of the code executed each main loop run was of O(n^2) complexity,
  12. where n is the number of connections to the server, and the main loop is cycling
  13. at least 1000/BNETD_POLL_INTERVAL times per second ie at least 50 times per second).
  14. 2. The fdwatch API:
  15.     I started by reading the fdwatch code from the thttpd project, I used the 
  16. ideeas found on that code as a start point, but I got much far from those :).
  17.     The fdwatch API is described in fdwatch.h as follows:
  18. extern int fdwatch_init(void);
  19. extern int fdwatch_close(void);
  20. extern int fdwatch_add_fd(int fd, t_fdwatch_type rw, fdwatch_handler h, void *data);
  21. extern int fdwatch_update_fd(int fd, t_fdwatch_type rw);
  22. extern int fdwatch_del_fd(int fd);
  23. extern int fdwatch(long timeout_msecs);
  24. extern int fdwatch_check_fd(int fd, t_fdwatch_type rw);
  25. extern void fdwatch_handle(void);
  26.     The name of the functions should be self explanatory to what those functions
  27. do.
  28. 3. The changed code flow:
  29. A. the code flow before fdwatch
  30.     - main() calls server_process() 
  31.     - server_process() after doing some single time initializations, entered
  32.     the main loop
  33.     - in the main loop after handing the events it starts to prepare the sockets
  34.     for select/poll
  35.     - it starts a loop cycling through each address configured in bnetd.conf 
  36.     to listen on them and adds their sockets to the socket inspection list
  37.     - after this, it does a O(n) cycle where it populates the socket inspection list
  38.     with the sockets of every t_connection the server has (read availability)
  39.     - if any of this t_connections have packets in the outqueue (they need to 
  40.     send data) then the sockets are also added for write availability
  41.     - then pvpgn calls select()/poll() on the socket inspection list
  42.     - after the syscall returns, pvpgn cycles through each address configured
  43.     in bnetd.conf and checks if they are read available (if a new connection 
  44.     is to be made)
  45.     - pvpgn doesnt want to accept new connections when in shutdown phase but 
  46.     it did it the wrong way: it completly ignored the listening sockets if 
  47.     in shutdown phase, this made that once a connection was pending while in 
  48.     shutdown phase, select/poll imediatly returns because it has it in the read 
  49.     availability list and thus pvpgn was using 99% cpu while in shutdown phase
  50.     - anyway, after this, pvpgn does a O(n) cycle through each t_connection to 
  51.     check if its socket is read/write available
  52.     - problem is that when it was using poll() (the common case on Unices) to 
  53.     check if a socket was returned as read/write available by poll() it was 
  54.     using another O(n) function thus making the total cycle of O(n^2)
  55.     - while cycling through each connection to inspect if its socket was 
  56.     returned available by select/poll , pvpgn also checks if the connection 
  57.     is in destroy state (conn_state_destroy) and if so it destroys it
  58. B. the code flow after fdwatch
  59.     - I have tried to get every bit of speed I could from the design, so some 
  60.     things while it may look complex they have the reason of speed behind
  61.     - just like the old code flow main calls server_process()
  62.     - here pvpgn does some single time initializations
  63.     - different than before, here, in the single time intializations code I also 
  64.     add the listening sockets to the fdwatch socket inspection list (also 
  65.     the code will need to update this list when receiving SIGHUP)
  66.     - then pvpgn enters main server loop
  67.     - the code first treats any received events (just like the old code)
  68.     - then it calls fdwatch() to inspect the sockets state
  69.     - then it calls conn_reap() to destroy the conn_state_destroy connections
  70.     - then it calls fdwatch_handle() to cycle through each ready socket and handle
  71.     its changed status
  72. This is it! :)
  73. No cycles, no O(n^2), not even a O(n) there (well in fact there is something 
  74. similar to a O(n) inside fdwatch() but about that read bellow).
  75. FAQ:
  76. 1. Q: but where do the new connections get into the fdwatch inspection 
  77. list ? 
  78. A: they get in there when they are created, that means in the 
  79. function sd_accept() from server.c
  80. the reason is: why add the connection sockets each time before poll() when the 
  81. event of having a new connection, so a new socket to inspect is very very rare 
  82. compared to the number of times we call select/poll).
  83. 2. Q: where are the connections removed from the fdwatch inspection list ?
  84. A: where they should be, in conn_destroy() just before calling close() on the 
  85. socket
  86. 3. Q: where do we manifest our interest for write availability of a socket if 
  87. we have data to send to it ?
  88. A: in conn_push_outuque. the ideea is if we got data to send, ok update fdwatch 
  89. socket inspection list to look for write availability of the socket where we 
  90. need to send data
  91. 4. Q: what does fdwatch() do ?
  92. A: depending on the chosen backend it calls select or poll, or kqueue etc...
  93. For some backends it has to do some work before calling the syscall. Ex. for 
  94. select() and poll() it needs to copy from a template list of sockets to inspect 
  95. to the actual working set. The reason why depends on the backend but it really is
  96. a limitation of the how the syscall works and there is nothing pvpgn that can be
  97. made to not do that. For example in the poll backend, one might argue that 
  98. instead of updating a template and copy it to a working array before each poll(), 
  99. we should update the working set. But that also means that before calling poll(),
  100. we must set all "revents" field of each fd_struct to 0 , and my tests show that 
  101. a cycle through 1000 elements of poll fd structs setting revents to 0 is 5 times 
  102. slower than using a memcpy() to copy the whole array from a source.
  103. 5. Q: what does conn_reap() do ?
  104. A: to get the maximum from each possible backend (kqueue was the main reason here)
  105.  I moved the cycling through each ready socket and calling the handling function 
  106. for it, outside server.c and inside fdwatch backends. Because the old code used 
  107. that cycle from server.c to also check if connections are dead and need destroyed 
  108. I had to find another way to do it. The best way I found was to have in connection.c
  109. besides the connlist, another list, conn_dead, which will contain the current
  110. connections which have the state set to conn_set_state. Then conn_reap() just 
  111. cycles through each element of conn_dead and destroys them. This was the fastest 
  112. solution I could find out.
  113. 6. Q: what does fdwatch_handle() do ?
  114. A: it calls the backend's handle function. To get the max from each backend I 
  115. had to move the handling cycle as a backend specific function. In general this 
  116. functions cycle through each socket which was returned ready by the last 
  117. fdwatch() call, and calls the handler function (which was set when the socket 
  118. was added to the socket inspection list) giving it arguments a void * parameter 
  119. (also set when socket was added to the inspection list), and the type of readiness
  120. (read/write). Currently, pvpgn has 3 possible handlers: handle_tcp, handle_udp 
  121. and handle_accept. Each of this calls acordingly sd_accept, sd_tcpinput, 
  122. sd_tcpoutput, sd_udpinput (UDP sends are done directly, not queueing them and 
  123. checking for socket readiness to write, maybe this is another bug ?)