1년 전 진행했던 마실가실 프로젝트를 🛠️리팩토링하며 정리한 내용입니다.

 

마실가실 ERD (좌-리팩토링 전 / 우 - 리팩토링 후)

 

MSGS_REFACTORING_ERD

담당했던 테이블입니다.

사용자 테이블은 담당이 아니지만, 여행 일정 생성을 위해서는 필수 테이블이기에 같이 정리했습니다.

 

 

🏗️ ERD에서 다음 내역이 개선되었습니다.

 

공통

  • ID 타입을 VARCHAR → INT로 변경
    • 인덱스 생성 및 조회 성능 향상
    • AUTO_INCREMENT 활용을 통한 데이터베이스 입력 자동화 → 입력 실수 방지
  • 등록일, 수정일 등 일자 관련 데이터 형식을 DATE → TIMESTAMP로 변경
    • 시간 정보 추가
    • CURRENT_TIMESTAMP 활용을 통한 데이터베이스 입력 자동화 → 입력 실수 방지
  • 일부 데이터 크기 VARCHAR(255)로 조정
    • VARCHAR: 데이터 byte + 데이터 길이 byte
      VARCHAR의 스키마가 255 이하일 때 1 Byte를 예약하고, 255를 초과할 때 2 Byte를 예약
      데이터 크기가 255를 초과할 필요가 없을 경우, 255로 조정
  • 길이가 고정된 데이터의 경우 VARCHAR → CHAR로 변경
    • CHAR는 길이가 고정되어 있어 데이터 저장 및 조회 시 일관된 성능을 보임
    • 길이 정보를 저장하는 바이트로 인한 오버 헤드가 없음

 

여행 일자 테이블

  • 여행 일자 테이블에 날짜 및 일차 컬럼 추가
    • 자주 사용되는 데이터를 컬럼에 저장하여 성능 개선

 

+ 3차 리팩토링

(2024.08.04)

 

전에 알던 내가 아냐 Brand New Sound...

 

ERD 2차 리팩토링을 하다가 그런 생각이 들었습니다.

나 무슨 기준으로 테이블을 만들고 있는 걸까.. 🤔

기존 테이블 구조에 의하면 여행지가 중복된 데이터로 하염없이 쌓이기 시작하는데, 이래도 되는걸까..🤔

DB 설계가 잘못되었다..🫡

 

그래서 고쳤습니다.

 

기존 테이블은 페이지/동작을 기준으로 작성하였습니다.

  * 예: 사용자 / 일정생성 / 일정후기 등

 

  1. 변경 테이블은 엔티티를 기준으로 작성하였습니다.
    • 예: 사용자 / 일정 / 여행지 등
  2. 다중 필드를 제거하였습니다.
    • 기존 테이블 구조의 경우, 목적지와 관련된 내용이 중복되어 기록됩니다.
    • 중복 데이터가 반복될 수있는 부분을 제거하였습니다.
  3. 계산 필드를 제거하였습니다.
    • 기존 여행 일정에 1일차, 2일차를 계산하는 필드를 제거하였습니다.
    • 원본 데이터가 변경될 경우 계산된 필드가 올바르게 갱신되지 않으면 잘못된 정보를 제공할 수 있는 가능성을 제거하였습니다.
  4. 식별 관계를 줄였습니다.
    • 식별 관계는 간단히 외래키가 자식 테이블의 PK 역할도 수행하는 관계를 의미합니다.
    • 복합키 사용으로 인한 복잡성 증가나 성능 저하의 문제를 고려하여 식별 관계를 줄였습니다.

 

 

 

🙋‍♀️

본 포스트는 공부 목적으로 작성하였습니다.
보시는 도중 잘못된 부분이나 개선할 부분이 있다면 댓글로 알려주시면 수정하도록 하겠습니다.

 

📑

참고 자료

 

MySQL varchar(255)를 사용하는 이유?

이유 MySQL에서 테이블을 만들 때 varchar(255)를 자주 사용했다. varchar가 가변길이 타입으로 char에 비해 실제 저장한 데이터의 크기만큼 저장한다고 알고 있었다. 그런데 왜 하필 255인지는 생각해본

