---
title: "STM Lesson 95. LAN. W5500. FTP Server. Part 2"
url: "https://maker.wiznet.io/teddy/projects/stm-lesson-95-lan-w5500-ftp-server-part-2/"
markdown_url: "https://maker.wiznet.io/teddy/projects/stm-lesson-95-lan-w5500-ftp-server-part-2/md"
type: "UCC: User Created Content"
author: "Narod stream"
author_url: "https://narodstream.ru/stm-urok-95-lan-w5500-ftp-server-chast-2/"
editor: "WIZnet"
editor_url: "https://maker.wiznet.io/"
original_author: "Narod stream"
original_url: "https://narodstream.ru/stm-urok-95-lan-w5500-ftp-server-chast-2/"
published: "2023-08-02"
language: "en"
likes: 0
views: 376
comments: 0
source: "WIZnet Makers (https://maker.wiznet.io/)"
---

# STM Lesson 95. LAN. W5500. FTP Server. Part 2

> STM Lesson 95. LAN. W5500. FTP Server. Part 2

Original author: Narod stream (source: https://narodstream.ru/stm-urok-95-lan-w5500-ftp-server-chast-2/)

## Article

B [**previous part**](https://narodstream.ru/stm-urok-95-lan-w5500-ftp-server-chast-1/) classes we set up the project, slightly improved the code in terms of working with the HTTP server, and also created the very first functions for working with the FTP protocol.

Let's get back to the project in our function **ftp_receive** in file **ftpd.c** in the same condition, but only in his nasty body ( else ). This body will be executed provided that the package is received if we have already created a control connection. We add another condition to this body that will check that the package came with ( data is not empty ). Why do we need to respond to empty packages

`ftpprop.``connect_stat``= FTP_CONNECT;`

`}`

`else`

`{`

`** //проверим, что пакет не пустой**`

`** if**``**(len > 0)**`

`** {**`

`** }**`

`}`

Now go to the file **w5500.c** and add another pointer installation function there in the reading buffer after function **GetReadPointer**

`**//-----------------------------------------------**`

`**void**``** SetReadPointer(**``**uint8_t**``** sock_num, **``**uint16_t**``** point)**`

`**{**`

`** uint8_t**``** opcode;**`

`** opcode = (((sock_num<<2)|BSB_S0)<<3)|OM_FDM1;**`

`** w5500_writeReg(opcode, Sn_RX_RD0, point>>8);**`

`** w5500_writeReg(opcode, Sn_RX_RD1, (**``**uint8_t**``**)point);**`

`**}**`

`**//-----------------------------------------------**`

Add a prototype to this function in the title file.

Let's go back to the file **ftpd.c** and fill the body of the conditions we just created in the function **ftp_receive**

`if``(len > 0)`

`{`

`** //Отобразим размер принятых данных**`

`** sprintf**``**(str1,**``**"S%d len buf:0x%04Xrn"**``**,sn,len);**`

`** HAL_UART_Transmit(&huart2,(**``**uint8_t**``***)str1,**``**strlen**``**(str1),0x1000);**`

`** //примем данные**`

`** point = GetReadPointer(sn);**`

`** //Отобразим адрес данных**`

`** sprintf**``**(str1,**``**"S%d point RX:0x%04Xrn"**``**,sn,point);**`

`** HAL_UART_Transmit(&huart2,(**``**uint8_t**``***)str1,**``**strlen**``**(str1),0x1000);**`

`** w5500_readSockBuf(sn, point, sect, len);**`

`** //Передвинем указатель**`

`** SetReadPointer(sn, point+len);**`

`** RecvSocket(sn);**`

`** //завершим нулём**`

`** sect[len] = 0;**`

`** //отобразим их в терминальной программе**`

`** sprintf**``**(str1,**``**"S%d: %srn"**``**, sn, (**``**char**``***)sect);**`

`** HAL_UART_Transmit(&huart2,(**``**uint8_t**``***)(**``**char**``***)sect,**``**strlen**``**((**``**char**``***)sect),0x1000);**`

`}`

The procedure for receiving package data, written in this body, is standard, there is more information in terminal programs. But for now, let her be. We are going to ensure that we are normally in the process of creating code. In this section of the code, we place the accepted data in the sect array and then display it in the terminal program.

We collect the code, send the controller and again try to connect to the FTP server, looking at the information in the terminal program

![Image06](https://narodstream.ru/wp-content/uploads/2017/10/Image06.png)

All commands are accepted and nomally displayed in the terminal program.

Now we need to somehow respond to these teams.

We create the above function of parsing the package data bar, immediately creating a number of variables, pointers and arrays

`**//-----------------------------------------------**`

`**void**``** ftp_cmd_parse(**``**uint8_t**``** sn, **``**char**``*** buf)**`

`**{**`

`** char**``cmd_point, *ch, *arg, *ch_tmp;**`

`** char**``** buf_send[200];**`

`** uint16_t**``** len;**`

`** uint16_t**``** offset;**`

`** data_sect_ptr**``** *datasect = (**``**void**``***)buf_send;**`

`**}**`

`**//-----------------------------------------------**`

Call this function in the body conditions at the end of the code we just added

`HAL_UART_Transmit(&huart2,(``uint8_t``*)(``char``*)sect,``strlen``((``char``*)sect),0x1000);`

`**ftp_cmd_parse(sn, (**``**char**``***)sect);**`

`}`

`}`

`}`

`else``if``(sn == FTP_SOCKET_DATA)`

We will create a global array of commands presented in strict form and converted to lowercase, since FTP has no restrictions on this score and commands can come in different registers. By the number of string elements, the array must converge in the title file

`extern``volatile``uint16_t``tcp_size_wnd;`

`//-----------------------------------------------`

`**/* Command table */**`

`**static**````**char**``** *ftp_commands[] = {**`

`**"user"**``**,**`

`**"acct"**``**,**`

`**"pass"**``**,**`

`**"type"**``**,**`

`**"list"**``**,**`

`**"cwd"**``**,**`

`**"dele"**``**,**`

`**"name"**``**,**`

`**"quit"**``**,**`

`**"retr"**``**,**`

`**"stor"**``**,**`

`**"port"**``**,**`

`**"nlst"**``**,**`

`**"pwd"**``**,**`

`**"xpwd"**``**,**`

`**"mkd"**``**,**`

`**"xmkd"**``**,**`

`**"xrmd"**``**,**`

`**"rmd "**``**,**`

`**"stru"**``**,**`

`**"mode"**``**,**`

`**"syst"**``**,**`

`**"xmd5"**``**,**`

`**"xcwd"**``**,**`

`**"feat"**``**,**`

`**"pasv"**``**,**`

`**"size"**``**,**`

`**"mlsd"**``**,**`

`**"appe"**``**,**`

`**NULL**`

`**};**`

`**//-----------------------------------------------**`

Let's get back to function now **ftp_cmd_parse** and start composing her body, and it promises to be considerable.

Convert the incoming command to the lower case as well, converting the characters to the first space or to the end of the data

`data_sect_ptr``*datasect = (``void``*)buf_send;`

`**//преобразуем к нижнему регистру**`

`**for**``** (ch = buf; *ch != **``**' '**``** && *ch != **``**''**``**; ch++) *ch = tolower(*ch);**`

Next, look for a team on our list.

`for``(ch = buf; *ch != ``' '``&& *ch != ``''``; ch++) *ch = tolower(*ch);`

`**// найдём команду в списке**`

`**for**``** (cmd_point = ftp_commands; *cmd_point != NULL; cmd_point++)**`

`**{**`

`** if**``** (**``**strncmp**``**(*cmd_point, buf, **``**strlen**``**(*cmd_point)) == 0) **``**break**``**;**`

`**}**`

If we don’t find it, we will send in response that the team is non-existent and leave the function

`if``(``strncmp``(*cmd_point, buf, ``strlen``(*cmd_point)) == 0) ``break``;`

`}`

`**if**``** (*cmd_point == NULL)**`

`**{**`

`** sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"500 Unknown command '%s'rn"**``**, buf);**`

`** tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, **``**strlen**``**((**``**char**``***)datasect->**``**data**``**));**`

`** return**``**;**`

`**}**`

Next, add a condition that finds out if the client is authorized on the server or not yet

`return``;`

`}`

`**if**``** (ftpprop.**``**login_stat**``** == FTPS_NOT_LOGIN)**`

`**{**`

`**}**`

Further in the body of this condition we add a condition of the switch type, which will recognize team options in which the reaction to three types of teams will exit the condition, and if other teams come as famous, since the unknown we have already filtered, we will send a message to the client that he is not yet atorized and will leave the function completely

`if``(ftpprop.``login_stat``== FTPS_NOT_LOGIN)`

`{`

`** switch**``**(cmd_point - ftp_commands)**`

`** {**`

`** case**````***USER_CMD***``**:**`

`** case**````***PASS_CMD***``**:**`

`** case**````***QUIT_CMD***``**:**`

`** break**``**;**`

`** default**``**:**`

`** len = **``**sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"530 Please log in with USER and PASSrn"**``**);**`

`** tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, len);**`

`** return**``**;**`

`** }**`

`}`

Next, let's get out of the condition of non-authorization and set the pointer to the argument of the team

`return``;`

`}`

`}`

`**//установим указатель на аргумент команды клиента**`

`**arg = &buf[**``**strlen**``**(*cmd_point)];**`

`**while**``**(*arg == **``**' '**``**) arg++;**`

Then we begin to explore which team came to us.

Add for this **switch**

`while``(*arg == ``' '``) arg++;`

`**switch**``** (cmd_point - ftp_commands)**`

`**{**`

`**}**`

And now let's start adding various team options to it.

At the moment, according to the traffic analyzer, we saw that the client sent us the USER command with the username argument for processing. Here we will process it, at the same time add the default branch

`switch``(cmd_point - ftp_commands)`

`{`

`** case**````***USER_CMD***``** :**`

`** break**``**;**`

`** default**``**: **``**// Invalid**`

`** break**``**;**`

`}`

Let's start with default, so as not to forget later.

`default``: ``// Invalid`

`** len =**``**sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"500 Unknown command '%s'rn"**``**, arg);**`

`** tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, len);**`

`** HAL_Delay(1000);**`

`** DisconnectSocket(sn); **``**//Разъединяемся**`

`** SocketClosedWait(sn);**`

`** sprintf**``**(str1,**``**"S%d closedrn"**``**,sn);**`

`** HAL_UART_Transmit(&huart2,(**``**uint8_t**``***)str1,**``**strlen**``**(str1),0x1000);**`

`** OpenSocket(sn,Mode_TCP);**`

`** //Ждём инициализации сокета (статус SOCK_INIT)**`

`** SocketInitWait(sn);**`

`** //Продолжаем слушать сокет**`

`** ListenSocket(sn);**`

`** SocketListenWait(sn);**`

`break``;`

Here we behave in a standard way: we separate the socket, reopen it and start listening, while displaying some information about this in the terminal program. I turned on the delay to avoid very quick looping, I think it won’t hurt much.

Now the actual processing of the team **USER**.

`case``*USER_CMD*``:`

`** sprintf**``**(str1, **``**"USER_CMD : %s"**``**, arg);**`

`** HAL_UART_Transmit(&huart2,(**``**uint8_t**``***)str1,**``**strlen**``**(str1),0x1000);**`

`** //отправим ответ на команду**`

`** len = **``**strlen**``**(arg);**`

`** arg[len - 1] = 0x00;**`

`** arg[len - 2] = 0x00;**`

`** strcpy**``**(ftpprop.**``**username**``**, arg);**`

`** len = **``**sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"331 Enter PASS commandrn"**``**);**`

`** tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, len);**`

`break``;`

Here we show in the terminal program a team with an argument to see if we correctly extracted it, then measure its length, reset the carriage return and the transition to a new line, copy to the structure field with properties, in the future it will be the name of the client’s user, then we send the next message to the client with a password request. Here we applied a new type of string length measurement when using the sprintf function. This function returns it as an outgoing argument.

Let's go to the file now **w5500.c** and connect there a variable of our structure with properties

`extern``http_sock_prop_ptr``httpsockprop[8];`

`**extern**````**ftp_prop_ptr**``** ftpprop;**`

Also almost forgot. It is necessary to accept and process packages **w5500_packetReceive** handle the status of the disabled socket. This will be required if the socket is disconnected for some unknown reasons, for example, at the initiative of the client. When we turn it off intentionally, then we will again create a connection with it. And there are other moments.

Therefore, we will create a nasty case of an open socket in this function and write the corresponding code there

`http_receive(sn);`

`}`

`}`

`**else**`

`** {**`

`** //управляющий сокет FTP**`

`** if**``**(sn==FTP_SOCKET_CTRL)**`

`** {**`

`** if**``**(ftpprop.**``**connect_stat**``**) ftpprop.**``**connect_stat**``**=FTP_DISCONNECT;**`

`** if**``**(ftpprop.**``**login_stat**``**==FTPS_LOGIN) ftpprop.**``**login_stat**``**=FTPS_NOT_LOGIN;**`

`** SetReadPointer(FTP_SOCKET_CTRL, 0);**`

`** }**`

`** }**`

`}`

That is, in this case, we re-initialize the settings and set the buffer pointer to 0.

Let's now collect the code and sew the controller and follow the response to the USER command from the server, and also see what the client answers us.

Here is the result in the terminal program

![Image07](https://narodstream.ru/wp-content/uploads/2017/10/Image07.png)

We see that the client answered us with a password transfer.

We will also see what is happening in our traffic analyzer.

![Image08](https://narodstream.ru/wp-content/uploads/2017/10/Image08.png)

Here we also see that our client received our message and answered us with his password, to which the server replied that this was an unknown team for him. Of course, it is unknown to the server until we have processed it yet.

This is what we will do. Let's go back to the file **ftpd.c** and add the client authorization function over the command parsing function

`**//-----------------------------------------------**`

`**void**``** ftp_login(**``**uint8_t**``** sn, **``**char**``** * pass)**`

`**{**`

`** char**``** buf_send[100];**`

`** uint16_t**``** len;**`

`** data_sect_ptr**``** *datasect = (**``**void**``***)buf_send;**`

`** len = **``**sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"230 Logged onrn"**``**);**`

`** tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, len);**`

`** ftpprop.**``**login_stat**``** = FTPS_LOGIN;**`

`**}**`

`**//-----------------------------------------------**`

In this function, we set the data for transmission, transmit a message about the successful authorization of the client, and also set the structure field with the authorization property to authorized. As such, we will not have authorization in fact, we do not check the validity of the couple, we simply agree with everything that the client sends us.

Now back to our switch in function **ftp_cmd_parse** and process the team there **PASS**

`break``;`

`**case**````***PASS_CMD***``** :**`

`** len = **``**strlen**``**(arg);**`

`** arg[len - 1] = 0x00;**`

`** arg[len - 2] = 0x00;**`

`** ftp_login(sn, arg);**`

`** break**``**;**`

`default``: ``// Invalid`

Everything is simple here. We also reset the line translation and carriage return and call the client authorization function.

We will collect the code, we will send the controller and see what the client will require from us this time.

The result in the terminal program

![Image09](https://narodstream.ru/wp-content/uploads/2017/10/Image09.png)

Customer sent us a team **Syst**, which is a request for identification of the operating system, for which we should tell him a little about our server.

We will also see a traffic analyzer in which, of course, we will see the client being sent that this team is unknown to us ( bye )

![Image10](https://narodstream.ru/wp-content/uploads/2017/10/Image10.png)

Moreover, the client turned out to be a very polite client and was not offended by our shameful response about the team’s ignorance and requested the next team. But, I think, there may be such cases when an offending client comes across, who will simply break the connection with us, so let's still answer something to him

`break``;`

`**case**````***SYST_CMD***``** :**`

`** len = **``**sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"215 UNIX emulated by W5500rnrn"**``**);**`

`** tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, len);**`

`** break**``**;**`

`default``: ``// Invalid`

Check the client’s reaction, for which we will collect the code and sew the controller.

The same reaction — the client sent a team **Feat**, which serves to coordinate functions, that is, the client asks now to tell more about the server in more detail what he can.

We will answer him with our capabilities

`break``;`

`**case**````***FEAT_CMD***``** :**`

`** len = **``**sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"211-Features:rn MDTMrn REST STREAMrn SIZErn MLST size*;type*;create*;modify*;rn MLSDrn UTF8rn CLNTrn MFMTrn211 ENDrn"**``**);**`

`** tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, len);**`

`** break**``**;**`

`default``: ``// Invalid`

These are our opportunities. There is also a requirement that after listing all the possibilities you need to send another word **END**, so that the client realizes that he had the last property of the server. What are these opportunities, I will not list here, as this can be found in special sources via FTP.

We will collect the project, sew the controller and check the result immediately in the Wireshark traffic analyzer, since there is still more information there, in the terminal program we do not see a report on the packages that came from us

![Image11](https://narodstream.ru/wp-content/uploads/2017/10/Image11.png)

We see that the answer has come from us. Let's see how Wireshark decrypted it

![Image12](https://narodstream.ru/wp-content/uploads/2017/10/Image12.png)

So, the traffic analyzer saw everything beautifully.

The next command is sent to us by the CWD client, sees the server’s response about the unknown, and then sends food and PWD.

The first CWD — command is a customer request to change the current directory. The name of the current directory the client transfers in the argument. The client asks to go to the root directory, so as an argument he transmits a slash ( to the slash ).

Slowly start answering him

`break``;`

`**case**````***CWD_CMD***``**:**`

`** len = **``**strlen**``**(arg);**`

`** arg[len - 1] = 0x00;**`

`** arg[len - 2] = 0x00;**`

`** //если не подкаталог**`

`** if**``**(arg[0]==**``**'/'**``**)**`

`** {**`

`** strcpy**``**(ftpprop.**``**work_dir**``**, arg);**`

`** }**`

`** break**``**;**`

`default``: ``// Invalid`

Here we enter in the field with the name of the current directory ( folders, directories ) the transmitted argument. Only this if the client passed the root directory as an argument.

Next, we process the transfer by the client as an argument the transition to a level higher. This is such a case when the client asks from any catalog, which is currently to go out and go to the upper level. In this case, the client gives us two points in the argument

`strcpy``(ftpprop.``work_dir``, arg);`

`}`

`**//переход на верхний уровень**`

`**else**````**if**``**(**``**strncmp**``**(arg,**``**".."**``**, 2) == 0)**`

`**{**`

`**}**`

`break``;`

Now let's start writing the body of this transition to a level higher

`else``if``(``strncmp``(arg,``".."``, 2) == 0)`

`{`

`** //отрежем каталог самого нижнего уровня**`

`** //Найдём последний символ '/'**`

`** offset = 0;**`

`** while**``**(1)**`

`** {**`

`** ch_tmp = **``**strchr**``**(ftpprop.**``**work_dir**``**+offset,**``**'/'**``**);**`

`** if**``**(!ch_tmp)**`

`** {**`

`** ftpprop.**``**work_dir**``**[offset] = **``**''**``**;**`

`** break**``**;**`

`** }**`

`** ch = ch_tmp;**`

`** offset = ch_tmp - ftpprop.**``**work_dir**``** + 1;**`

`**}**`

Here we use the name of the current directory of the structure stored in our field with the properties of the server. we will try to cut off the directory of the lowest level from it so that the directory of the higher level becomes the current directory. To do this, we are looking for the last slash character and, accordingly, insert 0 into this place, which will correspond to the end of the line. Then we remove the slash, if the name of the catalog ends, we do not need it. This does not apply to the root directory

`offset = ch_tmp - ftpprop.``work_dir``+ 1;`

`}`

`** //отрежем символ '/', если подкаталог**`

`** len = **``**strlen**``**(ftpprop.**``**work_dir**``**);**`

`** if**``** (len>1)**`

`** {**`

`** if**``**(ftpprop.**``**work_dir**``**[len-1]==**``**'/'**``**)**`

`** {**`

`** ftpprop.**``**work_dir**``**[len-1]=0;**`

`** }**`

`** }**`

`}`

`break``;`

Then we leave this condition ( not from the cycle, but from the condition completely and we will process all other conditions

`ftpprop.``work_dir``[len-1]=0;`

`}`

`}`

`}`

`**else**`

`**{**`

`** //дополним путь символом '/' в конце, если его нет**`

`** len = **``**strlen**``**(ftpprop.**``**work_dir**``**);**`

`** if**``**(ftpprop.**``**work_dir**``**[len-1]!=**``**'/'**``**) **``**strcat**``**(ftpprop.**``**work_dir**``**,**``**"/"**``**);**`

`** strcat**``**(ftpprop.**``**work_dir**``**, arg);**`

`**}**`

`break``;`

Here, on the contrary, we will supplement the name with a slash.

Then we leave this body and give the client a response to the message

`strcat``(ftpprop.``work_dir``, arg);`

`}`

`**len = **``**sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"250 CWD successful. "%s" is current directory.rn"**``**, ftpprop.**``**work_dir**``**);**`

`**tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, len);**`

`break``;`

We will open the controller by collecting the code in advance and checking that the server has processed the client command

![Image13](https://narodstream.ru/wp-content/uploads/2017/10/Image13.png)

The team was accepted by the client, to which the client reacted, now sending not PWD, but another TYPE team. This is a customer request for the server to work with the client in ASCII. This is evidenced by argument A. If, for example, the argument would be B, then this would mean a client’s request to apply the binary standard.

Also in Total Commander, we already see that as if we already have a connection via FTP, but so far no list has been displayed

![Image14](https://narodstream.ru/wp-content/uploads/2017/10/Image14.png)

This is because we have not yet transferred the list of files and directories of the current directory, and no one has requested it yet. Moreover, this is already being done by connection for data transfer. But we will get to this.

In the meantime, we will process the client team

`break``;`

`**case**````***TYPE_CMD***``** :**`

`** len = **``**strlen**``**(arg);**`

`** arg[len - 1] = 0x00;**`

`** arg[len - 2] = 0x00;**`

`** switch**``**(arg[0])**`

`** {**`

`** case**````**'A'**``**:**`

`** case**````**'a'**``**: **``**/* Ascii */**`

`** ftpprop.**``**ftp_type**``** = ASCII_TYPE;**`

`** len = **``**sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"200 Type set to %srn"**``**, arg);**`

`** tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, len);**`

`** break**``**;**`

`** case**````**'B'**``**:**`

`** case**````**'b'**``**: **``**/* Binary */**`

`** case**````**'I'**``**:**`

`** case**````**'i'**``**: **``**/* Image */**`

`** ftpprop.**``**ftp_type**``** = IMAGE_TYPE;**`

`** len = **``**sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"200 Type set to %srn"**``**, arg);**`

`** tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, len);**`

`** break**``**;**`

`** default**``**: **``**/* Invalid */**`

`** len = **``**sprintf**``**((**``**char**``***)datasect->**``**data**``**, **``**"501 Unknown type "%s"rn"**``**, arg);**`

`** tcp_send_ftp_one(sn, (**``**uint8_t**``** *)buf_send, len);**`

`** break**``**;**`

`** }**`

`** break**``**;**`

`default``: ``// Invalid`

There is at least considerable code, but there’s nothing to explain in it. We simply respond to possible arguments and send appropriate messages.

We collect the code, send the controller and see what the client asks for next

![Image15](https://narodstream.ru/wp-content/uploads/2017/10/Image15.png)

We see that our client received the message and now he sends us a team **PASV**. This command indicates the client’s request to use a passive exchange mode, since we installed the corresponding checkbox in the FTP connection settings of the client. If the server agrees to such an exchange mode, then it is obliged to respond with the IP address and connection port for data transfer, since we already know that there will be a separate connection for data transfer. Since the command given for the server is still unknown, the server says that the client does not calm down and offers its connection options.

B [**next part**](https://narodstream.ru/stm-urok-95-lan-w5500-ftp-server-chast-3/) classes we will write code to display information about the catalog to the client.

[Embedded media](https://www.youtube.com/embed/yNlkIVc7PtU)

---

Source: https://maker.wiznet.io/teddy/projects/stm-lesson-95-lan-w5500-ftp-server-part-2/
