위즈네트 아카데미

TUTORIAL

튜토리얼

Home  > 튜토리얼
지난 포스팅에서 W5500에 대한 Ping Test까지 완료하였습니다.  이제 본격적으로 ioLibrary를 활용 실제 응용을 구현해 보겠습니다.

- TCP Loopback (Echo Server) 구현하기

Loopback 은 수신한 데이타를 아무런 가공없이 그대로 되돌려주는 Echo Server 프로그램으로,  TCP Server나 Client 그리고 UDP등으로 구현이 가능합니다.

Echo Server에서 Protocol은 말그대로 수신한 데이타를 아무런 가공없이 그대로 수신한 상대방에게 돌려 줍니다. 일반적으로 Telnet, FTP, HTTP, NTP 등과 같은 Protocol은 정해진 Data를 수신하고 그것을 해석하고 그에 상응하는 Data를 가공하여 상대방에 돌려주는데, 여기서는 그런 Protocol의 구현에 앞서 가장 간단한 Echo Server를 구현해 보겠습니다.

그럼 TCP Echo Server를 구현하기 위해 필요한 함수를 먼저 살펴 보겠습니다. (각 SOCKET API들은 앞서 언급한 SOCKET_APIs.chm Doxygen 문서를 참조). 이 모든 함수는 Blocked (Polling) 혹은 Non-Blocked Mode로 선택적으로 함수를 사용할 수 있으며, 이는 socket() 함수 호출 시 (즉 생성시) flag 설정 시 SF_IO_NONBLCOK 값을 추가해주면 된다. flag 설정시 SF_IO_NONBLOCK을 추가하지 않을 경우, 즉 Default로 설정할 경우 SF_IO_BLOCK으로 설정됩니다. 또한 Socket 의 IO Mode는 setsockopt() 함수를 이용하여 동작 중에도  설정이 가능합니다.

[sourcecode language="plain"]
/* Socket Creatatiion */
int8_t socket(uint8_t sn, uint8_t protocol, uint16_t port, uint8_t flag);

/* Listen to a TCP client */
int8_t listen(uint8_t sn);

/* Connect to a server */
int8_t connect(uint8_t sn);

/* Send data to the peer */
int32_t send(uint8_t sn, uint8_t* buf, uint32_t len);

/* Receive data from the peer */
int32_t recv(uint8_t sn, uint8_t* buf, uint32_t len);

/* Disconnect the socket only used in TCP */
int8_t disconnect(uint8_t sn);

/* Close the socket without disconnect-processing */
int8_t close(uint8_t sn);
[/sourcecode]

WIZnet은 TCP 관련 Application 구현 시 참고 할 수 있는 Skeleton code를 아래와 같이 제공합니다.

[sourcecode language="plain"]
{
...
switch(getSn_SR(sn)) {
case SOCK_ESTABILSHED: /* TCP ESTABLISHED */
/* TCP Server Side : When acceptting from a peer to request to connection */
/* TCP Cleint Side : When the connection request is acceptted by the server */
//
// TODO
//
// Start to data communication
send(sn, buf, len); // or recv(sn,buf,len)
recv(sn,buf, len); // or send(sn,buf,len)
break;
case SOCK_CLOSE_WAIT:
/* Disconnect request : The socket don't need any more */
disconnect(sn); // Don't returned until the disconnect is succeeded.
// or close(sn); // Without processing to disconnect to the peer
break;
case SOCK_CLOSED: /* Socket Closed State */
/* TCP Server Side */
socket(sn, Sn_MR_TCP,server_port, 0x00); // Block-Io
/* TCP Client Side : any_port is random because this can't be used as same as the previous port num */
//socket(sn, Sn_MR_TCP,any_port, SF_IO_NONBLOCK); // Non-Block-IO
//
// TODO
//
break;
case SOCK_INIT: /* TCP Socket Creatation */
/* TCP Server Side : Wait to a conenctiion request from a peer */
listen(sn);
/* TCP Client Side : Reqeust to connect to the server with server_port num */
connect(sn,server_ip_address, server_port); // Don't return until the connection is success
//
// TODO
//
break;
case SOCK_LISTEN: /* TCP Server Mode : This state can be omitted*/
//
// TODO
//
break;
}
...
}
[/sourcecode]

TCP Sever Skeleton 코드를 참고하여 아래와 같이 구현해 보겠습니다.

