Краткий ответ: с использованием методов многопоточности.
Длинный ответ заключается в том, что события на самом деле являются просто сигналом, генерируемым другим кодом, который постоянно проверяет определенный набор обстоятельств. Например, очень простая часть кода, ответственная за вызов события DataAvailable, может выглядеть так:
While Socket Is Connected
If Data Is Available Raise Event DataAvailable
Loop
.Net-библиотека имеет класс Socket, который можно обернуть классом, который вы проектируете так, чтобы он вел себя немного больше, чем класс Winsock, который был доступен в VB6. Как только вы изучите документацию для класса Socket и узнаете, как создавать пользовательские события для класса, нетрудно представить, как может быть разработана такая оболочка. Реальное препятствие для большинства разработчиков из VB6 (и для меня) заключается в том, что вы должны немного узнать о многопоточных приложениях, чтобы они работали должным образом.
Вероятно, вы можете найти такую обертку, немного покопавшись, но я настоятельно рекомендую вам хотя бы попытаться написать свою собственную. Поскольку одноядерные машины быстро уйдут в прошлое, опыт использования многопоточных технологий станет требованием любого порядочного программиста.