SocketServer and threading

I am trying to write a gateway side script that will work as a TCP server.

I am having trouble trying to import the libraries I would like to use. Per the jython and python 2.1 documentation I should be able to import SocketServer and threading, but keep receiving the error “ImportError: no module named SocketServer”
“ImportError: no module named threading”.

http://www.jython.org/docs/library/socketserver.html
http://docs.python.org/release/2.1/modindex.html

What do I need to do to use these libraries?

Have you considered the Developer Program and writing your own module?

Yes, but if the technology is already natively in Ignition why go through the trouble of trying to write a module that does the same thing? If the libraries I need for writing a gateway side TCP server are not available then I will work on writing module. My understanding is that Ignition has python 2.1 libraries available for use.

I’ve done it two ways, using both Python sockets and the Java libraries (per this thread: http://inductiveautomation.com/forum/viewtopic.php?f=70&t=5358&p=13319&hilit=socket#p13319). For threading, just use the Ignition asynchonous calls.

If it is something simple, the Python socket library is sufficient (i.e. sending and receiving occassional messages to printers and other devices), but if you have a need for speed, the java libraries are better. The post-V2.1 Python libraries perform much better, but you’ll find the 2.1 and earlier ones to be sort of lacking.

Someday I’ll sit down and write a module, but it works so well this way that I have bumped it down lower in the priority list.

If I do use system.util.invokeAsynchronous how do I stop the TCP server from listening for connections?

Close the server socket that your asynchronous task is accepting on, and the blocked call to accept() will throw an IOException that should exit your function, ending the task.

I have been doing some research. What if I use a global variable and non blocking? I could then update the global variable and the thread would check the variable value in order to exit

yeah, that could work. by “non-blocking” did you mean java NIO? If so, you may want to reconsider just to avoid the deep and tangled complexity pit you’re hurling yourself into.

on your suggestion to close the server socket and catch the IOException how do you keep track of the server socket after you call invokeAsynchronous?

this is what i came up with for NIO. Does my NIO solution have any performance advantage over the IOException method? or vice versa?

def TCPServer():
	from java.nio import *
	from java.nio.channels import *
	from java.nio.channels.spi import *
	from java.net import *
	import time
	from java.util import *
	from java.nio.charset import CharsetDecoder
	from java.nio.charset import Charset
	import system

	global KEEP_RUNNING
	
	#this stuff is used for reading and writing to a Java ByteBuffer
	buf = ByteBuffer.allocateDirect(1024)
	charset = Charset.forName("US-ASCII")
	decoder = charset.newDecoder()
	enc = charset.newEncoder()
	
	#create sever socket channel
	sChannel = ServerSocketChannel.open()
	
	#bind server socket channel to ip and port
	sChannel.socket().bind(InetSocketAddress("192.168.1.101", 10005))
	
	#configure the server socket channel to not block
	sChannel.configureBlocking(0)
	
	KEEP_RUNNING = 1	

	#keep checking for connections as long as global variable is still true
	while(KEEP_RUNNING == 1):
		print "Waiting for connections"
		try:
			#this is non blocking
			sc = sChannel.accept()
		except:
			print "error in ServerSocketChanel accept()"
			sChannel.close()
			sc = None
			buf.rewind()
			buf.clear()

		if (sc == None): #no client has tried to connect
			time.sleep(10)
		else: #client has connected
			try:
				sc.configureBlocking(0) #makes the client connection non blocking when trying to read data
				print "Incoming connection from: " + str(sc.socket().getRemoteSocketAddress())
			except:
				print "error in configuring non blocking"
				sc = None
				buf.rewind()
				buf.clear()

			try:
				currentData = ""
				buf.rewind() #makes sure bytebuffer is at the beginning
				sc.write(enc.encode(CharBuffer.wrap("S\r\n"))) #write data to bytebuffer to be sent to client
				numRead = sc.read(buf) #read data from bytebuffer
				#keep reading as long as global variable is still true and the end of message has not been reached
				while(KEEP_RUNNING == 1 and currentData.endswith("\r\n") == 0):
					if(numRead != 0):
						print "numRead = "+str(numRead)
					time.sleep(2)
					sc.write(enc.encode(CharBuffer.wrap("S\r\n")))
					numRead = sc.read(buf)
					bufPosition = buf.position() #get bytebuffer position
					buf.rewind() #move to beginning of bytebuffer
					currentData = decoder.decode(buf).toString() #get the current data from bytebuffer as string
					buf.position(bufPosition) #put the bytebuffer position back to end to continue reading
					print "buf position="+str(bufPosition)
					currentData = currentData[:bufPosition] #gets ride of trailing white space from bytebuffer
					print currentData
					time.sleep(5)

				decoder.reset()
				bufPosition = buf.position()
				buf.rewind()
				print decoder.decode(buf).toString()[:bufPosition]
				buf.clear()
	
				sc.close()
			except:
				print "error in read"
				sc = None
				buf.rewind()
				buf.clear()

	print "Thread stopped"
	
	sChannel.close()

system.util.invokeAsynchronous(TCPServer)

Well at first glance that code looks OK, but NIO code is notoriously complex to get right.

Any performance advantage you may gain (this is actually controversial) is totally counteracted by the way you’re using sleeps to wait for more data in the same thread that accepts new connections.

That said, I believe that you’re writing a serial-over-ethernet protocol, so I’d say that performance is not important at all.

Basically, use whatever works, but I’m warning you that if your NIO starts giving you funky behavior, the troubleshooting is hellish.

To keep track of a traditional server socket you could just make it global (just like your KEEP_RUNNING flag) in order to shut the socket down.