[sourcecode language="plain"]
///////////////////////////////////////////////////////////////
// Loopback Test Example Code using ioLibrary_BSD //
///////////////////////////////////////////////////////////////
int32_t loopback_tcps(uint8_t sn, uint8_t* buf, uint16_t port)
{
int32_t ret;
uint16_t size = 0, sentsize=0;
switch(getSn_SR(sn))
{
case SOCK_ESTABLISHED :
if(getSn_IR(sn) & Sn_IR_CON)
{
printf("%bu:Connectedrn",sn);
setSn_IR(sn,Sn_IR_CON);
}
if((size = getSn_RX_RSR(sn)) > 0)
{
if(size > DATA_BUF_SIZE) size = DATA_BUF_SIZE;
ret = recv(sn,buf,size);
if(ret <= 0) return ret;
sentsize = 0;
while(size != sentsize)
{
ret = send(sn,buf+sentsize,size-sentsize);
if(ret < 0)
{
close(sn);
return ret;
}
sentsize += ret; // Don't care SOCKERR_BUSY, because it is zero.
}
}
break;
case SOCK_CLOSE_WAIT :
printf("%bu:CloseWaitrn",sn);
if((ret=disconnect(sn)) != SOCK_OK) return ret;
printf("%bu:Closedrn",sn);
break;
case SOCK_INIT :
printf("%bu:Listen, port [%u]rn",sn, port);
if( (ret = listen(sn)) != SOCK_OK) return ret;
break;
case SOCK_CLOSED:
printf("%bu:LBTStartrn",sn);
if((ret=socket(sn,Sn_MR_TCP,port,0x00)) != sn)
return ret;
printf("%bu:Openedrn",sn);
break;
default:
break;
}
return 1;
}
[/sourcecode]

다소 복잡해 보이지만 getSn_RX_RSR() 함수의 사용, 그리고 Return에 따른 에러 처리를 무시하고 코드를 살펴보면 Skeleton Code를 그대로 볼 수 있습니다. getSn_RX_RSR()함수 사용은 현재 Non-Block IO mode로 동작하는 recv() 함수가 Blocked 되는 것을 방지하기 위해 미리 수신한 크기를 확인하고 확인 후 recv() 함수를 호출하도록 구현되어 있습니다.

- UDP Loopback (Echo Server) 구현하기

UDP Echo Server를 구현하기 위해 필요한 함수를 먼저 살펴보겠습니다. (SOCKET_API.chm 참조) TCP와 마찬가지로 Blocked 혹은 Non-blocked로 사용 가능합니다.

[sourcecode language="plain"]
/* Socket Creatatiion */
int8_t socket(uint8_t sn, uint8_t protocol, uint16_t port, uint8_t flag);

/* Send data to a peer */
int32_t sendto(uint8_t sn, uint8_t* buf, uint32_t len, uint8_t* addr, uint16_t port);

/* Receive data from the peer */
int32_t recvfrom(uint8_t sn, uint8_t* buf, uint32_t len, uint8_t* addr, uint16* port);

/* Close the socket without disconnect-processing */
int8_t close(uint8_t sn);
[/sourcecode]

다음은 UDP 관련 Application 구현 시 참고할 수 있는 아주 간단한 Skeleton Code를 아래와 같이 제공한다.

[sourcecode language="plain"]
{
...
switch(getSn_SR(sn)) {
case SOCK_UDP: /* TCP Socket Creatation */
//
// TODO
//
// Start to data communication
sendto(sn, buf, len, peer_ip_address, peer_port_num); // or recvfrom(...);
recvfrom(sn,buf, len, peer_ip_address, peer_port_num); // or sendto(...);
//
// If the socket don't need any more
if(is_closed) close(sn);
//
break;
case SOCK_CLOSED: /* Socket Closed State */
/* TCP Server Side */
socket(sn, Sn_MR_UDP,server_port, 0x00); // Block-Io
/* TCP Client Side : any_port is random because this can't be used as same as the previous port num */
//socket(sn, Sn_MR_TCP,any_port, SF_IO_NONBLOCK); // Non-Block-IO
//
// TODO
//
break;
}
...
}
[/sourcecode]

UDP Skeleton Code를 참고하여 UDP Loopback을 아래와 같이 구현해 보겠습니다.

[sourcecode language="plain"]
int32_t loopback_udps(uint8_t sn, uint8_t* buf, uint16_t port)
{
int32_t ret;
uint16_t size, sentsize;
uint8_t destip[4];
uint16_t destport;
//uint8_t packinfo = 0;
switch(getSn_SR(sn))
{
case SOCK_UDP :
if((size = getSn_RX_RSR(sn)) > 0)
{
if(size > DATA_BUF_SIZE) size = DATA_BUF_SIZE;
ret = recvfrom(sn,buf,size,destip,(uint16_t*)&destport);
if(ret <= 0)
{
printf("%bu: recvfrom error. %ldrn",sn,ret);
return ret;
}
size = (uint16_t) ret;
sentsize = 0;
while(sentsize != size)
{
ret = sendto(sn,buf+sentsize,size-sentsize,destip,destport);
if(ret < 0)
{
printf("%bu: sendto error. %ldrn",sn,ret);
return ret;
}
sentsize += ret; // Don't care SOCKERR_BUSY, because it is zero.
}
}
break;
case SOCK_CLOSED:
printf("%bu:LBUStartrn",sn);
if((ret=socket(sn,Sn_MR_UDP,port,0x00)) != sn)
return ret;
printf("%bu:Opened, port [%u]rn",sn, port);
break;
default :
break;
}
return 1;
}
[/sourcecode]

