Async_TCP using pyFltk

Lesson 29: Asynchronous TCP using pyFltk event loop

Server code:

#tcp server
import socket
from fltk import *

class tcpwin(Fl_Window):
    def __init__(self,x,y,w,h,label):
        Fl_Window.__init__(self,x,y,w,h,label)
        self.begin()
        self.conbut=Fl_Light_Button(40,30,270,40,"Accept Connection")
        self.inp=Fl_Input(40,80,270,40,"Send:")
        self.brow=Fl_Multi_Browser(40,130,270,140)
        self.end()
        self.callback(self.close)
        self.conbut.callback(self.conbut_cb)
        self.inp.callback(self.send_cb)
        self.inp.when(FL_WHEN_ENTER_KEY)
        
    def conbut_cb(self, wid):
        host = 'localhost'
        port= 4444
        self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #fd=3
        self.s.bind((host, port)) 
        self.s.listen()
        fdl=self.s.fileno() #listening fd
        Fl.add_fd(fdl, self.acceptConnections) #watch for connection requests

    def acceptConnections(self, fdl): #runs when data comes to socket s
        self.conn, raddr = self.s.accept() #fd=4 blocking
        self.fd=self.conn.fileno() #4
        Fl.add_fd(self.fd, self.receive_data) 
        #watch for data through established connection
        
    def close(self,wid):
        try: # in case no connection
            self.conn.close()
        except:
            print('closing without a connection')
        finally:
            self.hide()

    def send_cb(self, widget):
        self.conn.sendall(self.inp.value().encode())    
        
    def receive_data(self, fd):
        data=self.conn.recv(1024)
        print(data)
        if data==b'':
            self.conn.close()
            Fl.remove_fd(self.fd)
        else:
            self.brow.add(data.decode())

a=tcpwin(55,55,333,333,"TCP Server")
a.show()
Fl.run()

Client code:

#tcp client

import socket
from fltk import *

class tcpwin(Fl_Window):
    def __init__(self,x,y,w,h,label):
        Fl_Window.__init__(self,x,y,w,h,label)
        self.begin()
        self.conbut=Fl_Light_Button(40,30,270,40,"Connect")
        self.inp=Fl_Input(40,80,270,40,"Send:")
        self.brow=Fl_Multi_Browser(40,130,270,140)
        self.end()
        self.callback(self.close)
        self.conbut.callback(self.conbut_cb)
        self.inp.callback(self.send_cb)
        self.inp.when(FL_WHEN_ENTER_KEY)

    def conbut_cb(self, wid):
        host = 'localhost'
        port= 4444
        self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #fd=3
        self.s.connect((host,port))
        self.fd=self.s.fileno() #3
        Fl.add_fd(self.fd, self.receive_data)

    def close(self,wid):
        try:
            self.s.close()
        except:
            print('closing without a connection')
        finally:
            self.hide()

    def send_cb(self, widget):
        self.s.sendall(self.inp.value().encode())

    def receive_data(self, fd):
        data=self.s.recv(1024)
        print(data)
        if data==b'':
            self.s.close()
            Fl.remove_fd(self.fd)
        else:
            self.brow.add(data.decode())

a=tcpwin(55,55,333,333,"TCP Client")
a.show()
Fl.run()

Below is a better solution with only one file for client and server

import socket,sys
from fltk import *
#usage: python3 program.py server/client localhost 5555
# 3 args: (client/server) host port
# remember machine to machine, host = 0.0.0.0 for server
class mywin(Fl_Window):
    def __init__(self,x,y,w,h,label):
        Fl_Window.__init__(self,x,y,w,h,label)
        self.begin()
        self.inp=Fl_Input(30,80,270,40,"Type")
        self.inp.align(FL_ALIGN_LEFT_TOP)
        self.sbut=Fl_Return_Button(30,130,270,40,"Send")
        self.brow=Fl_Multi_Browser(30,180,270,140)
        self.end()
        self.callback(self.close) #close window callback

        self.conn=0
        self.sbut.callback(self.send_cb)
        self.host = sys.argv[2]
        self.port=int(sys.argv[3])
        self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

        if sys.argv[1] == "server":
            self.s.bind((self.host, self.port)) # server must bind and listen
            self.s.listen()
            Fl.add_fd(self.s.fileno(), self.acceptconn)

        elif  sys.argv[1]=='client':
            try:
                self.s.connect( (self.host,self.port) ) #client connects to server
                Fl.add_fd(self.s.fileno(), self.recvdata)
            except:
                print("Server needs to start first")
                self.s.close()
                sys.exit(1)

    def acceptconn(self, fd):
        if self.conn == 0: #only allow one client to connect
            self.conn, addr = self.s.accept()
            Fl.add_fd(self.conn.fileno(), self.recvdata)

    def send_cb(self, widget):
        text = self.inp.value()
        self.brow.add("@B222@r"+text) #background color 222, right justified
        if sys.argv[1] == "server":
            try:
                self.conn.sendall(text.encode())
            except:
                print("Client has not yet connected, or has disconnected")
        elif sys.argv[1]=="client":
            try:
                self.s.sendall(text.encode())
            except:
                print('Server may have disconnected')
                self.s.close()
                self.hide()
        self.inp.value('')

def recvdata(self, fd):
        if sys.argv[1] == "server":
            data=self.conn.recv(1024) #server communicates through socket conn
            #data is null byte b'' when socket is closed
            if data == b'':
                Fl.remove_fd(self.conn.fileno())
                print('stopped watching socket conn')
                return
        elif sys.argv[1]== "client":
            data=self.s.recv(1024)    #client communicates through socket s
            if not data: #same as if data == b''
                Fl.remove_fd(self.s.fileno())
                print('stopped watching socket s')
                return
        self.brow.add(data.decode())

    def close(self, wid):
        if sys.argv[1]== "server":
            self.conn.close()
        self.s.close() #close socket s for both client and server
        self.hide()

a=mywin(55,55,333,333,"sockets "+sys.argv[1])
a.show()
Fl.run()