velog.io

 

DB 설계는 어떻게 해야 할까?

도대체 어떻게 설계해야 할까? 😂

velog.io

 

 

Created: 2024-07-14
Updated: 2024-07-28

Language

 

 

Computer Science

모두의 네트워크

0. 읽은 시기🗓️
2024.07

1. 책 소개📖
OSI모델과 TCP/IP 모델의 기초를 쌓을 수 있는 책

2. 독후감✍️
정보처리기사와 기술 면접을 대비해서 네트워크 기초를 공부해 본 적은 있지만, 취업 이후에는 네트워크와 관련된 작업을 하지 않다보니 자연스럽게 잊혀졌다. 그러다 올해부터 회사에서 미니 프로젝트를 진행할 것이라는 소식을 들었다. 어떤 내용으로 진행할지 고민하다, 이번 기회에 자주 접하고 있지만 잘 모르는 주제를 선정해서 공부하면 좋을 것 같다고 생각했다. 그리고 그 주제가 네트워크🛜가 됐다.
인터넷을 통해서 정보를 수집하는 것도 좋지만, 정제된 지식이 정리된 책을 읽어보면 좋을 것 같다는 생각에 구입한 책이다(교보문고 기준 네트워크 검색어 + 판매량 순으로 순위권에 보이지 않는 책이지만, 어디선가 추천 글을 보아서 샀다🫠).
개인적으로 추천하는 책이고, 추천하는 이유는 지식을 연결해주기 때문이다. 간단한 예시로 4계층(전송 계층)은 신뢰할 수 있는 통신을 구현하는 계층이라고 외웠었는데, 신뢰할 수 있는 통신을 어떻게 만드는지 쉽고 간단하게 알려준다.
이 책을 읽고 막연하게 '정보는 상호 합의하에.. 잘.. 주고 받고 있지 않을까🫠..?'라는 생각을 '눈에 보이지 않는 전파를 통해 데이터가 이동하고 있구나🤓!'로 바꿀 수 있었다(그래서 가끔은 전파가 늘 이렇게 우리랑 함께해도 건강에 괜찮은가🥸라는 생각도 한다).

3. 새롭게 알게 된 내용📑

웹 서버에서의 데이터 전달과 처리

  • 물리 계층
    데이터가 전기 신호로 웹 서버에 도착
  • 데이터 링크 계층
    이더넷 프레임의 목적지 MAC 주소와 자신의 MAC 주소 비교
    주소가 같으면 이더넷 헤더와 트레일러를 분리하고 네트워크 계층으로 전달
  • 네트워크 계층
    목적지의 IP 주소와 웹 서버의 IP 주소가 같은지 확인
    주소가 같으면 IP 헤더를 분리하고 전송 계층에 전달
  • 전송 계층
    목적지 포트 번호를 확인하여 어떤 애플리케이션으로 전달해야 되는지 판단
    TCP 헤더를 분리하여 응용 계층에 전달
  • 응용 계층
    전송 계층으로부터 받은 데이터를 애플리케이션에 맞게 해석하고 처리
    예: HTTP 요청이 도착하면 웹 서버는 이를 해석하여 적절한 웹 페이지를 반환

4. 구매처🛒

 

 

Database

SQL 첫걸음

0. 읽은 시기🗓️
2024.01

1. 책 소개📖
SQL 입문서

더보기

2. 독후감✍️
책이 쉽다. 그리고 MySQL🐬 환경이다. 다음 책은 SQL 레벨업인데, 그건 또 어렵다(그래서 읽는 걸 좀 미뤘다). SQL 기본 지식이 있는 상태(저는 SQLD를 소지하고 있습니다🙋‍♀️)라면, 이 책을 굳이 읽지 않아도 될 것 같다. 책도 얇고 단원이 잘 나눠져있어서 들고다니며 가볍게 보기 좋을 것 같다. 근데, 쿼리 좀 작성해봤으면 안 읽어도 될 것 같다.

3. 새롭게 알게 된 내용📑

새롭게 알게된 내용이나 알고 있던 내용을 깔끔하게 정리할 수 있도록 도와준 내용🤓!

