0

I am sending message from one PC to other PC over UDP with my own header. User can set size of fragment for the message what means that I have to divide message into fragments and send them over to other PC, what should work in my code (did not test because receiving is the problem).

Now, on the receiver side, I need to keep track of fragment index, total fragment number and some other things which are defined in my header to check for possible data loss like (CRC). But let's get back to the problem.

I am saving every received fragment pieces by it's index into a list. So let's say index of first fragment in header is 1, so I want to save first fragment on position 1. I keep doing this in a while cycle until I get last data fragment. But something doesn't work properly. At the end, I want to print out my received message in order as it was saved in the list, from index 1 until the end using ''.join(list).

Check out my code, it is either printing out the message divided into fragments or when I move out the print of while cycle, then it prints nothing, not even the Receive: message.

Sending:

def send_loop(self):
    global mType, fragSize, fragIndex, fragCount, crc
    mType=0
    fragSize=0
    fragIndex=0 
    fragCount=0 
    crc=0
    fragSize = int(input('Fragment size: ')) #max size of fragment
while True:
            message = input('Enter message: ')
    #sending text message
            if (message[:2] == '-m'):
                mType = 1 #text message
                if message.startswith('-m '):
                    message = message[3:] # remove "-m "
                fragCount = math.ceil(len(message) / fragSize)  #rounding up number of fragments to send

                while message!= '':
                    data = bytearray()
                    data.extend(message[:fragSize].encode('utf-8'))
                    fragIndex += 1
                    header = struct.pack('!hIIII', mType, fragSize, fragIndex, fragCount, crc)
                    self.sock.sendto(header + bytearray(data), (self.host, self.port))
                    message = message[fragSize:] #set start of message to the right by size of fragSize

Receiving:

