Think of the descriptor as an array index. An index into an array stored in the operating system, one for each process in the system.
When you open a descriptor (be it a file, message queue, socket, etc.), the operating system writes the parameters for this object to an array unique to your process. Then, the operating system returns the index into this array in your program.
Every time your program uses this "descriptor", the operating system really looks for a structure in this private array, it continues to figure out how to handle the object associated with this "descriptor".
Linux typically reserves descriptors 0, 1, and 2 for STDIN, STDOUT, and STDERR, respectively. But from then on, all open handles will be numbered 3, 4, and so on. And your handle 3 may refer to the file /tmp/foo.txt, and another handle to process 3 may refer to the file /tmp/bar.txt. Therefore, if two processes use similar file handles, it does not matter.
By the way, you do not need to know or care about what the descriptor actually contains. Theoretically, it could be anything - a magic number, a pointer, an integer, it does not matter. The handle is really a secret token that you simply pass to the operating system anytime you want to access the system object.
source share