-- * Null 값의 정렬 순서
--    * MySQL 기준 Null 값을 가장 작은 값으로 취급
--    * Oracle 기준 Null 값을 가장 큰 값으로 취급

-- * LIMIT
--    * 행수 제한
--    * where 조건절과 달리 order by 정렬 이후 최종적으로 수행
--    * OFFSET: 시작 위치 지정(0부터 시작)

select * from sample33 limit 3;

/*
+------+
| no   |
+------+
|    1 |
|    2 |
|    3 |
+------+
*/

select * from sample33 limit 3 offset 3;

/*
+------+
| no   |
+------+
|    4 |
|    5 |
|    6 |
+------+
*/

 

4. 구매처🛒

 

 

 

Code Quality

내 코드가 그렇게 이상한가요?

0. 읽은 시기🗓️
2024.03

1. 책 소개📖
좋은 코드 / 나쁜 코드로 배우는 설계 입문

2. 독후감✍️
누군가 내 코드를 읽으며 인상을 찌푸릴 때, 한 번쯤은 속으로 물었던 그 말. '제 코드가 그렇게 이상한가요..?'

코드 품질과 관련된 내용은 어째서인지 신입 개발자에게는 너무 성급한 주제라고 생각했다. 새로운 지식을 한창 쌓아갈 시기에 쌓인 지식을 다듬는 느낌이라 그간 미뤄왔는데, 한 번도 사용하지 않았던 MVVM패턴에 맞춰 기능을 구현하기 바빠 나도 내 코드를 보면서 ' 어😯? ' 하는 경우가 생겨 공부를 시작했다.

교보문고에 "코드"를 검색해서 판매량 기준으로 정렬하면, 좋은 책들과의 경쟁에서 밀려 3페이지 가장 마지막에 머물러있다. 하지만, 이 책은 내가 처음으로 코드 설계와 구조에 대해 관심을 갖게해준 책으로, 주변에 추천하고 싶은 책이다. 읽으면서 중요한 내용은 다시 복기하려고 적어두었는데, 문맥을 잊어버릴까봐 책도 결국은 샀다.

이 글을 수정하고 있는 24년 7월 기준 나는 C#을 쓰고 있는데, Java로 되어있는 예제 코드를 따라 쓰면서 오랜만에 다시 Java를 공부하는 재미도 있었다. 여러 좋은 내용이 있었지만, 프로젝트에서 이 책을 보고 적용해봤던 내용은 조건 분기 부분이었다. 책에서 설명한 조기 return을 활용하는 방법은 아래와 같다.

3. 새롭게 알게 된 내용📑

if(0 < member.hitPoint){
    if(member.canAct()){
        if(magic.costMagicPoint <= member.magicPoint){
            member.consumeMagicPoint(magic.costMagicPoint);
            member.chant(magic);
        }
    }
}


// 조기 리턴으로 중첩 제거하기 1
if(member.hitPoint <= 0) return;

if(member.canAct()){
    if(magic.costMagicPoint <= member.magicPoint){
        member.consumeMagicPoint(magi.costMagicPoint);
        member.chant(magic);
    }
}


// 조기 리턴으로 중첩 제거하기 2
if(member.hitPoint <= 0){
    return;
}
if(!member.canAct()){
    return;
}
if(member.magicPoint < magic.costMagicPoint){
    return;
}
member.consumeMagicPoint(magic.costMagicPoint);
member.chant(magic);

 

float hitPointRate = member.hitPoint / member.maxHitPoint;

if(hitPointRate == 0){
    currentHealthCondition = HealthCondition.dead;
}
else if(hitPointRate < 0.3){
    currentHealthCondition = HealthCondition.danger;
}
else if(hitPointRate < 0.5){
    currentHealthCondition = HealthCondition.caution;
}
else{
    currentHealthCondition = HealthCondition.fine;
}

return currentHealthCondition;


// 가독성을 낮추는 else 구문도 조기 리턴으로 해결하기 1
float hitPointRate = member.hitPoint / member.maxHitPoint;

