unit Server;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdContext, IdBaseComponent, IdComponent, IdCustomTCPServer,
IdTCPServer, IdCmdTCPServer, IdExplicitTLSClientServerBase, IdFTPServer,
IdIntercept, IdServerInterceptLogBase, IdServerInterceptLogFile, ShlObj;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
Edit3: TEdit;
edit2: TEdit;
edit1: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
IdFTPServer1: TIdFTPServer;
logfile1: TIdServerInterceptLogFile;
Label4: TLabel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure IdFTPServer1ChangeDirectory(ASender: TIdFTPServerContext;
var VDirectory: string);
procedure IdFTPServer1DeleteFile(ASender: TIdFTPServerContext;
const APathName: string);
procedure IdFTPServer1FileExistCheck(ASender: TIdFTPServerContext;
const APathName: string; var VExist: Boolean);
procedure IdFTPServer1GetFileDate(ASender: TIdFTPServerContext;
const AFilename: string; var VFileDate: TDateTime);
procedure IdFTPServer1GetFileSize(ASender: TIdFTPServerContext;
const AFilename: string; var VFileSize: Int64);
procedure IdFTPServer1MakeDirectory(ASender: TIdFTPServerContext;
var VDirectory: string);
procedure IdFTPServer1RetrieveFile(ASender: TIdFTPServerContext;
const AFileName: string; var VStream: TStream);
procedure IdFTPServer1RenameFile(ASender: TIdFTPServerContext;
const ARenameFromFile, ARenameToFile: string);
procedure IdFTPServer1RemoveDirectory(ASender: TIdFTPServerContext;
var VDirectory: string);
procedure IdFTPServer1UserLogin(ASender: TIdFTPServerContext;
const AUsername, APassword: string; var AAuthenticated: Boolean);
procedure IdFTPServer1StoreFile(ASender: TIdFTPServerContext;
const AFileName: string; AAppend: Boolean; var VStream: TStream);
procedure IdFTPServer1AfterUserLogin(ASender: TIdFTPServerContext);
procedure IdFTPServer1Exception(AContext: TIdContext;
AException: Exception);
procedure IdFTPServer1Status(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
procedure IdFTPServer1Stat(ASender: TIdFTPServerContext;
AStatusInfo: TStrings);
procedure IdFTPServer1Execute(AContext: TIdContext);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
homedir: string;
implementation
{$R *.dfm}
function setSlashes(APath:String):String;
var
slash:string;
begin
slash := StringReplace(APath, ‘/’, », [rfReplaceAll]);
slash := StringReplace(slash, », », [rfReplaceAll]);
Result := slash;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
idftpserver1.DefaultPort:=strtoint(edit3.text);
idftpserver1.Active:=true;
showmessage(‘Подключено’);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
idftpserver1.Active:=false;
close;
end;
procedure TForm1.IdFTPServer1AfterUserLogin(ASender: TIdFTPServerContext);
begin
//Установить домашний каталог
homedir:=’c:’;
end;
procedure TForm1.IdFTPServer1ChangeDirectory(ASender: TIdFTPServerContext;
var VDirectory: string);
begin
ASender.CurrentDir:= VDirectory;
end;
procedure TForm1.IdFTPServer1DeleteFile(ASender: TIdFTPServerContext;
const APathName: string);
begin
if fileexists(APathName) then
begin
DeleteFile(APathName);
end;
end;
procedure TForm1.IdFTPServer1Exception(AContext: TIdContext;
AException: Exception);
begin
showmessage(AException.Message);
end;
procedure TForm1.IdFTPServer1Execute(AContext: TIdContext);
begin
logfile1.DoLogWriteString(AContext.Connection.IOHandler.AllData);
end;
procedure TForm1.IdFTPServer1FileExistCheck(ASender: TIdFTPServerContext;
const APathName: string; var VExist: Boolean);
begin
if fileexists(APathName) then
begin
VExist:=true;
end
else
begin
VExist:=False;
end;
end;
procedure TForm1.IdFTPServer1GetFileDate(ASender: TIdFTPServerContext;
const AFilename: string; var VFileDate: TDateTime);
var
fdate:tdatetime;
begin
//Поставить дату файла в переменную
fdate:= FileAge(AFilename);
if not (fdate=-1) then begin
VFileDate:=fdate;
end;
end;
procedure TForm1.IdFTPServer1GetFileSize(ASender: TIdFTPServerContext;
const AFilename: string; var VFileSize: Int64);
Var
LFile : String;
rec:tsearchrec;
ASize: Int64 ;
begin
LFile := setslashes(homedir + AFilename );
try
if FindFirst(Lfile, faAnyFile, rec) = 0 then repeat
Asize:=rec.Size;
until FindNext(rec) <> 0;
finally
FindClose(rec);
end;
if Asize > 1 then
VFileSize:= Asize
else
VFilesize:=0;
end;
procedure TForm1.IdFTPServer1MakeDirectory(ASender: TIdFTPServerContext;
var VDirectory: string);
var
ldir:string;
begin
ldir:= setslashes(homedir + VDirectory);
if not DirectoryExists(ldir) then
if not CreateDir(ldir) then
raise Exception.Create(‘Невозможно создать ‘+ldir);
end;
procedure TForm1.IdFTPServer1RemoveDirectory(ASender: TIdFTPServerContext;
var VDirectory: string);
Var
LFile : String;
begin
LFile := setslashes(homedir + VDirectory);
if directoryexists(LFile) then begin
RemoveDir(LFile);
end
else
begin
Raise Exception.Create(‘Не удается удалить каталог’);
end;
end;
procedure TForm1.IdFTPServer1RenameFile(ASender: TIdFTPServerContext;
const ARenameFromFile, ARenameToFile: string);
begin
if not Renamefile(ARenameFromFile,ARenameToFile) then
begin
Raise Exception.Create(‘Не удается переименовать файл ‘);
end;
end;
procedure TForm1.IdFTPServer1RetrieveFile(ASender: TIdFTPServerContext;
const AFileName: string; var VStream: TStream);
begin
VStream := TFileStream.Create(setSlashes
(homedir+AFilename),fmOpenRead);
end;
procedure TForm1.IdFTPServer1Stat(ASender: TIdFTPServerContext;
AStatusInfo: TStrings);
var
i:integer;
begin
for i:= 0 to astatusinfo.Count-1 do
begin
memo1.Lines.Add(astatusinfo.Strings[i]);
end;
end;
procedure TForm1.IdFTPServer1Status(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
begin
memo1.lines.add(Astatustext);
end;
procedure TForm1.IdFTPServer1StoreFile(ASender: TIdFTPServerContext;
const AFileName: string; AAppend: Boolean; var VStream: TStream);
begin
if not Aappend then
VStream := TFileStream.Create(AFileName,fmCreate)
else
VStream := TFileStream.Create(AFileName,fmOpenWrite)
end;
procedure TForm1.IdFTPServer1UserLogin(ASender: TIdFTPServerContext;
const AUsername, APassword: string; var AAuthenticated: Boolean);
begin
if (AUsername = edit1.Text) and (APassword = edit2.Text) then
begin
AAuthenticated:=True
end
else
begin
AAuthenticated := False;
end;
end;
end.
Chertenok_n_13 218 / 124 / 99 Регистрация: 14.03.2011 Сообщений: 628 |
||||
1 |
||||
Обработка ошибки27.09.2012, 11:57. Показов 3973. Ответов 14 Метки нет (Все метки)
Такая возникла проблема.
и вообще, может ли вызывать эту ошибку то, что к порту уже подключен клиент?
0 |
2649 / 2270 / 279 Регистрация: 24.12.2010 Сообщений: 13,725 |
|
27.09.2012, 12:18 |
2 |
Зачем вообще в одном и том же приложении понадобился и сервер и клиент ?
0 |
218 / 124 / 99 Регистрация: 14.03.2011 Сообщений: 628 |
|
27.09.2012, 13:20 [ТС] |
3 |
mss, для передачи звука.
0 |
210 / 169 / 24 Регистрация: 24.04.2012 Сообщений: 615 |
|
27.09.2012, 13:49 |
4 |
Пыталась сделать, чтобы если порт у сервера занят уже, то поменять значения портов наоборот. И кем же он уже занят? И смысл менять, тогда клиент не сможет занять этот порт и опять исключение.
и вообще, может ли вызывать эту ошибку то, что к порту уже подключен клиент? По UDP никто никуда не подключается. Пакет шлется на адрес и порт, а открыт на том адресе тот порт или нет и дошел ли вообще пакет никого не волнует.
0 |
2649 / 2270 / 279 Регистрация: 24.12.2010 Сообщений: 13,725 |
|
27.09.2012, 13:57 |
5 |
клиент принимать не может Может. И в TCP и в UDP.
0 |
218 / 124 / 99 Регистрация: 14.03.2011 Сообщений: 628 |
|
27.09.2012, 19:35 [ТС] |
6 |
По UDP никто никуда не подключается. Пакет шлется на адрес и порт, а открыт на том адресе тот порт или нет и дошел ли вообще пакет никого не волнует. а откуда тогда ошибка Could not bind socket. Address and port are already in use?? Добавлено через 52 секунды
Может. И в TCP и в UDP. просто тогда придется разграничивать,что на одном запускается клиент, а на другом сервер. это удобно если два компьютера. а если больше?
0 |
2649 / 2270 / 279 Регистрация: 24.12.2010 Сообщений: 13,725 |
|
27.09.2012, 20:36 |
7 |
придется разграничивать,что на одном запускается клиент, а на другом сервер. это удобно если два компьютера. а если больше? Это я, наверно, со стенкой говорил. С дубовой.
0 |
218 / 124 / 99 Регистрация: 14.03.2011 Сообщений: 628 |
|
27.09.2012, 20:45 [ТС] |
8 |
Это я, наверно, со стенкой говорил. С дубовой. хорошо, что Вы в этом прекрасно разбираетесь. люди мне кажется тут за помощью обращаются.
1 |
2649 / 2270 / 279 Регистрация: 24.12.2010 Сообщений: 13,725 |
|
27.09.2012, 21:03 |
9 |
что-нибудь посоветовать Я УЖЕ посоветовал — выкинь из воего приложения либо IdUDPClient либо IdUDPServer.
0 |
218 / 124 / 99 Регистрация: 14.03.2011 Сообщений: 628 |
|
27.09.2012, 22:42 [ТС] |
10 |
mss, ты сказал, что вполне хватит. после этого я сказала для чего мне это надо и больше ты ничего вразумительного по этому поводу не сказал.
0 |
210 / 169 / 24 Регистрация: 24.04.2012 Сообщений: 615 |
|
28.09.2012, 00:33 |
11 |
Кладешь на форму IdUDPServer1, чтобы послать пакет вызываешь IdUDPServer1.Send(); , принимаешь процедурой IdUDPServer1UDPRead.
1 |
218 / 124 / 99 Регистрация: 14.03.2011 Сообщений: 628 |
|
28.09.2012, 11:00 [ТС] |
12 |
Nutserus, ага. Добавлено через 34 минуты
0 |
210 / 169 / 24 Регистрация: 24.04.2012 Сообщений: 615 |
|
28.09.2012, 11:53 |
13 |
порты должны же быть одинаковые? так? Одинаковые с чем? У тебя один компонент и один порт. Я конечно понимаю, читать сдк скучно и неинтересно, сразу хочется руками потрогать, тогда почему бы не скачать готовый исходник и пилить его под себя? Идея неновая и реализаций ее множество.
0 |
218 / 124 / 99 Регистрация: 14.03.2011 Сообщений: 628 |
|
28.09.2012, 12:12 [ТС] |
14 |
искала исходники. в одном направлении клиент-сервер есть и много, а сервер-сервер например ни разу не встретила тоже самое как клиент-клиент.
0 |
2649 / 2270 / 279 Регистрация: 24.12.2010 Сообщений: 13,725 |
|
28.09.2012, 19:02 |
15 |
У udp-компонента, в твоем случае например TIdUDPClient, есть методы приема и есть методы передачи данных. точно таким же образом ответить.
1 |
IT_Exp Эксперт 87844 / 49110 / 22898 Регистрация: 17.06.2006 Сообщений: 92,604 |
28.09.2012, 19:02 |
Помогаю со студенческими работами здесь Обработка ошибки Обработка ошибки Обработка ошибки Обработка ошибки Искать еще темы с ответами Или воспользуйтесь поиском по форуму: 15 |
What is this?
This knowledgebase contains questions and answers about PRTG Network
Monitor and network monitoring in general. You are invited to get involved by asking and
answering questions!
Learn more
Intuitive to Use. Easy to manage.
More than 500,000 users rely on Paessler PRTG every day. Find out how you can
reduce cost, increase QoS and ease planning, as well.
Free Download
After upgrading to the latest version I am getting this error on three sensors. One is an Amazon CloudWatch sensor, one is an HTTP sensor and the other is an FTP sensor. All are on the same server. Restarting the probe sometimes will bring one of them back, but then the others throw the error. The sensors were fine before the upgrade.
cloudwatch
ftp
sockets
Best Answer
Accepted Answer
If this message is shown by PRTG sensors, this means that the PRTG Probe is not able to open any outgoing TCP ports for outgoing monitoring requests.
There are two possible reasons for the message «Could not bind socket. Address and port are already in use»:
- In the probe settings you have selected an outgoing IP (and with that a network interface) that can not reach the desired target system. The IP stack on the probe system tries to open an outgoing port but the selected network interface can’t provide it.
- Sometimes overloading situations (which can be caused by PRTG, or other programs, or other problems) can cause the IP stack of the probe system to run out of outgoing port numbers. This can sometimes be cured by rebooting the probe system.
Disclaimer: The information in the Paessler Knowledge Base comes without warranty of any kind. Use at your own risk. Before applying any instructions please exercise proper system administrator housekeeping. You must make sure that a proper backup of all your data is available.
Корректное отключение
Для корректного завершения сетевого подключения обе стороны должны послать пакеты с сигналом о завершении (FIN), которые указывают что стороны не будут больше отсылать данные, также каждая сторона должна подтвердить (ACK) получение сигнала о завершении сетевого обмена данными. FIN инициируется когда приложение вызывает метод close(), shutdown() или exit(). После завершения работы метода close() ядро переходит в режим ожидания подтверждения от второй стороны приема сигнала о завершении. Это делает возможной ситуацию когда процесс инициировавший отключение будет завершен прежде чем ядро освободит рессурсы связанные с подключением, и снова разрешит использовать порт для связывания с другим процесоом (в этом случае, при попытке использования порта мы получим исключение AddressAlreadyInUse).
На изображении:
- Имеется установленное соединение, состояние ESTABLISHED
- Клиент инициирует окончание подключения, посылает серверу сигнал о завершении подключения (FIN), переходит в состояние ожидания ответа сервера (FIN_WAIT_1)
- Сервер получает сигнал о завершении подключения и отправляет подтверждение (ACK), переходит в состояние ожидания завершения подключения (CLOSE_WAIT) (вызывает close())
- Сервер отсылает клиенту сигнал о том что успешно закрыл подключение (FIN) и пробует прочитать подтверждение клиента (ACK), после чего не дожидаясь его отключается.
- Теперь клиенту может придти два сигнала в разной очередности
ACK — клиент получил подтверждение о том что сервер понял его намерение закрыть подключение
- Клиент переходит в состояние ожидания сигнала об окончании закрытия подключения (FIN) от сервера (FIN_WAIT_2)
- Клиент получает сигнал о закрытии подключения сервером (FIN), отправляет подтверждение (ACK), некоторое время ждет(TIME_WAIT) и отключается (ядро освобождает рессурсы) (CLOSED)
FIN — клиент получает сигнал о закрытии подключения на стороне сервера(FIN), раньше чем подтверждение от сервера (ACK), о получении инициирующего сигнала о закрытии от клиента (FIN)
- Клиент отправляет подтверждение приема сигнала о том что сервер закрывает соединение, и переходит в состояние отключения (CLOSING)
- После отключения пробует считать сигнал подтверждения от сервера (который был отправлен сервером сразу после получения от клиента сигнала о завершении работы, пункт 2), некоторое время ожидает(TIME_WAIT) и ядро освобождает рессурсы (CLOSING).
На рисунке показаны все воможные состояния, которые могут быть во время корректного завершения, в зависимости от порядка получения пакетов FIN и ACK от удаленной стороны. Обратите внимание, если вы инициировали завершение подключения (левая половина рисунка), то другая сторона не будет ожидать подтверждения получения вами пакета FIN (правая половина рисунка). Сотояние TIME_WAIT требуется на случай если подтверждение (ACK) которое вы отправили не было получено на другой стороне, или на случай появления ложных пакетов по какой-то причине. Я не знаю почему на стороне сервера не сделали состояние TIME_WAIT, хотя если клиент инициирует закрытие, это безусловно и не должно требовать ожидания. Состояние TIME_WAIT может удерживать порт в течение нескольких минут после завершения процесса. Время удержания варьируется в зависимости от операционной системы, в некоторых операционных системах оно является динамическим, стандартные значения лежат в диапазоне от 1 до 4 минут.
Если обе стороны успеют инициировать сигнал завершения, раньше чем получат его от другой стороны, то обе стороны будут вынуждены пройти через ожидание (TIME_WAIT).
Корректное отключение слушающей стороны
Слушающий сокет может быть закрыт немедленно, при отсутствии входящих подключений, его состояние переходит сразу в CLOSED. При наличии входящих подключений, будет произведен переход к FIN_WAIT_1 и затем к TIME_WAIT.
Обратите внимание, на стороне слушающего сокета невозможно гарантировать чистое закрытие. Пока вы проверяете использование соединения методом select() до закрытия, существует крошечная, но реальная возможность появления входящего подключения после вызова select() и до вызова close().
Непредвиденное отключение удаленной стороны
При внезапном отключении сервера, локальная сторона инициирует закрытие соединения, и в этом случае TIME_WAIT неизбежен. Если удаленная сторона исчезает из-за сбоя сети или перезагрузки машины (редкие случаи), локальный порт будет оставаться привязанным вплоть до истечения таймаута состояния TIME_WAIT. Хуже того, некоторые старые операционные системы, не реализуют таймаут для состояния FIN_WAIT_2, и могут оставаться в нем бесконечно долго, в этом случае спасти может только перезагрузка системы.
Если же локальное приложение (клиент) падает в процессе активного соединения, порт будет занят пока не завершится состояние TIME_WAIT, то же верно для приложений, закрытых в процессе подключения к удаленной стороне(pending).
Способы избежания проблем
Опция SO_REUSEADDR
Можно использовать метод setsockopt(), для установки опции SO_REUSEADDR, что позволит создавать привязку к порту даже если он еще находится в состоянии TIME_WAIT (привязка к порту будет разрешена только для одного процесса). Это самый простой и эффективный метод избежать сообщения «address already in use».
Но, как ни странно, использование опции SO_REUSEADDR, может привести к более трудноотлавливаемым ошибкам чем «address already in use». SO_REUSEADDR позволяет использовать порт застрявший в TIME_WAIT, но вы все еще сможете использовать этот порт в том процессе, в котором он привязан изначально.
WHAT?
Предположим я использую локальный порт 1010 и подключаюсь на порт 300 сервера foobar.com, затем клиент отключается и порт переходит в состояние TIME_WAIT, и я могу использовать этот порт (1010) в любом подключении за исключением подключения к foobar.com на порт 300.
Ситуация в которой это может вызвать проблему может быть такой: моя программа пытается найти зарезервированный локальный порт(<1024) для привязки, чтобы подключиться к службе которая требует зарезервированный порт, и если я буду использовать опцию SO_REUSEADDR, то при каждом запуске программы на моей машине я буду получать тот же зарезервированный порт, даже если он висит в TIME_WAIT, и могу получить «Address already in use», в том месте где порт был использован в последний раз. В этом случае нужно отказаться от использования опции SO_REUSEADDR.
Некоторые не любят использовать SO_REUSEADDR, т.к. эта опция имеет проблемы с безопасностью. В некоторых операционных системах эта опция может позволить разным процессам использовать один и тот же порт одновременно. И это проблема, потому что большинство серверов привязываются к порту не используя конкретный адрес, вместо этого они используют INADDR_ANY (команда netstat отобразит их как *.8080). Таким образом, если сервер связывается с адресом *.8080, то другой процесс, от другого пользователя локальной машины, может подключиться к адресу local_machine.8080 (и намерения его могут быть совсем не хорошими), и перехватывать все ваши подключения, т.к. он указал более конкретный адрес. Эта проблема проявляется только на многопользовательских системах, не имеющих ограничений для учетных записей, и это не является уязвимостью которая доступна снаружи локальной машины, ее можно легко избежать используя привязку к конкретному адресу машины (не используя INADDR_ANY).
Другим не нравится что ядро системы тратит свои рессурсы на сотни или даже тысячи TIME_WAIT состояний, этой проблемы также можно избежать используя подход описанный ниже.
Клиент отключается первым
Глядя на рисунок выше, мы видим, что состояния TIME_WAIT можно избежать когда закрытие инициируется на удаленной стороне, а значит проблем можно избежать, если сервер позволяет клиенту инициировать отключение первым. Для этого можно построить архитектуру пользовательского протокола таким образом, что клиент знает когда ему нужно инициировать закрытие. Сервер может произвести безопасное отключение получив команду EOF от клиента, однако нам все равно придется установить таймаут ожидания отключения клиента, чтобы тот смог корректно завершить работу. Почти всегда достаточно подождать несколько секунд, пока соединение с сервером не будет корректно завершено.
Эту концепцию, вероятно, имеет смысл назвать «удаленная сторона отключается первой», иначе мы будем зависить от того что мы называем клиентом и что сервером. Если вы разрабатываете некую систему, состоящую из нескольких клиентских программ, которые находятся на одной машине и обращаются к разным серверам, то вы захотите перенести ответственность за отключение на сервера, для сберегания рессурсов клиентской машины.
К примеру, я написал скрипт который использует remote shell(rsh), для общения со всеми машинами моей сети, и он выполняет работу параллельно, постоянно используя несколько открытых соединения. Для rsh доступно меньше 1024 портов. Сначала я использовал команду «rsh -n», которая вызывает отключение локальной стороны в первую очередь. После нескольких тестов все свободные порты меньше 1024, оказались в состоянии TIME_WAIT, и процесс остановился. Удаление опции -n приводит к инициированию отключения на удаленной стороне, и проблема TIME_WAIT устраняется, однако это может привести rsh к зависанию в ожидании входаящего подключения. И если вы закрываете входящее подключение локально, порт снова окажется в состояние TIME_WAIT. В конечном счете я просто отказался от использования rsh и написал свою реализацию на perl (текущую версию можно скачать тут)
Уменьшение таймаута
Если, по какой-то причине, ни один из изложенных вариантов вам не подходит, есть возможность сократить таймаут состояния TIME_WAIT. Возможность и реализация такой операции зависит от операционной системы которую вы используете. Стоить помнить, что слишком короткий таймаут может иметь негативные последствия, в частности при потере пакетов или в перегруженных сетях.