Sticky Keyboard
Sticky Keyboard

Reputation: 31

Incomplete data transfer over python socket

I'm running into issues transferring data over TCP with a remote client and server written in Python. The server is located in a pretty remote region with relatively slow internet connection (<2Mb/sec). When the client is run on the LAN with the server the complete string is transferred (2350 bytes); however, when I run the client outside of the LAN sometimes the string is truncated (1485 bytes) and sometimes the full string comes through (2350 bytes). The size of the truncated string always seems to be 1485 bytes. The full size of the string is well below the set buffer size for the client and server.

I've copied abbreviated versions of the client and server code below, where I have tried to edit out all extraneous details:

Client

import socket
from   time   import sleep

class FTIRdataClient():

    def __init__(self,TCP_IP="xxx.xxx.xxx.xxx",TCP_Port=xxx,BufferSize=4096):

        #-----------------------------------
        # Configuration parameters of server
        #-----------------------------------
        self.TCP_IP          = TCP_IP
        self.TCP_Port        = int(TCP_Port)
        self.RECV_BUFFER     = int(BufferSize) 

    def writeTCP(self,message):

        try:
            sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)    
            sock.connect((self.TCP_IP,self.TCP_Port))
            sock.send(message)
            incomming = sock.recv(self.RECV_BUFFER)
            sock.close()    
        except:
            print "Unable to connect to data server!!"           
            incomming = False   

        return incomming         

if __name__ == "__main__":

    #----------------------------------
    # Initiate remote data client class
    #----------------------------------
    dataClass = FTIRdataClient(TCP_IP=dataServer_IP,TCP_Port=portNum,BufferSize=4096)

    #--------------------------------
    # Ask database for all parameters
    #--------------------------------
    allParms = dataClass.writeTCP("LISTALL")

Server

import os
import sys
import socket
import select
import smtplib
import datetime       as     dt


class FTIRdataServer(object):

    def __init__(self,ctlFvars):
        ...

    def runServer(self):

        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.bind((self.TCP_IP,self.TCP_Port))
        #self.server_socket.setsockopt(socket.IPPROTO_TCP,socket.TCP_NODELAY,1)
        self.server_socket.listen(10)         
        self.connection_list.append(self.server_socket)

        #-------------------------------------
        # Start loop to listen for connections
        #-------------------------------------
        while True:     

            #--------------------
            # Get list of sockets
            #--------------------
            read_sockets,write_sockets,error_sockets = select.select(self.connection_list,[],[],5)

            for sock in read_sockets:

                #-----------------------
                # Handle new connections
                #-----------------------
                if sock == self.server_socket:

                    #----------------------------------------------
                    # New connection recieved through server_socket
                    #----------------------------------------------
                    sockfd, addr = self.server_socket.accept()

                    self.connection_list.append(sockfd)
                    print "Client (%s, %s) connected" % addr

                #-------------------------------------
                # Handle incomming request from client
                #-------------------------------------
                else:

                    #------------------------
                    # Handle data from client
                    #------------------------
                    try:
                        data = sock.recv(self.RECV_BUFFER)

                        #------------------------------------------------
                        # Three types of call to server:
                        #  1) set   -- sets the value of a data parameter
                        #  2) get   -- gets the value of a data parameter
                        #  3) write -- write data to a file
                        #------------------------------------------------
                        splitVals = data.strip().split()

                        ...

                        elif splitVals[0].upper() == 'LISTALL':

                            msgLst = []

                            #----------------------------
                            # Create a string of all keys 
                            # and values to send back
                            #----------------------------
                            for k in self.dataParams:
                                msgLst.append("{0:}={1:}".format(k," ".join(self.dataParams[k])))

                            msg = ";".join(msgLst)

                            sock.sendall(msg)

                        ...

                        else:
                            pass

                    #---------------------------------------------------
                    # Remove client from socket list after disconnection
                    #---------------------------------------------------
                    except:
                        sock.close()
                        self.connection_list.remove(sock)
                        continue

        #-------------
        # Close server
        #-------------
        self.closeServer()


    def closeServer(self):
        ''' Close the TCP data server '''
        self.server_socket.close()

Your help is greatly appreciated!!!