if(hitPoint == 0){
    return HealthCondition.dead;
}
else if(hitPoint < 0.3){
    return HealthCondition.danger;
}
else if(hitPoint < 0.5){
    return HealthCondition.caution;
}
else{
    return HealthCondition.fine;
}


// 가독성을 낮추는 else 구문도 조기 리턴으로 해결하기 2
float hitPointRate = member.hitPoint / member.maxHitPoint;

if(hitPoint == 0){
    return HealthCondition.dead;
}
if(hitPoint < 0.3){
    return HealthCondition.danger;
}
if(hitPoint < 0.5){
    return HealthCondition.caution;
}

return HealthCondition.fine;

4. 구매처🛒

 

 

Etc.

 

18_Transaction_거래를_시작하지

조금 더 생각해 보고 싶은 부분을 공부한 글입니다.

  • 작성일: 2024-06-29
  • 수정일: 2024-07-09

 

 

주제를 선정한 이유

최근에 Transaction 관련 문제로 코드를 한 번 크게 수정했습니다. 성능 상의 문제로 해당 Transaction은 다시 수정되었지만, 오랜만에 '이게 Transaction의 논리적 단위지🔥!'라는 깨달음을 얻어 정리하고자 합니다✍️.

 

 

Transaction

Transaction이란, 데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 모두 수행되어야 할 연산들을 의미합니다.
대표적인 예시로는 은행의 입출금이 있습니다. 내 통장에서 돈을 이체할 경우, 내 통장에서의 출금과 타인 통장에서의 입금은 각각 다른 동작이지만 반드시 한꺼번에 처리 되어야 할 논리적인 기능이 되는 것이지요.
논리적 기능을 어떻게 정의하냐는 사람마다, 상황마다 다릅니다. 예를 들어, 통장의 입출금만 하나의 트랜잭션으로 볼 것인가, 아니면 고객의 통장에 기록을 남기는 것까지 하나의 트랜잭션으로 담을 것인가 등의 다양한 경우가 만들어질 수 있습니다.

 

 

Transaction 특징(ACID)

ACID는 트랜잭션의 안전성을 보장하기 위해 필요한 주요한 특징들을 모아놓은 것입니다.

  1. 원자성(Atomicity)
    트랜잭션의 연산이 모두 데이터베이스에 반영되지 않는다면 전부 반영되서는 안된다는 원칙입니다. One or Nothing과 같은 개념이죠. 이를 통해 데이터의 오염을 방지할 수 있습니다.
    예를 들어 크레파스를 사서, 그림을 그린다가 하나의 트랜잭션이라면 크레파스만 사고 끝났다는 없습니다🫠. 크레파스를 사서 그림까지 그리던지, 그림을 안그릴거면 크레파스를 환불해야 합니다🥸.
    방금 전은 가벼운 예시지만, 원자성을 지키는 것의 중요하고도 대표적인 예시는 송금입니다. 내 통장에서 돈을 빼서 타인 통장에 입금을 할 경우, 내 통장에서 돈만 빼내는 건 안됩니다☠️. 타인 통장에 입금을 실행하지 못하게 된다면 내 통장에 돈을 다시 돌려줘야 합니다😐.

 

  1. 일관성(Consistency)
    트랜잭션 이후에도 이전과 동일하게, 즉 일관되게 데이터베이스의 제약이나 규칙을 만족시켜야 한다는 것입니다.
    송금 예시와 연결지어 생각해보면, 데이터베이스에 입금은 반드시 0원보다 커야한다라는 규칙이 있다면 제 통장에서 0원이 출금되고, 타인의 통장에 0원이 입금된다하면 원자성은 만족하지만 일관성은 만족시키지 못하게 됩니다. 트랜잭션 이후 규칙을 만족시키지 않았기 때문이죠.

 

  1. 독립성, 격리성(Isolation)
    마음에 드는 정의로 이해하시면 됩니다🫠.
  • 둘 이상의 트랜잭션이 동시에 실행될 경우, 어떤 하나의 트랜잭션이 다른 트랜잭션에 끼어들 수 없다.
  • 수행중인 트랜잭션이 완전히 완료될 때까지 다른 트랜잭션에서 수행 결과를 참조할 수 없다.
  • 트랜잭션이 동시에 실행될 때와 연속으로 실행될 때의 데이터베이스 상태가 동일해야 한다.

