ConnectionRepository.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. from PandaModules import *
  2. import Task
  3. import DirectNotifyGlobal
  4. import DirectObject
  5. from PyDatagram import PyDatagram
  6. import types
  7. class ConnectionRepository(DirectObject.DirectObject, CConnectionRepository):
  8. """
  9. This is a base class for things that know how to establish a
  10. connection (and exchange datagrams) with a gameserver. This
  11. includes ClientRepository and AIRepository.
  12. """
  13. notify = DirectNotifyGlobal.directNotify.newCategory("ConnectionRepository")
  14. taskPriority = -30
  15. def __init__(self, config):
  16. DirectObject.DirectObject.__init__(self)
  17. CConnectionRepository.__init__(self)
  18. self.config = config
  19. # Set this to 'http' to establish a connection to the server
  20. # using the HTTPClient interface, which ultimately uses the
  21. # OpenSSL socket library (even though SSL is not involved).
  22. # This is not as robust a socket library as NSPR's, but the
  23. # HTTPClient interface does a good job of negotiating the
  24. # connection over an HTTP proxy if one is in use.
  25. # Set it to 'nspr' to use Panda's net interface
  26. # (e.g. QueuedConnectionManager, etc.) to establish the
  27. # connection, which ultimately uses the NSPR socket library.
  28. # This is a much better socket library, but it may be more
  29. # than you need for most applications; and there is no support
  30. # for proxies.
  31. # Set it to 'default' to use the HTTPClient interface if a
  32. # proxy is in place, but the NSPR interface if we don't have a
  33. # proxy.
  34. self.connectMethod = self.config.GetString('connect-method', 'default')
  35. self.connectHttp = None
  36. self.http = None
  37. self.recorder = None
  38. def readDCFile(self, dcFileNames = None):
  39. """ Reads in the dc files listed in dcFileNames, or if
  40. dcFileNames is None, reads in all of the dc files listed in
  41. the Configrc file.
  42. The resulting DCFile object is stored in self.dcFile. """
  43. self.dcFile = DCFile()
  44. self.dclassesByName = {}
  45. self.dclassesByNumber = {}
  46. dcImports = {}
  47. if dcFileNames == None:
  48. readResult = self.dcFile.readAll()
  49. if not readResult:
  50. self.notify.error("Could not read dc file.")
  51. else:
  52. for dcFileName in dcFileNames:
  53. readResult = self.dcFile.read(Filename(dcFileName))
  54. if not readResult:
  55. self.notify.error("Could not read dc file: %s" % (dcFileName))
  56. self.hashVal = self.dcFile.getHash()
  57. # Now import all of the modules required by the DC file.
  58. for n in range(self.dcFile.getNumImportModules()):
  59. moduleName = self.dcFile.getImportModule(n)
  60. moduleName = self.mangleDCName(moduleName)
  61. module = __import__(moduleName, globals(), locals())
  62. if self.dcFile.getNumImportSymbols(n) > 0:
  63. # "from moduleName import symbolName, symbolName, ..."
  64. # Copy just the named symbols into the dictionary.
  65. for i in range(self.dcFile.getNumImportSymbols(n)):
  66. symbolName = self.dcFile.getImportSymbol(n, i)
  67. if symbolName == '*':
  68. # Get all symbols.
  69. dcImports.update(module.__dict__)
  70. else:
  71. mangledName = self.mangleName(symbolName)
  72. gotAny = 0
  73. if hasattr(module, symbolName):
  74. dcImports[symbolName] = getattr(module, symbolName)
  75. gotAny = 1
  76. if hasattr(module, mangledName):
  77. dcImports[mangledName] = getattr(module, mangledName)
  78. gotAny = 1
  79. if not gotAny:
  80. self.notify.error("Symbol %s not found in module %s." % (
  81. symbolName, moduleName))
  82. else:
  83. # "import moduleName"
  84. # Copy the module itself into the dictionary.
  85. dcImports[moduleName] = module
  86. # Now get the class definition for the classes named in the DC
  87. # file.
  88. for i in range(self.dcFile.getNumClasses()):
  89. dclass = self.dcFile.getClass(i)
  90. number = dclass.getNumber()
  91. className = dclass.getName()
  92. className = self.mangleDCName(className)
  93. # Does the class have a definition defined in the newly
  94. # imported namespace?
  95. classDef = dcImports.get(className)
  96. if classDef == None:
  97. self.notify.info("No class definition for %s." % (className))
  98. else:
  99. if type(classDef) == types.ModuleType:
  100. if not hasattr(classDef, className):
  101. self.notify.error("Module %s does not define class %s." % (className, className))
  102. classDef = getattr(classDef, className)
  103. if type(classDef) != types.ClassType:
  104. self.notify.error("Symbol %s is not a class name." % (className))
  105. else:
  106. dclass.setClassDef(classDef)
  107. self.dclassesByName[className] = dclass
  108. self.dclassesByNumber[number] = dclass
  109. def mangleDCName(self, name):
  110. """ This is provided as a hook so that derived classes
  111. (e.g. the AIRepository) can rename symbols from the .dc file
  112. according to the conventions associated with this particular
  113. repository (e.g., an AIRepository appends 'AI' to class and
  114. module names)."""
  115. return name
  116. def connect(self, serverList,
  117. successCallback = None, successArgs = [],
  118. failureCallback = None, failureArgs = []):
  119. """
  120. Attempts to establish a connection to the server. May return
  121. before the connection is established. The two callbacks
  122. represent the two functions to call (and their arguments) on
  123. success or failure, respectively. The failure callback also
  124. gets one additional parameter, which will be passed in first:
  125. the return status code giving reason for failure, if it is
  126. known.
  127. """
  128. ## if self.recorder and self.recorder.isPlaying():
  129. ## # If we have a recorder and it's already in playback mode,
  130. ## # don't actually attempt to connect to a gameserver since
  131. ## # we don't need to. Just let it play back the data.
  132. ## self.notify.info("Not connecting to gameserver; using playback data instead.")
  133. ## self.connectHttp = 1
  134. ## self.tcpConn = SocketStreamRecorder()
  135. ## self.recorder.addRecorder('gameserver', self.tcpConn)
  136. ## self.startReaderPollTask()
  137. ## if successCallback:
  138. ## successCallback(*successArgs)
  139. ## return
  140. hasProxy = 0
  141. if self.checkHttp():
  142. proxies = self.http.getProxiesForUrl(serverList[0])
  143. hasProxy = (proxies != 'DIRECT')
  144. if hasProxy:
  145. self.notify.info("Connecting to gameserver via proxy list: %s" % (proxies))
  146. else:
  147. self.notify.info("Connecting to gameserver directly (no proxy).");
  148. if self.connectMethod == 'http':
  149. self.connectHttp = 1
  150. elif self.connectMethod == 'nspr':
  151. self.connectHttp = 0
  152. else:
  153. self.connectHttp = (hasProxy or serverList[0].isSsl())
  154. self.bootedIndex = None
  155. self.bootedText = None
  156. if self.connectHttp:
  157. # In the HTTP case, we can't just iterate through the list
  158. # of servers, because each server attempt requires
  159. # spawning a request and then coming back later to check
  160. # the success or failure. Instead, we start the ball
  161. # rolling by calling the connect callback, which will call
  162. # itself repeatedly until we establish a connection (or
  163. # run out of servers).
  164. ch = self.http.makeChannel(0)
  165. self.httpConnectCallback(ch, serverList, 0,
  166. successCallback, successArgs,
  167. failureCallback, failureArgs)
  168. else:
  169. # Try each of the servers in turn.
  170. for url in serverList:
  171. self.notify.info("Connecting to %s via NSPR interface." % (url.cStr()))
  172. if self.tryConnectNspr(url):
  173. self.startReaderPollTask()
  174. if successCallback:
  175. successCallback(*successArgs)
  176. return
  177. # Failed to connect.
  178. if failureCallback:
  179. failureCallback(0, '', *failureArgs)
  180. def disconnect(self):
  181. """Closes the previously-established connection.
  182. """
  183. self.notify.info("Closing connection to server.")
  184. CConnectionRepository.disconnect(self)
  185. self.stopReaderPollTask()
  186. def httpConnectCallback(self, ch, serverList, serverIndex,
  187. successCallback, successArgs,
  188. failureCallback, failureArgs):
  189. if ch.isConnectionReady():
  190. self.setConnectionHttp(ch)
  191. ## if self.recorder:
  192. ## # If we have a recorder, we wrap the connect inside a
  193. ## # SocketStreamRecorder, which will trap incoming data
  194. ## # when the recorder is set to record mode. (It will
  195. ## # also play back data when the recorder is in playback
  196. ## # mode, but in that case we never get this far in the
  197. ## # code, since we just create an empty
  198. ## # SocketStreamRecorder without actually connecting to
  199. ## # the gameserver.)
  200. ## stream = SocketStreamRecorder(self.tcpConn, 1)
  201. ## self.recorder.addRecorder('gameserver', stream)
  202. ## # In this case, we pass ownership of the original
  203. ## # connection to the SocketStreamRecorder object.
  204. ## self.tcpConn.userManagesMemory = 0
  205. ## self.tcpConn = stream
  206. self.startReaderPollTask()
  207. if successCallback:
  208. successCallback(*successArgs)
  209. elif serverIndex < len(serverList):
  210. # No connection yet, but keep trying.
  211. url = serverList[serverIndex]
  212. self.notify.info("Connecting to %s via HTTP interface." % (url.cStr()))
  213. ch.preserveStatus()
  214. ch.beginConnectTo(DocumentSpec(url))
  215. ch.spawnTask(name = 'connect-to-server',
  216. callback = self.httpConnectCallback,
  217. extraArgs = [ch, serverList, serverIndex + 1,
  218. successCallback, successArgs,
  219. failureCallback, failureArgs])
  220. else:
  221. # No more servers to try; we have to give up now.
  222. if failureCallback:
  223. failureCallback(ch.getStatusCode(), ch.getStatusString(),
  224. *failureArgs)
  225. def checkHttp(self):
  226. # Creates an HTTPClient, if possible, if we don't have one
  227. # already. This might fail if the OpenSSL library isn't
  228. # available. Returns the HTTPClient (also self.http), or None
  229. # if not set.
  230. if self.http == None:
  231. try:
  232. self.http = HTTPClient()
  233. except:
  234. pass
  235. return self.http
  236. def startReaderPollTask(self):
  237. # Stop any tasks we are running now
  238. self.stopReaderPollTask()
  239. taskMgr.add(self.readerPollUntilEmpty, "readerPollTask",
  240. priority = self.taskPriority)
  241. def stopReaderPollTask(self):
  242. taskMgr.remove("readerPollTask")
  243. def readerPollUntilEmpty(self, task):
  244. while self.readerPollOnce():
  245. pass
  246. return Task.cont
  247. def readerPollOnce(self):
  248. if self.checkDatagram():
  249. dg = PyDatagram()
  250. self.getDatagram(dg)
  251. self.handleDatagram(dg)
  252. return 1
  253. # Unable to receive a datagram: did we lose the connection?
  254. if not self.isConnected():
  255. self.stopReaderPollTask()
  256. self.lostConnection()
  257. return 0
  258. def lostConnection(self):
  259. # This should be overrided by a derived class to handle an
  260. # unexpectedly lost connection to the gameserver.
  261. self.notify.warning("Lost connection to gameserver.")
  262. def handleDatagram(self, datagram):
  263. # This class is meant to be pure virtual, and any classes that
  264. # inherit from it need to make their own handleDatagram method
  265. pass
  266. def send(self, datagram):
  267. self.sendDatagram(datagram)
  268. # debugging funcs for simulating a network-plug-pull
  269. def pullNetworkPlug(self):
  270. self.notify.warning('*** SIMULATING A NETWORK-PLUG-PULL ***')
  271. self.setSimulatedDisconnect(1)
  272. def networkPlugPulled(self):
  273. return self.getSimulatedDisconnect()
  274. def restoreNetworkPlug(self):
  275. if self.networkPlugPulled():
  276. self.notify.info('*** RESTORING SIMULATED PULLED-NETWORK-PLUG ***')
  277. self.setSimulatedDisconnect(0)
  278. def doFind(self, str):
  279. """ returns list of distributed objects with matching str in value """
  280. for value in self.doId2do.values():
  281. if `value`.find(str) >= 0:
  282. return value
  283. def doFindAll(self, str):
  284. """ returns list of distributed objects with matching str in value """
  285. matches = []
  286. for value in self.doId2do.values():
  287. if `value`.find(str) >= 0:
  288. matches.append(value)
  289. return matches