I suspect it's due to connection pooling. It's not really SQL Server either, it's the network library, or ADO on TCP/IP.

The point of connection pooling is to sort of recycle connections to the database. Let's say the database allows 10 concurrent connections, or a pool of 10, the database server will disconnect connections that have been idling for x amount of time so that it can accept other incoming connections. The disconnected connection is still in memory, and reconnecting it takes a lot less time than when a closed connection opens. I think it is possible to turn connection pooling off by passing POOLING=OFF in the connection string, but it's an optimization and setting it to off may have a performance hit, atlthough I'd try it as a last resort.

In my test code I "drop" the connection by using KILL on the spid in SQL Server, because this kind of error is hard to get in a dev. scenario. The front-end effect is the same- the connection gets no notification of it's disconnected state.