트랜잭션의 독립성은 여러 의미로 설명할 수 있습니다.
계속해서 송금을 예시로 설명하자면, A->B : 30만원 송금 B->C: 20만원 송금을 한다고 했을 때,
A와 B간 1개의 트랜잭션, B와 C간 1개의 트랜잭션이므로, A, B, C의 관계를 하나로 묶어서 A: 30만원 출금, B: 10만원 입금, C: 20만원 입금을 처리 할 수 없습니다.
A->B의 트랜잭션이 끝나지 않았는데, B->C 트랜잭션에서 이 결과를 참조할 수 없기 때문이죠.

 

  1. 지속성(Durability)
    성공적으로 완료된 트랜잭션의 결과는 영구적으로 반영되어야 한다는 것입니다. 송금이 성공적으로 처리되었으면, 시스템(데이터베이스) 상에 기록이 되어야 한다는 의미입니다.

 

 

Transaction 연산

  1. Commit
    한 개의 트랜잭션이 성공적으로 끝났고, 데이터베이스가 일관된 상태(=일관성)에 있을 때, 트랜잭션의 성공을, 즉 변경이 완료됬음을 트랜잭션 관리자에게 알려주는 연산입니다.
  2. Rollback
    트랜잭션이 수행되던 도중 비정상적으로 종료되어 일부만 정상적으로 처리되었을 때, 트랜잭션의 원자성을 구현하기 위해 해당 트랜잭션의 모든 연산을 취소할 것을 트랜잭션 관리자에게 알려주는 연산입니다.

 

 

정리

Transaction이란
데이터베이스에 변경을 일으키는 작업의 논리적 단위로

  1. 하나의 트랜잭션을 이루는 모든 내용은 전부 반영되거나 전부 반영되지 않아야하며,
  2. 트랜잭션 반영 이후에도 데이터베이스의 제약이나 규칙등을 지켜야 하고,
  3. 완료되지 않은 트랜잭션의 결과를 참조하면 안되며,
  4. 성공적으로 완료된 트랜잭션은 데이테베이스에 영구히 반영되어야 한다는 특징을 갖는다.

 

 

📚참고 자료

[DataBase] 트랜잭션(Transaction)이란?
[데이터베이스] 트랜잭션의 ACID 성질

미니 프로젝트로 네트워크와 관련된 공부를 하고 있습니다.

네트워크 공부는 처음이기에 이와 관련된 내용을 정리해 보고자 합니다✍️.

 

TCP Server

  * 특정 IP 주소와 포트에서 클라이언트의 연결 요청을 기다리고, 연결이 수립되면 데이터를 주고받는 역할

using System.Net;
using System.Net.Sockets;
using System.Text;

namespace TcpServerTest1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            NetworkStream stream = null;
            TcpListener listener = null;
            Socket clientSocket = null;
            StreamReader reader = null;
            StreamWriter writer = null;

            try
            {
                // 1. 서버 IP 주소 설정
                IPAddress ipAddress = IPAddress.Parse("127.0.0.1");

                // 2. TCPListener 생성 및 시작
                // TCPListener: 클라이언트의 연결을 수신
                listener = new TcpListener(ipAddress, 5001);
                listener.Start(); // 서버가 클라이언트의 연결 요청을 수신하도록 시작

                // 3. 클라이언트의 연결 대기
                // 클라이언트의 연결 요청을 받아들여 Socket 객체를 반환
                // 클라이언트가 연결을 시도할 때까지 블로킹(즉, 이 코드에서 멈추고 기다림)
                clientSocket = listener.AcceptSocket();

                // 4. 클라이언트와의 데이터 통신을 위한 스트림 설정
                // clientSocket과 연결된 NetworkStream 객체를 생성 -> 이를 통해 클라이언트와 데이터를 주고받을 수 있음
                // Encoding: 데이터를 바이트로 변환하거나 바이트를 텍스트로 변환할 때 사용
                stream = new NetworkStream(clientSocket);
                Encoding encoding = Encoding.GetEncoding("utf-8");

                // 5. 데이터 읽기와 쓰기를 위한 스트림 리더와 라이터 생성
                reader = new StreamReader(stream, encoding);
                writer = new StreamWriter(stream, encoding) { AutoFlush = true };

                // 6. 데이터 수신 및 송신 루프
                while (true) 
                {
                    // 클라이언트로부터 데이터 읽기
                    string str = reader.ReadLine();
                    Console.WriteLine(str);

                    // 클라이언트에게 데이터 응답
                    writer.WriteLine(str);
                }

            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                clientSocket.Close();
            }
        }
    }
}

 

 

