1.сокет TCP представляет собой поток (пакеты являются подробностями реализации)
сетевой стек оптимизирован для ограничения количества отправляемых пакетов.таким образом, когда вы вызываете send()
несколько раз в сокете, сетевой стек может свободно разделять (или не разделять) данные между несколькими пакетами.
на компьютере, работающем в реальной сети, которая намного медленнее петлевого соединения, если у вас все еще есть пакет, ожидающий отправки во время вызова send()
, то новые данные добавляютсяв ожидании пакета.на принимающей стороне сетевой стек может свободно объединять несколько пакетов (данные, полученные из сокета, буферизируются), чтобы представить вам данные сразу.
поэтому на сервере вы пишете:
for record in info:
self.channel.send(record)
self.channel.send("#p")
последний пакет может содержать конец программы И терминатор, объединенный вместе.в клиенте:
a = client.recv(1024)
if a=="#p":
break
терминатор может не находиться в начале принятого пакета и не может быть единственными символами, существующими в пакете.в этом случае вы не обнаружите терминатор, не выйдете из цикла, снова вызовете recv()
и остановитесь, поскольку recv()
является блокирующим вызовом, и сервер больше никогда не будет отправлять данные.
Итак, вам нужно выбрать другой способ узнать, когда сервер завершил отправку данных.Есть много возможностей:
- Первый очевидный способ - на клиенте, вместо
if a=="#p":
, напишите if a.endswith("#p"):
.это устраняет проблему, существующую в вашем коде - , вы также можете сначала отправить длину программы перед ее отправкой.клиент считывает длину, затем считывает это количество символов и останавливается
- , когда сервер может просто закрыть соединение после завершения отправки программы.в клиенте вы обнаружите, что соединение закрыто, когда
recv()
возвращает пустую строку, тогда вы знаете, что программа была получена.к сожалению, если на сервере произойдет сбой перед отправкой всей программы, вы запустите на клиенте незавершенную программу
, существует множество других возможностей исправить эту проблему ...
2.Ваша логика приема неверна
Теперь рассмотрим логику приема (код отредактирован для удаления ненужных строк) :
1. a=client.recv(1024)
2. b=a
3. f=1
4. while f:
5. a = client.recv(1024)
6. if a=="#p":
7. f=0
8. break
9. b+=a
здесь,сначала вы ждете некоторые данные (строка 1).затем вы вводите цикл while (строка 4) и снова ждете каких-то данных (строка 5)!
recv()
- это блокирующий вызов: это означает, что он не вернется, пока не будет данных для возвратаили соединение закрыто.если сервер отправляет менее 1024 байтов, включая терминатор, вы получаете все в строке 1 (включая терминатор), но все еще ждете дополнительных данных в строке 5 ...
обратите внимание, что в баге по-прежнему есть ошибкаваш тест: если разделитель разделен между 2 recv()
вызовами (что происходит, если длина программы ровно 1023 байта), a.endswith('#p')
никогда не будет иметь значение True
.
, поэтому здесь правильный приемлогика, которая проверяет терминатор, как только некоторые данные получены:
a = client.recv(1024)
b = a
f = 1
while f:
if b.endswith("#p"):
f=0
break
a = client.recv(1024)
b += a
обратите внимание, что это можно значительно упростить, удалив ненужные переменные:
b = client.recv(1024)
while not b.endswith("#p"):
b += client.recv(1024)
3,правильно освободите ресурсы, используемые в вашем коде
Сначала я не подчеркивал этот момент, но вы всегда должны правильно закрывать соединение!
язык python сделан таким образом, что соединение будет неявно закрыто, как только на переменную, на которую он ссылается, больше не будет ссылаться (в вашем случае, когда оно выходит из области видимости).однако его явное закрытие прояснит, в какой момент вы ожидаете, что соединение закроется.
некоторая реализация может решить отложить выпуск ресурса на более позднее время (пользователи Jython могут столкнуться с этой проблемой), оставив соединение открытым ... это не проблема, но может привести к странному поведению позжекогда ваша программа превратится в более функциональный продукт.