Upvotes: 0

Views: 2682

Answers (1)

Sticky Keyboard
Sticky Keyboard

Reputation: 31

For anyone who is interested I found the solution to this problem. John Nielsen has a pretty good explanation here. Basically, TCP stream only guarantees that bytes will not arrive out of order or be duplicated; however, it does not guarantee how many groups the data will be sent in. So one needs to continually read (socket.recv) until all the data is sent. The previous code work on the LAN because the server was sending the entire string in one group. Over a remote connection the string was split into several groups.

I modified the client to continually loop on socket.recv() until the socket is closed and I modified the server to immediately close the socket after sending the data. There are several other ways to do this mentioned in the above link. The new code looks like:

Client

class FTIRdataClient(object):

    def __init__(self,TCP_IP="xxx.xxx.xx.xxx",TCP_Port=xxxx,BufferSize=4024):

        #-----------------------------------
        # Configuration parameters of server
        #-----------------------------------
        self.TCP_IP          = TCP_IP
        self.TCP_Port        = int(TCP_Port)
        self.RECV_BUFFER     = int(BufferSize) 

    def setParam(self,message):
        try:
            sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)    
            sock.connect((self.TCP_IP,self.TCP_Port))
            sock.sendall("set "+message)

            #-------------------------
            # Loop to recieve all data
            #-------------------------
            incommingTotal = ""
            while True:
                incommingPart = sock.recv(self.RECV_BUFFER)
                if not incommingPart: break
                incommingTotal += incommingPart

            sock.close()    
        except:
            print "Unable to connect to data server!!"           
            incommingTotal = False   

        return incommingTotal

Server class FTIRdataServer(object):

def __init__(self,ctlFvars):

    ...

def runServer(self):

    self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.server_socket.bind((self.TCP_IP,self.TCP_Port))
    #self.server_socket.setsockopt(socket.IPPROTO_TCP,socket.TCP_NODELAY,1)
    self.server_socket.listen(10)         
    self.connection_list.append(self.server_socket)

    #-------------------------------------
    # Start loop to listen for connections
    #-------------------------------------
    while True:    

        #--------------------
        # Get list of sockets
        #--------------------
        read_sockets,write_sockets,error_sockets = select.select(self.connection_list,[],[],5)

        for sock in read_sockets:

            #-----------------------
            # Handle new connections
            #-----------------------
            if sock == self.server_socket:

                #----------------------------------------------
                # New connection recieved through server_socket
                #----------------------------------------------
                sockfd, addr = self.server_socket.accept()

                self.connection_list.append(sockfd)
                print "Client (%s, %s) connected" % addr

            #-------------------------------------
            # Handle incomming request from client
            #-------------------------------------
            else:

                #------------------------
                # Handle data from client
                #------------------------
                try:
                    data = sock.recv(self.RECV_BUFFER)

                    ...                   

                    elif splitVals[0].upper() == 'LISTALL':

                        msgLst = []

                        #----------------------------
                        # Create a string of all keys 
                        # and values to send back
                        #----------------------------
                        for k in self.dataParams:
                            msgLst.append("{0:}={1:}".format(k," ".join(self.dataParams[k])))

                        msg = ";".join(msgLst)

                        sock.sendall(msg)

                    elif splitVals[0].upper() == 'LISTALLTS':   # List all time stamps

                        msgLst = []

                        #----------------------------
                        # Create a string of all keys 
                        # and values to send back
                        #----------------------------
                        for k in self.dataParamTS:
                            msgLst.append("{0:}={1:}".format(k,self.dataParamTS[k]))

                        msg = ";".join(msgLst)

                        sock.sendall(msg)                        

                    ...
                    else:
                        pass

                    #------------------------
                    # Close socket connection
                    #------------------------
                    sock.close()
                    self.connection_list.remove(sock)   

                #------------------------------------------------------
                # Remove client from socket list if client discconnects
                #------------------------------------------------------
                except:
                    sock.close()
                    self.connection_list.remove(sock)
                    continue

    #-------------
    # Close server
    #-------------
    self.closeServer()

Whatever. This is probably common knowledge and I'm just a little slow.

Upvotes: 1

Related Questions