TCP Client

using System.Net.Sockets;
using System.Text;

namespace TcpClientTest1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            TcpClient client = null;

            try
            {
                // 1. TCPClient 생성
                client = new TcpClient();

                // 2. 서버에 연결 시도
                // 서버가 해당 포트에서 연결 요청을 듣고 있어야 함
                client.Connect("localhost", 5001);

                // 3. 네트워크 스트림 획득
                // 네트워크 상에서 데이터를 스트림 방식으로 송수신하는 기능을 제공
                // 스트림: 데이터를 바이트 단위로 처리하며, 데이터를 차례로 읽거나 쓸 수 있는 방식
                // 예를 들어, 서버가 클라이언트에 메시지를 보낼 때 NetworkStream을 통해 데이터를 읽을 수 있음
                // 클라이언트는 NetworkStream을 통해 서버로 메시지를 전송할 수 있음
                NetworkStream stream = client.GetStream();

                // 4. 인코딩 설정
                Encoding encoding = Encoding.GetEncoding("utf-8");

                // 5. 스트림 리더와 라이터 생성
                // StreamWriter: 데이터를 서버로 보내는 데 사용, AutoFlush: 매번 쓰기 작업 후에 버퍼를 자동으로 비움
                // StreamReader: 서버로부터 데이터를 읽는 데 사용
                StreamWriter writer = new StreamWriter(stream) { AutoFlush=true };
                StreamReader reader = new StreamReader(stream, encoding);

                // 6. 사용자 입력 받기
                string dataToSend = Console.ReadLine();

                while (true)
                {
                    // 7. 서버에 데이터 전송
                    writer.WriteLine(dataToSend);

                    // 8. 종료 조건 체크
                    if (dataToSend.IndexOf("<EOF>") > -1)
                    {
                        break;
                    }

                    // 9. 서버로부터 데이터 읽기 및 출력
                    Console.WriteLine(reader.ReadLine());

                    // 10. 다음 데이터 입력
                    dataToSend = Console.ReadLine();
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                throw;
            }
            finally
            {
                client.Close();
            }
        }
    }
}

 

 

TCP Server + Multi Threading

  * 기존 TCP Server에서는 Server : Client가 1:1 구조를 이루면서, 클라이언트가 2이상일 때 처리하지 못함

  * 여러 클라이언트가 동시에 서버에 연결하고 통신할 수 있도록 멀티스레딩을 활용

using System.Net;
using System.Net.Sockets;
using System.Text;

namespace TcpServerTest1
{
    // 개별 클라이언트와의 통신을 처리
    // 각 클라이언트는 별도의 ClientHandler 인스턴스를 가지며, 이 인스턴스는 별도의 스레드에서 실행
    internal class ClientHandler
    {
        // 클라이언트와의 연결을 나타내는 소켓
        Socket socket = null;

        // 클라이언트와의 데이터 통신을 위한 스트림
        NetworkStream stream = null;

        // 클라이언트로부터 데이터를 읽는 리더
        StreamReader reader = null;

        // 클라이언트에게 데이터를 쓰는 라이터
        StreamWriter writer = null;

        public ClientHandler(Socket socket)
        {
            this.socket = socket;
        }