def create_socket(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind((self.host, self.port))
        rec_list = []
        while True:
            data, addr = sock.recvfrom(65535)
            header = data[:18]
            data = data[18:]
            (mType, fragSize, fragIndex, fragCount, crc) = struct.unpack('!hIIII', header)

        #type of message is text
            if mType == 1:
                if len(rec_list) < fragCount:
                    rec_list = ['None'] * fragCount   #empty list for messages of size fragCount
                rec_list[fragIndex] = data.decode('utf-8') 


                print(
                    '\nTyp: '       + str(mType) + 
                    '\nFragSize: '  + str(fragSize) + 
                    '\nFragIndex: ' + str(fragIndex) + 
                    '\nFragCount: ' + str(fragCount) + 
                    '\nCRC: '       + str(crc)
                    )
                msg = ''.join(rec_list)
                print('\nReceived: ' +  msg) 
redesert17
  • 35
  • 9
  • We would need to see the sending code also. – John Gordon Oct 29 '16 at 20:06
  • @JohnGordon updated – redesert17 Oct 29 '16 at 20:09
  • The `while True` is fine to keep your socket listening, however, you need to know when you're done receiving the data, right? It _seems_ to me that there's a condition (or another nested `while` loop) missing somewhere? Also, keep in mind that when you do `rec_list = ['None'] * fragCount`, you're destroying any information that you might have already extracted into `rec_list` from a previous package, right? – Savir Oct 29 '16 at 20:41
  • @BorrajaX I will look at the `while True` because it seems okay to me for now but about the `rec_list = ['None'] ` yes I know that it can destroy previous information and I need to fix it also, because I can send a message after another and it will keep rewriting the `list`, but I haven't figured out how should I do it. Would appreciate help. – redesert17 Oct 29 '16 at 20:45

1 Answers1

0

So... Below you'll find some code that keeps listening (in the first while True) forever. What I added is the second while loop. When you receive something, you need to enter into "rebuild" mode (into the second loop) and continue being there until all the chunks that conform your message are received.

Since the total number of chunks that you need to rebuild the full message is sent in every package, you can use that information to know how many pieces you're going to need in order to rebuild the message. Every time you receive a package, that means you've received one chunk (and that information is kept in the received_chunks variable).

Once you have received the same number of chunks as the number of total chunks, you know that you have rebuild your message, and you can exit the inner while to continue listening for the next message.

Note that I'm also extending the rec_list if it's smaller than the total number of fragments that conform the message. You probably don't need that, because right before getting into the inner loop it's initialized to an empty list. In the inner loop, you could substitute this:

if len(rec_list) < fragCount:
    need_to_add = fragCount - len(rec_list)
    rec_list.extend([None] * need_to_add)

By this:

if not rec_list:
    rec_list = [None] * fragCount

I did it like this to avoid deviating too much from the code. There's more cleanup you can do. I'd recommend doing more print(s to see the status of the different variables and what's happening...

while True:
    received_chunks = 0
    rec_list = []
    # Let's start receiving chunks...
    while True:
        data, addr = sock.recvfrom(65535)
        header = data[:18]
        data = data[18:]
        (mType, fragSize, fragIndex, fragCount, crc) = struct.unpack('!hIIII', header)
        print(
            '\nTyp: ' + str(mType) +
            '\nFragSize: ' + str(fragSize) +
            '\nFragIndex: ' + str(fragIndex) +
            '\nFragCount: ' + str(fragCount) +
            '\nCRC: ' + str(crc)
        )
        # type of message is text
        if mType == 1:
            if len(rec_list) < fragCount:
                need_to_add = fragCount - len(rec_list)
                rec_list.extend([None] * need_to_add)  # empty list for messages of size fragCount
            rec_list[fragIndex - 1] = data.decode('utf-8')
        print("We have received %s chunks. We expect a total of %s."
              " We are awaiting for %s chunks"
              % (received_chunks, fragCount, fragCount - received_chunks))
        received_chunks += 1
        if received_chunks == fragCount:
            break  # Get out of the second loop because we have all the chunks that we need.

        print("rec_list so far=%s" % (rec_list))
    # This is where the second (inner) while loop ends.

    print("Yay!! We got a FULL message...")
    msg = ''.join(rec_list)
    print('\nReceived: ' + msg)
Savir
  • 17,568
  • 15
  • 82
  • 136
  • I tried it but when I send first message, it prints out nice all together but when I write second message after the first one, then I get error `rec_list[fragIndex-1] = data.decode('utf-8') IndexError: list assignment index out of range ` – redesert17 Oct 29 '16 at 22:11
  • Oh! Your sender script is accepting things like `python sender_script.py -m foo -m bar"` ? – Savir Oct 29 '16 at 22:12
  • **If that's the case**, I think your problem is in the `sender_script`... When you do `message = message[3:]` in it, `message` will be `foo -m bar`? – Savir Oct 29 '16 at 22:19
  • No, no. `message=message[3:]` is cutting the `-m` so the length of the message becomes clean message which is followed after `-m ` – redesert17 Oct 29 '16 at 22:24
  • What are you sending in the part that sends data? Some other type besides a `-m` message? (a text message?) – Savir Oct 29 '16 at 22:28
  • I'm asking because if I launch the sender in another terminal, and I keep doing... `-m foo` (+enter), the receiver does receive `foo`. If after that I type `-m foobar`, the receiver does receive `foobar`... I can't reproduce that behavior **:-S** – Savir Oct 29 '16 at 22:29
  • The message is something like this: `-m Hello world!` The `-m` is only a switch for message type, because I am also going to send files. And I think the problem is in the `if not rec_list: rec_list = [''] * fragCount` because it looks like it doesn't empty the `list`, it skips the condition and goes to the index which is not empty I guess. Or? – redesert17 Oct 29 '16 at 22:31
  • The idea of doing that is not exactly emptying the list but rather create a new list with `fragCount` elements (all initialized in that case to empty string)... I just changed my test code to what you mention in the comment, and it's sill working fine on my end? This is odd... – Savir Oct 29 '16 at 22:38
  • Wait, have you added the two lines on my example: `received_chunks = 0 rec_list = []` right below the first `while loop`? Because once you receive (and _rebuild_) a message, you need to "reset" the count of received chunks and the list where you're storing the chunks. – Savir Oct 29 '16 at 22:39
  • I have those two lines just like you have after first `while True`, there are those two. I have no idea why it doesn't work. Do you want more code? – redesert17 Oct 29 '16 at 22:45
  • MmmmmHHHhhhMMMMMMMmmm... Maybe, but before that, three questions: 1) If in your sender you send `-m Hello World!`, what does the receiver print as `FragSize`, `FragIndex` and `FragCount` right before the failure? 2) What is the fragment size chosen in the sender side. 3) How is the sender called? (You mentioned `-m Hello world` in another comment... Is that all what is entered in the sender side?) – Savir Oct 29 '16 at 22:50
  • Okay, thats what I just noticed is that I send first message `-m hello` and the `fragSize` is set to `3` `fragIndex = 2` `fragCount=2` in the final, when it is printed out, what is CORRECT! But! Right after this, I send `-m world` and the `fragSize=3`, correct, `fragIndex = 3` INCORRECT!!! `fragCount=2` correct. So there is that index flying high. And sender is also setting the `fragSize` but it is right at the start of the program, it is kinda separated from the messages. – redesert17 Oct 29 '16 at 22:58
  • I sounds like `fragIndex` needs to be reset to `0` in the sender, after sending the message, so every message starts over at `0` (I'd put `fragIndex=0` right after the `message = input('Enter message: ')`) What happens if you do that? – Savir Oct 29 '16 at 23:10
  • I have added some more code on the top of sender function, there are some variables declared, which might cause trouble with resetting `fragIndex` – redesert17 Oct 29 '16 at 23:11
  • 1
    WORKS! Just like you said, putting it after `enter message` helped! Thank you very much! – redesert17 Oct 29 '16 at 23:13
  • Yaaay!! Finally! **:+1:** It makes sense too, since each message is split in their own fragments, right? **:)** – Savir Oct 29 '16 at 23:15
  • Yes, I will have to do the same I guess for sending files, I hope it will work. And also, can I kind of say that it is ACK ? Because ACK is checking for correct chunk that receiver is awaiting but what my code does is that it basically saves all pieces on index and at the end checks if number of fragments equals. Question is, what if there is really one or more missing and I need to resend it from sender? – redesert17 Oct 29 '16 at 23:22
  • Well... If you accept another type (besides the text `-m`, you might have to move things around a bit on the code) For ACK... that's a gonna take more thinking... First of all (theoretically) you could get chunks of message "A" and of message "B" out of order right? You might need a way of identifying what message does the chunk belong to. And then... How to know that the message is incomplete? The sender might just be taking a long time? You might need to time it out? (like _"if I haven't received the full message in 10 seconds, then I timeout_") Depends a bit on what you wanna do. – Savir Oct 29 '16 at 23:32
  • Its a school assigment and it must be done in 22 hours, so I have to do it :D Is there something I could learn fast to do the ACK? Using those timeouts and stuff? – redesert17 Oct 29 '16 at 23:35
  • Oh ,boy... Well... If it's only 22 hours, then I wouldn't get that fancy, at least not for now... In the TCP protocol, an ACK only means that I have received **this** package. So what you can do is send back a datagram with only one field: The `fragIndex` and F it... Like ok, I got this fragment. In your sender, you could do something like `Send fragment --> Get the ACK --> No ack? Then resend fragment` – Savir Oct 29 '16 at 23:40
  • F it? I am going to try it, would be nice if we could stay in touch if I run into trouble, your explanation fits me very well. Btw, can I apply this `list` for sending files also? Because I am doing it right now and have no idea if its a good choice. – redesert17 Oct 29 '16 at 23:50
  • I'll try to stay in touch **:)** You can apply this idea for sending files, yeah. You just will have more chunks (probably files are much bigger than `Hello world`, which is 11 bytes) However, keep in mind that the code as it shown in my answer, all that `list` magic is under the `if mType == 1:`, so if `mType` is different than `1`, it won't apply (you might have to move the logic of the list filling outside that `if`) – Savir Oct 30 '16 at 00:17
  • Also, consider if your question about the ACK is worth opening another question (mostly because people who find this question in Google and don't read these comments might have trouble understanding the iterations over the code) Up to you, though **:-)** – Savir Oct 30 '16 at 00:22
  • If I move the filling logic out of the `if` then what will be the purpose of that `if mType == 1`? How will I separate different messages? – redesert17 Oct 30 '16 at 00:37
  • I'm guessing it would be more used at the end: Once you reconstruct a message, you could just `print` it on the console (if it's a string, hence `mType == 1`) or you could just save it somewhere (if it's a file with some other `mType`) – Savir Oct 30 '16 at 00:38
  • I will create new topic for ACK after I get some sleep, but now I just want to finish sending file which is causing some trouble. I cant get file to open and get sent by segments into the list. – redesert17 Oct 30 '16 at 01:05
  • Cool, makes sense! By the way, to fully read a file, you can call `.read()` on the file object. Take a look to https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files – Savir Oct 30 '16 at 01:08
  • I am thinking about sending first file name using the method of fragments and then sending content of the file. Should I use two while cycles one after another? Or what would be better? – redesert17 Oct 30 '16 at 01:27
  • Hmm... If you do that, then you're gonna have two separate messages (one the filename, one more the file contents) so when you receive a file, you must also have kept track of the _previous_ message and that's a bit more complex. Something you can do is reserve a number of characters for the filename (let's say... `128`) and send that before sending the contents of the file. If the filename is less than `128`, just fill it with whitespaces. That way, if in the receiver your `mType` signals a "file", you build your message, get the first `128` chars (that's the filename) and then the contents. – Savir Oct 30 '16 at 01:39
  • Thats what I thought. But when I try to read file by fragments, it gives me error `TypeError: '_io.BufferedReader' object is not subscriptable`. Any ideas? – redesert17 Oct 30 '16 at 01:42
  • Are you calling `file.read()` or `file[something]` (the latter will give you that error) See https://www.google.com/search?q=_io.BufferedReader%27+object+is+not+subscriptable&oq=_io.BufferedReader%27+object+is+not+subscriptable&aqs=chrome..69i57.397j0j7&sourceid=chrome&ie=UTF-8 – Savir Oct 30 '16 at 01:44
  • I am using `f=open(file_name,"rb")` – redesert17 Oct 30 '16 at 01:45
  • Then you should be able to load its contents doing `contents = f.read()` Is that giving you an error? The contents of the file should be in the `contents` variable – Savir Oct 30 '16 at 01:46
  • It looks good for now, but I can't really test it because it looks like I can't use the list for saving binary data. I am trying to send `JPG` file. Receiving part is the problem now. – redesert17 Oct 30 '16 at 01:53
  • You can pass the file contents through a `base64` encoder, send that, and, once you have received all the chunks, decode it in the receiver (that will convert all the "weird" characters to regular ascii) See http://stackoverflow.com/questions/3715493/encoding-an-image-file-with-base64 – Savir Oct 30 '16 at 01:55
  • Received all the chunks... And removed the first 128 characters that are actually the filename, not the file contents **;-)** – Savir Oct 30 '16 at 01:57
  • `f=open(file_name,"rb") contents = f.read() contents.encode(base64_encode)` gives me error `'bytes' object has no attribute 'encode'` – redesert17 Oct 30 '16 at 02:05
  • Double check the link I pasted in the comment above. You encode the contents with `encoded_contents = base64.b64encode(contents)` (you'll need to `import base64` at the top of your `.py` file) – Savir Oct 30 '16 at 02:07
  • It might be my bad, cause I run it again with no encoding just plain `contents` with binary data I guess and it gives me error all the way in `receiver` because `join` wants a string. `content = ''.join(rec_list) TypeError: sequence item 0: expected str instance, bytes found` – redesert17 Oct 30 '16 at 02:10
  • Looks like you're getting closer. Take a look to: http://stackoverflow.com/questions/17068100/joining-byte-list-with-python – Savir Oct 30 '16 at 02:11
  • Basically, you need to transform from `bytes` to `str`: Something like `b''.join(rec_list).decode()` – Savir Oct 30 '16 at 02:13
  • Got it, but again previous problem with index??? Here is info of the variables: `FragSize: 1500 FragIndex: 5016 FragCount: 5015` – redesert17 Oct 30 '16 at 02:18
  • In the sender, are you calculating the total length (which is `file_name` + `file_contents` _before_ calculating the number of fragments?) Because the `file_name` should also count towards the total size to transmit. – Savir Oct 30 '16 at 02:19
  • Not the filename yet, I have skipped the name for now, doing just plain content – redesert17 Oct 30 '16 at 02:20
  • Mmm... If you're using the `base64` encoder, are you calculating the number of fragments _after_ encoding? Basically, the calculation of the number of fragments should be the last thing you do in the sender before sending... – Savir Oct 30 '16 at 02:22
  • I am not using `base64`. And yes, calculation is just before sending. I really have no idea why is it doing this. Its exactly same as with strings and it just doesn't work. Every time, `index` number is bigger by `1` then `count` number, in every file I try to send. – redesert17 Oct 30 '16 at 02:28
  • Unfortunately it's a bit difficult for me guessing without seeing the code... Maybe it's worth it opening another question for it? (knowing how the site works, it'd be great if you could isolate in your code the file sending part, and dispose of the "regular string" sending part? ) I'm fairly sure the question will be better received that way (if there's too much code, people tend to downvote it) – Savir Oct 30 '16 at 02:34
  • Okay, check it out here please, http://stackoverflow.com/questions/40325616/sending-file-over-udp-divided-into-fragments – redesert17 Oct 30 '16 at 02:49