Я считаю, что ваша проблема в том, что вы используете блокирующие, а не неблокирующие сокеты.
Когда вы используете блокирующие сокеты и отправляете 1M данных, сетевой стек может ждать, пока все данные будут помещены в буфер, если буферы заполнены, вы будете заблокированы, а индикатор выполнения будет ждать всего 1M для принятия в буферы, это может занять некоторое время, и ваш индикатор выполнения будет нервным.
Если, однако, вы используете неблокирующие сокеты, какой бы размер буфера вы не использовали, он не будет блокироваться, и вам придется самостоятельно ждать с помощью select / poll / epoll / what-works-on-your-platform (select - это самый портативный хотя). Таким образом, ваш индикатор будет быстро обновляться и отображать наиболее точную информацию.
Обратите внимание, что у отправителя индикатор выполнения частично нарушен, поскольку ядро буферизует некоторые данные, и вы достигнете 100%, прежде чем другая сторона действительно получит данные. Единственный способ обойти это, если ваш протокол включает в себя ответ на количество данных, полученных получателем.
Как уже говорили другие, второе предположение, что ОС и сеть в основном бесполезны, если вы продолжаете использовать блокирующие сокеты, выберите размер, который достаточно велик, чтобы включать больше данных, чем один пакет, чтобы вы не отправляли слишком мало данных в пакет, так как это без необходимости уменьшит вашу пропускную способность. Я бы пошел с чем-то вроде 4K, чтобы включить как минимум два пакета одновременно.