- Loopback (Echo Server) Test 하기
앞서 구현된 loopback_tcps() 와 loopback_udp() 함수를 ioLibrary_W5500_main.c 의 함수 선언부에 추가하고 Loopback을 위한 Data buffer를 변수 선언부에 추가합니다. Data buffer 크기는 Target System에 맞게 적절히 조절합니다.

[sourcecode language="plain"]
/////////////////////////////////////////
// SOCKET NUMBER DEFINION for Examples //
/////////////////////////////////////////
#define SOCK_TCPS 0
#define SOCK_UDPS 1

////////////////////////////////////////////////
// Shared Buffer Definition for LOOPBACK TEST //
////////////////////////////////////////////////
#define DATA_BUF_SIZE 2048
uint8_t gDATABUF[DATA_BUF_SIZE];
[/sourcecode]

또한 main() 함수의 main loop에 Loopback 기능을 수행할 수 있도록 다음과 같이 추가합니다.

[sourcecode language="plain"][/sourcecode]

...
/* Network initialization */
network_init();

/*******************************/
/* WIZnet W5500 Code Examples */
/* TCPS/UDPS Loopback test */
/*******************************/
/* Main loop */
while(1)
{
uint32_t ret = 0;
/* Loopback Test */
// TCP server loopback test
if( (ret = loopback_tcps(SOCK_TCPS, gDATABUF, 5000)) < 0) {
printf("SOCKET ERROR : %ldrn", ret);
}

// UDP server loopback test
if( (ret = loopback_udps(SOCK_UDPS, gDATABUF, 3000)) < 0) {
printf("SOCKET ERROR : %ldrn", ret);
}
} // end of Main loop
// NOTREACHED

return 0;
}
모든 코드 구현이 완료되었습니다.
Hercules Program을 사용하여 테스트해 보겠습니다. 컴파일 후 프로그램을 다운로드하고 다음과 같이 Target Board를 실행합니다.

1. Hercules 프로그램을 Serial 모드로 실행하고 Target Board를 동작 시키면 Target Board의 출력을 다음과 같이 확인할 수 있습니다.
main_start
2. 또 하나의 Hercules Program을 TCP client 모드로 실행한 후, Target 보드의 Network 정보를 입력하고 접속을 시도합니다. 접속을 성공하였다면 "Hello, W5500!!!" 을 Target Board로 전송하면 Taget Board로 부터 되 돌아 오는 "Hello, W5500!!!"을 다음과 같이 확인 할 수 있습니다.

loop_tcps

3. UDP Loopback을 테스트하기 위해서 또 하나의 Hercules Program을 UDP mode로 실행하고, TCP와 동일하게 Target Board의 Nework 정보를 입력한 후 [Listen] 버튼을 클린합니다. UDP 채널이 성공적으로 만들어 졌다면, TCP와 동일하게 "Hello, W5500!!!"을 Target Board로 전송하고 되돌아 오는 "Hello, W5500!!!"을 다음과 같이 확인합니다.

loop_udp

이상 W5500 용 ioLibrary를 C8051F380 보드로 Porting하고, 간단한 TCP Loopback (Echo server) Example 구현까지 해 보았습니다.

프로젝트 다운로드 ioLibrary_W5500_For_8051.zip

========================================================================================

W5500 ioLibrary_BSD 를 Silab 8051에 포팅하기 관련 글
1.W5500 ioLibrary_BSD를 SiLab 8051 에 포팅하기 – 환경설정
2.W5500 ioLibrary_BSD를 SiLab 8051 에 포팅하기 – 테스트보드 만들기
3. W5500 ioLibrary_BSD를 SiLab 8051 에 포팅하기 – ioLibrary Download
4. W5500 ioLibrary_BSD를 SiLab 8051 에 포팅하기 – Project Build
5. W5500 ioLibrary_BSD를 SiLab 8051 에 포팅하기 – C8051F380 설정하기
6. W5500 ioLibrary_BSD를 SiLab 8051 에 포팅하기-W5500 I/O control function 구현하기
7. W5500 ioLibrary_BSD를 Silab8051에 포팅하기-W5500 초기화
8. W5500 ioLibrary_BSD를 SiLab 8051 에 포팅하기 – Loopback (Echo Server) 구현하기

☞ 출처 : 밤소의 IoT 잠못드는 밤


이번 포팅관련 모든 테스트 및 포스팅은 위즈네트 칩 엔지니어인 김우열님께서 진행해 주셨습니다. W5500 사용 관련 문의 사항이 있으시면 김우열님의 블로그 밤소의 잠못드는 밤  IOT 이야기를 통해 contact 해 주세요.