본문 바로가기

Ethernet Chat Server/0.채팅 프로그램 초안 제작

W5500-EVB를 이용하여 채팅 프로그램 제작하기

이번 시간은, 우리의 최종목표인 W5500-EVB를 활용한 채팅프로그램 만들기를 해보겠습니다.
아래의 코드들의 수순은,
1. Socket 생성
2. Listen 상태(대기상태)
3. ESTABLISHED 상태(Client와 연결상태)
4. Data Recv/Trans
입니다. 하지만 , 여기서 주의하셔야 할 점은 아래의 Code에서의 Data는 Socket으로 받은 EthernetData라는 점입니다.(Ethernet 통신으로 받은 Data)
다음 시간에는 Serial로 출력하는 방법으로 넘어가도록 하겠습니다.

저번시간에 W5500-EVB는 bind() 와 Accept()가 없다고 말씀드렸는데, 그 이유는,
W5500-EVB에서 제공하는 라이브러리에서는 bind() + Socket()이 합쳐 있습니다. 그래서 따로 bind()가 필요하지 않는 것이구요.
Accept()는 윈도우 채팅프로그램에서 처럼 클론을 만들어서 Listen을 가져와서 Accept()하여 할당하면 하나의 서버와 여러개의 클라이언트들이 접속하여 채팅을 할 수 있습니다. 하지만 W5500-EVB의 라이브러리는 Accept()함수를 쓰지 않음으로써 서버와 클라이언트간에 1:1통신밖에 안됩니다. 총 8개의 Socket을 연결할 수 있습니다.

여기서 왜 이렇게 구현이 되어있나? 라는 의문이 드실겁니다.

이유는 WIZnet Socket 프로그래밍은 Hardware Socket Program이기 때문입니다.

Hardward Socket Programming이 무엇이냐면, PC의 Software의 운영체제에는 TCP/IP Stack이 따로 존재합니다. 즉, 이말은 PC의 메모리가 클수록 TCP/IP Stack은 많아 질테고 그만큼 Socket을 많이 만들수 있습니다. 이 방식의 장점은 여러개의 소켓을 만들어 프로그래밍할 수 있다는 장점이 있는 반면에, 클라이언트로 부터 너무 많은 접속이 들어와 Stack이 꽉차는 현상이 발생해 Overflow가 되면, PC가 먹통이 되어버립니다. 일명 디도스 공격도 이하와 같은 방식으로 한다죠?

그런데, WIZnet에서 개발한 칩은 이러한 TCP/IP Stack을 Chip화 시키면서 Hardware적으로 만들어버립니다. 그리고 H/W Socket을 8개로 지정해버리죠. 물론, Socket을 더 늘릴 수 있습니다. 하지만, 이 메모리 Stack이 상당히 가격이 비싸서 소켓을 8개만 사용하기로 Trade Off(책정)해버렸습니다. (소켓을 더 늘리면 그만큼 칩의 가격도 많이 상승한다는 점..)

여기서 Hardware적 Socket 프로그래밍의 장점은 Overflow를 안한다는 점입니다. Socket 만들 수 있는 갯수가 한정적이니깐요. 그만큼 안정적이게 되는거죠. 그리고 나중에 Socket에 접속을 해보시면 알 수 있으시겠지만, 여기서 말씀드리자면 Socket을 여러번 접속했다. 끊었다. 반복적으로 사용을 합니다. 즉, 0, 1, 2, 3, 4, 5, 6, 7 순으로 Socket이 연결되었다가 끊어졌다가를 반복합니다.

자, 이러한 차이점을 아셨다면, 이제 아래의 Code를 봐주시면 되겠습니다.

먼저 처음에 보시는 Code는 Loopback Code입니다. 이 Code를 기반으로 프로그램을 만들어보겠습니다.

[code language="c"]

int32_t tcp_client(uint8_t sn, uint8_t* buf, uint8_t* ip, uint16_t port)
{ // Loopback Function
	int32_t ret;
	uint16_t size = 0, sentsize = 0;
	switch (getSn_SR(sn)) {
	case SOCK_ESTABLISHED:
		//printf(";ESRABLISHED OK, ip[%d.%d.%d.%d] port [%d]\r\n", ip[0], ip[1], ip[2], ip[3], port);

		/**/
		//Connect Interrupt Check
		if (getSn_IR(sn) & Sn_IR_CON) {
			printf("%d:Connected\r\n", 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);
				//printf("%s",buf);
				if (ret > 0) {
					close(sn);
					return ret;
				}
				sentsize += ret; // Don't care SOCKERR_BUSY, because it is zero.
			}
		}
		//*

		break;
	case SOCK_CLOSE_WAIT:
		printf("%d:CloseWait\r\n", sn);
		if ((ret = disconnect(sn)) != SOCK_OK) return ret;
		printf("%d:Closed\r\n", sn);
		break;
	case SOCK_INIT:
		//printf("%d:Conntcting, ip[%d.%d.%d.%d] port [%d]\r\n", sn, ip[0], ip[1], ip[2], ip[3], port);
		//if( (ret = connect(sn,ip,port)) != SOCK_OK) return ret;
		printf("%d:Listen, port [%d]\r\n", sn, port);
		if ((ret = listen(sn)) != SOCK_OK) return ret;
		break;
	case SOCK_CLOSED:
		printf("%d:LBTStart\r\n", sn);
		if ((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn)
			return ret;
		printf("%d:Opened\r\n", sn);
		break;
	default:
		break;
	}
	return 1;
}
[/code]
[code language="c"]

