It always frustrates me how many people oversimplify the differences between TCP and UDP as "TCP is reliable but slow, while UDP is fast but unreliable".
This is not what TCP and UDP are about. TCP and UDP are two different abstractions over IP, and are used for different things. Both very good in their own fields.
TCP is a stream oriented protocol. You use TCP when you want to transfer a lot of sequential data from one point to another, maximizing the throughput (that is, minimize the amount of time it takes you to send -all- of the data). The automatic resending of lost datagrams and reordering are one of the features of TCP, but TCP also features stateful connections, flow control and congestion control. These features make TCP a really good tool for transferring large amounts of data from one point to another. Protocols such as HTTP and FTP where you want to send large chunks of data as fast as possible are meant to use TCP.
UDP, on the other hand, is a message oriented protocol. You use UDP when you want to send messages from one point to one or many points, minimizing the latency (that is, the amount of time it takes between a message is sent until it is received). In order to minimize latency, UDP does not implement datagram retransmissions or reordering. However, if you want your message-based application to support retransmissions and/or reordering, you can implement that yourself, and it's actually quite simple! Additionally, there is a bunch of fun things you can do in UDP that you simply can't do with TCP, like multicast and UDP hole punching.
TCP and UDP are both awesome technologies meant for different things, so what you want to do is to ask yourself whether the communications in your program are better represented as a stream of bytes, or as a series of messages, and a good rule of thumb is:
If your program is about sending messages, use UDP.
If your program is about sending a stream of bytes, use TCP.
This is what the decision between TCP and UDP should be like, not about reliability, which is just one of the many differences between them, and in my opinion the least important of them all.
Just like it is possible to make a stream abstraction over UDP, it is possible (and sadly very common) to make a message abstraction over TCP.
If you do choose to make a message abstraction over TCP, remember that you're going to have to use TCP in a way it was not meant to, which means you're going to encounter some difficulties. In particular:
Message boundaries: TCP treats data as a very long stream of bytes, and as such, has no concept of packets or messages. If you want to send messages over TCP, you're going to have to create some kind of message header that includes the length of each message.
Buffering on send: TCP expects you to be able to produce data as fast as you can. TCP datagrams can be very large, and if the network conditions are good, a TCP stack will buffer your bytes until it can send a very large datagram therefore maximizing throughput. This is a good thing for streams, but not so much for messages. Once too many times people end up flushing the buffer on each send, or using options such as
TCP_NODELAY, and even then you have no guarantees. To quote the late Richard Stevens:
2.11. How can I force a socket to send the data in its buffer?
You can't force it. Period. TCP makes up its own mind as to when
it can send data. Now, normally when you call write() on a TCP
socket, TCP will indeed send a segment, but there's no guarantee and
no way to force this. There are lots of reasons why TCP will not
send a segment: a closed window and the Nagle algorithm are two
things to come immediately to mind.
Buffering on receive: Even if you manage to flush the buffer on each send, there is no guarantee that you will get one reception for each flush you send. It is perfectly possible for you to get several messages on a single
recv call, because they were buffered at the reception end (especially on networks with high packet loss rates). You then have to implement a message reception queue, which is a pretty stupid thing to do when you're using TCP.
Heartbeats and connection drop detection: TCP has its own mechanism for detecting dropped connections, but they are usually too slow for message passing applications (the non-configurable keep alive datagram is sent about once every two hours). Once too many times people end up implementing their own hearbeat protocols inside the very same TCP stream, and then they end up asking "how to force close TIME_WAIT", "How to detect a closed connection" or something like that.
None of these problems exist if you make your message oriented program with UDP:
Message boundaries: Each message is a separate UDP datagram.
Buffering on send: Doesn't exist. A
sendto call immediately sends a datagram.
Buffering on receive: Doesn't exist. You will only get one datagram per
Connection drop detection: UDP is not connection oriented. You can implement a simple timeout with your own parameters to consider a client is no longer there.
If you want to use TCP for message passing because you think it's simple, don't.
Now, for the dreaded reliability "problem", you can implement simple reliability by including two fields on each message: the first one is a permanently increasing message ID, and the second one is the largest message ID you have received from a particular client. If your internal count differs too much from what the client says he has received, you just resend starting from the first message the client hasn't received. With wrapover, you can do this with just two additional bytes per message.
The best thing about your own reliability layer is that you can tune it for whatever your needs are: you may have some message types with reliability in them, while others without it. You can have various channels, each with its own sequence numbers, you can use those numbers to predict lag, and lots of other awesome stuff.
Don't run away from UDP because other people have told you "it's hard". They probably have never (seriously) used UDP, and probably have not even (seriously) used TCP. Actually UDP is much more simple and easier to understand than TCP.
Read Stevens' Unix Network Programming. And then reread it.