        // 클라이언트와 데이터를 주고받는 메서드
        // 무한 루프를 사용하여 지속적으로 클라이언트로부터 데이터를 읽고 응답
        public void Chat()
        {
            stream = new NetworkStream(socket);
            Encoding encoding = Encoding.GetEncoding("utf-8");

            reader = new StreamReader(stream, encoding);
            writer = new StreamWriter(stream, encoding) { AutoFlush = true };

            while (true)
            {
                // 클라이언트로부터 데이터 읽기
                string str = reader.ReadLine();
                Console.WriteLine(str);

                // 클라이언트에게 데이터 응답
                writer.WriteLine(str);
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            TcpListener listener = null;
            Socket clientSocket = null;

            try
            {
                // 1. 서버 IP 주소 설정
                IPAddress ipAddress = IPAddress.Parse("127.0.0.1");

                // 2. TCPListener 생성 및 시작
                // TCPListener: 클라이언트의 연결을 수신
                listener = new TcpListener(ipAddress, 5001);
                listener.Start(); // 서버가 클라이언트의 연결 요청을 수신하도록 시작
                
                while (true)
                {
	                // 프로그램이 클라이언트의 연결 요청을 받을 때마다, 새로운 Thread를 생성하고 그 스레드가 클라이언트와의 통신을 처리
                    
                    // 3. 클라이언트의 연결 대기 및 수락
                    clientSocket = listener.AcceptSocket();

                    // 4. 클라이언트 처리기 생성 및 스레드 시작
                    ClientHandler handler = new ClientHandler(clientSocket);
                    Thread t = new Thread(new ThreadStart(handler.Chat)); // 새 스레드를 시작하여 클라이언트와의 통신을 독립적으로 처리

                    t.Start();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                clientSocket.Close();
            }

        }
    }
}

 

 

좌: 단일 Thread 통신 / 우: 멀티 Thread 통신

 

 

 

📚 참고 자료

 

xaml에서 ⭐나만의 property⭐를 만들 수 있다는 걸 알고 계셨나요?

전 알고 있었습니다. 하지만, ⭐나만의 property⭐가 없어도 구현에 문제가 없어서 Dependency Property를 공부하는 걸 미루다가 프로젝트에서 사용되는 걸 보고, 이 참에 정리하고자 글을 작성해 봅니다.

 

 

 

Dependency Property

  * XAML, C# 코드 비하인드(.xaml.cs)에서 사용 가능

  * 의존속성 값이 변경되면 자동으로 어떤 것을 로드되게 하거나 랜더링 되도록 할 수 있음

    * 애니메이션, 스타일링, 데이터바인딩 등에 자주 사용

  * 기본으로 제공되는 UI 컨트롤은 대부분의 속성이 의존 속성으로 되어 있음

 

 

1. MainWindow에서 바로 사용하기

MainWindow.xaml

<Window x:Class="DependencyPropertyPratice.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DependencyPropertyPratice"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        >
    <Window.ContextMenu>
        <ContextMenu MenuItem.Click="ContextMenu_Click">
            <MenuItem Header="#02343F"/>
            <MenuItem Header="#F0EDCC"/>
            <MenuItem Header="#0A174E"/>
            <MenuItem Header="#F5D042"/>
        </ContextMenu>
    </Window.ContextMenu>

    <TextBox Name="colorBox"
             VerticalAlignment="Center" HorizontalAlignment="Center"
             Width="150" Height="50"
             />
</Window>

 

MainWindow.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace DependencyPropertyPratice
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        // DependencyProperty(MyColorProperty)를 위한 래퍼 속성의 MyColor
        // 이 래퍼속성에서는 System.Windows.DependencyObject 클래스의 GetValue()와 SetValue()를 이용해서 get, set을 정의해야 함
        public string MyColor
        {
            get { return (string)GetValue(MyColorProperty); }
            set { SetValue(MyColorProperty, value); }
        }