/*
===============================================================================
 Name : W5500-EVB.c
 Author : $(author)
 Version :
 Copyright : $(copyright)
 Description : main definition
===============================================================================
*/

#if defined (__USE_LPCOPEN)
#if defined(NO_BOARD_LIB)
#include <chip.h>
#else
#include <board.h>
#endif
#endif

#include "cr_section_macros.h"
#include "socket.h"
#include "spi_handler.h"
#include "w5500_init.h"

// TODO: insert other include files here

// TODO: insert other definitions and declarations here
#define SOCK_TCPS 0
#define PORT_TCPS 5000
#define DATA_BUF_SIZE 2048
uint8_t gDATABUF[DATA_BUF_SIZE];

int main(void) {

#if defined (__USE_LPCOPEN)
#if !defined(NO_BOARD_LIB)
	// Read clock settings and update SystemCoreClock variable
	SystemCoreClockUpdate();
	// Set up and initialize all required blocks and
	// functions related to the board hardware
	Board_Init();
	// Set the LED to the state of &amp;amp;amp;quot;On&amp;amp;amp;quot;
	Board_LED_Set(0, true);
#endif
#endif
	SPI_Init();
	W5500_Init();
	Net_Conf();

	int32_t ret;
	uint16_t size = 0, sentsize = 0;

	if ((socket(SOCK_TCPS, Sn_MR_TCP, PORT_TCPS, 0)) == 0){ // socket 생성
		printf("socket error\r\n");
		while (1); // 소켓생성 에러시, While문을 돌려 멈춤.
	}
	else{
		printf("Socket OK\r\n");
	}
	if ((listen(SOCK_TCPS)) == 0){ // Listen 상태 (Connection 접속 대기상태)
		printf("listen error\n");
		while (1); // Listen 에러시, while문을 돌려 멈춤
	}
	else{
		printf("listen OK\r\n");
	}

	while (1)
	{
		if (getSn_IR(SOCK_TCPS) & Sn_IR_CON){ // Client와 접속된 상태(ESTABLISHED STATE)
			printf("Connection!\n");
			setSn_IR(SOCK_TCPS, Sn_IR_CON); // interrupt clear
		}
		if ((size = getSn_RX_RSR(SOCK_TCPS)) > 0) // Data Lenth Read (W5500 Datasheet설명 참조)
		{
			if (size > 2048) size = 2048; { // Size가 2048이상일 경우, 2048까지만 출력.
				if ((ret = recv(SOCK_TCPS, gDATABUF, size)) == 0){ // Ethernet Socket Data Receive from Client
					printf("receive error\n");
					send(SOCK_TCPS, gDATABUF, size) // Ethernet Socket Data Trans from Server

						return 0; // 정상 완료.
				}
			}
		}
	}
}
[/code]
[code language="c"]

		if (getSn_IR(SOCK_TCPS) & Sn_IR_CON){ // Client와 접속된 상태(ESTABLISHED STATE)
			printf("Connection!\n");
			setSn_IR(SOCK_TCPS, Sn_IR_CON); // interrupt clear
		}

/*위의 코드는 W5500 Datasheet를 참조하여 확인하여 보시는 것이 이해하기 빠를거에요.
하지만, 설명을 덧붙이자면 W5500-EVB는 Sn_SR이라는 상태 레지스터를 이용하여 Socket 생성
Listen 상태 ESTABLISHED 상태로 가게 되는데요. 그래서 Client로부터 접속이 시도가 되어
Accept(연결)가 되면 Sn_SR레지스터는 ESTABLISHED상태로 바뀌면서 Sn_IR_CON을 1번 출력하게
됩니다. 이때 인터럽트가 되어 Connection이 되게 됩니다. 그런데, 여기서 인터럽트를 걸때,
Mask를 사용하여 인터럽트를 걸기때문에, 초기화를 해주려면 다시 한번 Set을 해주어야 합니다.
그래서 set_Sn_IR을 해주는 이유입니다. */

[/code]

다음은, Ethernet Socket으로 받은 Data를 Serial Data(UART)로 Print하고, Server 내부에서 Serial Data를 읽어서 Client로 보내는 코드를 올리도록 하겠습니다.