The keyword here is socket.
A socket is one endpoint of a two-way communication link between two programs running on the network. A socket is bound to a port number so that the TCP layer can identify the application that data is destined to be sent to.
On most operating systems, SOCKETS are identified by a number, similar to a file descriptor, that index a single entry into a table containing information about a connection. This information is usually in the following format:
SOURCEIP:SOURCEPORT DESTINATIONIP:DESTINATIONPORT PROTOCOL STATE
This table can be accessed, generally, via the NETSTAT command (or its equivalent) on most operating systems. In no event you can have two entries on such table with equal values (in other words: two entries with the exact same sourceip, sourceport, destinationip, destinationport and protocol at the same time). You can have two entries with same destination ports, source ports etc, any value, but never exact same values on two or more entries. And each entry is indexed (identified) by a single socket number. (There are exceptions to this rule)
This socket number is returned when you issue a SOCKET() function call. (on linux/freebsd/windows). Later, your program will decide what to do with the socket requested from the operating system. It can issue a CONNECT() call and connect this socket into a remote machine or a BIND() and LISTEN() calls to use it to wait for inbound connections.
This means that two processes CAN share a port, if they happen to share the SOCKET associated to the port. This is even a common way to do network programming. You can fork or spawn another process when your listening socket receives a connection request and you call ACCEPT() to accept it. ACCEPT() will return a socket number that will identify a new SOCKET created for that incomming connection, you will then pass the socketnum to the spawned/forked process.
For example on Windows:
The WSADuplicateSocket function is introduced to enable socket sharing across processes. A source process calls WSADuplicateSocket to obtain a special WSAPROTOCOL_INFO structure for a target process identifier. It uses some interprocess communications (IPC) mechanism to pass the contents of this structure to a target process. The target process then uses the WSAPROTOCOL_INFO structure in a call to WSPSocket. The socket descriptor returned by this function will be an additional socket descriptor to an underlying socket which thus becomes shared. Sockets can be shared among threads in a given process without using the WSADuplicateSocket function because a socket descriptor is valid in all threads of a process.
In other words, ports are used to help identify SOCKETS on a computer, which are single connections between TWO hosts on the network. This is true for both TCP and UDP protocols. They do not, usually, identify processes and most operating systems wont bother if two processes act on the same port. The keyword here is SOCKET, not process. Sockets are the things that must be unique, because they identify a unique connection between two hosts.
So, answering your question. Why are ports needed ?
Because if there were no ports, the table i refered to in the start of the text would be limited to:
SOURCEIP DESTINATIONIP PROTOCOL STATE
In other words, you could have only a single SOCKET for each host accessible to your computer, wich is not very usefull at all.
What Is a Socket?