        // DependencyProperty
        // DependencyProperty 클래스에는 public 생성자가 없으므로 static 메서드인 Register()를 사용해서 등록
        // 수정이 불가능 하도록 의존 속성은 readonly 필드로 선언되는데, 일반 UI 컨트롤 등 대부분의 의존속성은 FrameworkElement에 DependencyProperty로 정의되어 있음
        public static readonly DependencyProperty MyColorProperty = DependencyProperty.Register(
            "MyColor" // 의존 속성으로 등록될 속성
            , typeof(string) // 등록할 의존 속성 타입
            , typeof(MainWindow) // 의존 속성을 소유하게 될 Owner
            , new FrameworkPropertyMetadata(new PropertyChangedCallback(OnMyColorPropertyChanged))
            // 속성값 변경 시, 호출될 메서드
            // Property 값 변경에 따른 Callback() 등 새로운 속성을 추가하기 위해 FrameworkPropertyMetadata를 인자 값으로 전달 할 수 있음
            );

        private static void OnMyColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MainWindow window = d as MainWindow;
            SolidColorBrush brush = (SolidColorBrush)new BrushConverter().ConvertFromString(e.NewValue.ToString());
            window.Background = brush;
            window.Title = (e.OldValue == null) ? "이전 배경색 없음" : e.OldValue.ToString();
            window.colorBox.Text = e.NewValue.ToString();
        }

        private void ContextMenu_Click(object sender, RoutedEventArgs e)
        {
            string selectedColor = (e.Source as MenuItem).Header as string;
            MyColor = selectedColor;
        }

    }
}

 

 

2. SubWindow에서 상속받아서 사용하기

SubWindow.xaml

<local:MainWindow x:Class="DependencyPropertyPratice.WindowSub"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DependencyPropertyPratice"
        mc:Ignorable="d"
        Title="WindowSub" Height="450" Width="800"
        MyColor="#F95700"
        >
    <Grid>
        <Button Content="Go To the Main Window"
                Width="150" Height="30"
                Click="Button_Click"
                />
    </Grid>
</local:MainWindow>

 

☠️ 이렇게 못씁니다.

오류납니다.

'DependencyPropertyPratice.MainWindow' cannot be the root of a XAML file because it was defined using XAML.

 

WPF에서는 XAML 파일을 통해 이미 정의된 클래스를 다시 XAML의 루트 요소로 사용하는 것을 허용하지 않습니다.

 

상속을 받고자 한다면, MainWindow를 XAML이 아닌 코드 비하인드 파일(C# 파일)에서만 정의하여 SubWindow에서 이를 상속할 수 있도록 할 수 있습니다.

 

 

MainWindowOnlyCS.cs

using System.Windows;
using System.Windows.Media;

namespace DependencyPropertyPratice
{
    public class MainWindowOnlyCS : Window
    {
        public string MyColor
        {
            get { return (string)GetValue(MyColorProperty); }
            set { SetValue(MyColorProperty, value); }
        }

        public static readonly DependencyProperty MyColorProperty = DependencyProperty.Register(
            "MyColor" // 의존 속성으로 등록될 속성
            , typeof(string) // 등록할 의존 속성 타입
            , typeof(MainWindowOnlyCS) // 의존 속성을 소유하게 될 Owner
            , new FrameworkPropertyMetadata(new PropertyChangedCallback(OnMyColorPropertyChanged))
            // 속성값 변경 시, 호출될 메서드
            );

        private static void OnMyColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MainWindowOnlyCS window = d as MainWindowOnlyCS;
            SolidColorBrush brush = (SolidColorBrush)new BrushConverter().ConvertFromString(e.NewValue.ToString());
            window.Background = brush;
            window.Title = (e.OldValue == null) ? "이전 배경색 없음" : e.OldValue.ToString();
        }
    }
}

 

WindowSub.xaml

<local:MainWindowOnlyCS x:Class="DependencyPropertyPratice.WindowSub"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DependencyPropertyPratice"
        mc:Ignorable="d"
        Title="WindowSub" Height="450" Width="800"
        MyColor="#F95700"
        >
    <Grid>
        <Button Content="Go To the Main Window"
                Width="150" Height="30"
                Click="Button_Click"
                />
    </Grid>
</local:MainWindowOnlyCS>

 

.cs로 이뤄진 Window 파일을 상속받아서 xaml 태그에 MyColor를 전달하면 DependencyProperty에 따라 WindowSub의 배경색이 오랜지 색으로 변경됩니다.

 

 

 

 

📚 참고 자료

 

Reference Source

 

referencesource.microsoft.com