부제 : Cursor IDE: Node.js ↔ C# 심층 가이드
안녕하세요, 지난 번 Node.js 웹 클라이언트와 C# 윈도우 서버 간의 웹소켓 통신 기본 구현에 이어, 오늘은 한 걸음 더 나아가 JSON 기반의 통신 프로토콜을 설계하고 Cursor IDE의 AI 기능을 십분 활용하여 이를 구현하는 방법을 심층적으로 알아보겠습니다.
단순한 텍스트 메시지 교환을 넘어, 구조화된 데이터를 주고받는 것은 모든 실용적인 애플리케이션의 필수 요소입니다. Cursor IDE의 AI와 함께라면, 이 복잡한 과정도 훨씬 쉽고 빠르게 처리할 수 있습니다!
🚀 왜 JSON 프로토콜을 사용해야 할까요?
지난번 웹소켓 구현에서는 단순 텍스트 메시지를 주고받았습니다. 하지만 실제 애플리케이션에서는 사용자 정보, 명령어, 상태 값 등 다양한 종류의 데이터를 주고받아야 합니다. 이때 **JSON(JavaScript Object Notation)**은 다음과 같은 이유로 강력한 통신 프로토콜 형식으로 활용됩니다.
- 인간 친화적이고 가독성 높음: 사람이 읽고 쓰기 쉽습니다.
- 경량(Lightweight): XML 등에 비해 데이터 크기가 작아 네트워크 오버헤드가 적습니다.
- 다양한 언어 지원: JavaScript는 물론 C#, Python, Java 등 대부분의 프로그래밍 언어에서 JSON 파싱 및 생성이 기본적으로 지원되거나 라이브러리를 통해 쉽게 가능합니다.
- 유연성: 스키마에 엄격하게 얽매이지 않아 변경이 용이합니다.
우리는 이제 클라이언트와 서버가 서로의 의도를 명확히 이해하고, 특정 형식의 데이터를 주고받을 수 있도록 JSON 객체를 기반으로 통신 규칙(프로토콜)을 정의할 것입니다.
🛠️ 통신 프로토콜 설계: JSON 메시지 구조 정의
가장 먼저 할 일은 클라이언트와 서버 간에 주고받을 JSON 메시지의 구조를 정의하는 것입니다. 우리는 두 가지 유형의 메시지를 교환해 볼 것입니다:
- 채팅 메시지 (CHAT_MESSAGE): 클라이언트가 서버로 보내는 일반 텍스트 메시지
- 서버 상태 요청 및 응답 (STATUS_REQUEST / SERVER_STATUS): 클라이언트가 서버의 현재 상태를 요청하고, 서버가 그에 대한 정보를 응답
JSON 메시지 기본 구조:
{
"type": "MESSAGE_TYPE",
"payload": {
// 메시지 유형에 따른 실제 데이터
}
}
- type: 메시지의 종류를 나타내는 문자열 (예: "CHAT_MESSAGE", "STATUS_REQUEST", "SERVER_STATUS")
- payload: 실제 데이터가 담기는 객체
정의할 메시지 유형:
1. 클라이언트 → 서버: CHAT_MESSAGE
{
"type": "CHAT_MESSAGE",
"payload": {
"text": "안녕하세요, 서버!",
"timestamp": "2023-10-27T10:30:00Z" // 선택 사항
}
}
2. 클라이언트 → 서버: STATUS_REQUEST
{
"type": "STATUS_REQUEST",
"payload": {} // 요청 시 별도 데이터 없음
}
3. 서버 → 클라이언트: CHAT_ACK (채팅 메시지 수신 확인)
{
"type": "CHAT_ACK",
"payload": {
"originalMessage": "안녕하세요, 서버!",
"status": "received",
"serverTimestamp": "2023-10-27T10:30:05Z"
}
}
4. 서버 → 클라이언트: SERVER_STATUS (서버 상태 정보)
{
"type": "SERVER_STATUS",
"payload": {
"cpuUsage": "15%",
"memoryUsage": "40%",
"activeConnections": 2,
"serverTime": "2023-10-27T10:30:10Z"
}
}
🧑💻 Cursor IDE와 AI로 JSON 프로토콜 구현하기
이제 정의한 프로토콜을 Node.js 클라이언트와 C# 서버에 적용해봅시다. Cursor IDE의 AI 기능을 최대한 활용할 것입니다.
1. C# 윈도우 웹소켓 서버 수정 (JSON 파싱 및 생성)
C# 서버는 클라이언트로부터 JSON 메시지를 수신하고, 이를 파싱하여 적절한 응답 JSON 메시지를 생성하여 클라이언트로 다시 보낼 것입니다.
필요 라이브러리: C#에서 JSON을 쉽게 다루기 위해 Newtonsoft.Json (Json.NET) 라이브러리를 사용합니다.
- Newtonsoft.Json 설치 (AI 활용):
- Cursor IDE의 통합 터미널 (Ctrl + ~)을 열고 websocket-server-csharp/MyWebSocketServer 폴더로 이동합니다.
- AI에게 요청: Cmd + K (Ctrl + K)를 누르고 "Add Newtonsoft.Json NuGet package to this C# project." 라고 프롬프트를 입력합니다.
- Cursor가 dotnet add package Newtonsoft.Json 명령어를 제안할 것입니다. Enter를 눌러 실행합니다.
- JSON 메시지 클래스 정의:
- Program.cs 파일에 메시지 구조를 나타내는 C# 클래스를 정의합니다.
- AI에게 요청: Program.cs 파일 상단에 커서를 둔 후 Cmd + K를 누르고 다음과 같이 프롬프트합니다:
"Define C# classes for our WebSocket JSON protocol. Create a base BaseMessage class with a Type string property. Then, create specific classes: ChatMessagePayload, StatusRequestPayload, ChatAckPayload, ServerStatusPayload for the payload parts. Finally, create a generic WebSocketMessage<T> class for the full message, using T for the payload." - Cursor가 아래와 유사한 클래스 정의를 제안할 것입니다. 이를 복사하여 Program.cs 파일에 붙여넣습니다.
// Base message structure public class BaseMessage { public string Type { get; set; } } // Payload classes public class ChatMessagePayload { public string Text { get; set; } public DateTime? Timestamp { get; set; } } public class StatusRequestPayload { // No specific properties for a simple status request } public class ChatAckPayload { public string OriginalMessage { get; set; } public string Status { get; set; } public DateTime ServerTimestamp { get; set; } } public class ServerStatusPayload { public string CpuUsage { get; set; } public string MemoryUsage { get; set; } public int ActiveConnections { get; set; } public DateTime ServerTime { get; set; } } // Generic wrapper for WebSocket messages public class WebSocketMessage<T> : BaseMessage { public T Payload { get; set; } } - ProcessWebSocketRequest 메서드 수정 (JSON 처리 로직):
- 기존 Program.cs 파일의 ProcessWebSocketRequest 메서드를 수정하여 JSON 메시지를 파싱하고, Type에 따라 다른 로직을 처리하도록 합니다.
- ProcessWebSocketRequest 메서드 내부에 커서를 둔 후 Cmd + K를 누르고 프롬프트합니다:
"Modify this ProcessWebSocketRequest method to parse incoming WebSocket messages as JSON using Newtonsoft.Json. Deserialize the message to BaseMessage first to get its type. Then, based on the Type ('CHAT_MESSAGE' or 'STATUS_REQUEST'), deserialize to the correct payload type and handle it. For 'CHAT_MESSAGE', print it and send back a CHAT_ACK JSON. For 'STATUS_REQUEST', send back a SERVER_STATUS JSON with dummy system info. Ensure error handling for JSON parsing." - Cursor가 아래와 유사한 코드를 제안할 것입니다. 기존 ReceiveAsync 이후의 메시지 처리 로직을 아래 코드로 대체합니다.
using System; using System.Net; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; // 이 네임스페이스를 추가합니다. // ... (기존 클래스 정의 및 Main, StartWebSocketServer 메서드는 그대로 유지) private static async void ProcessWebSocketRequest(HttpListenerContext context) { // ... (이전 연결 수립 로직 유지) try { webSocketContext = await context.AcceptWebSocketAsync(subProtocol: null); WebSocket webSocket = webSocketContext.WebSocket; Console.WriteLine($"Client connected: {context.Request.RemoteEndPoint}"); byte[] buffer = new byte[1024 * 4]; // 버퍼 크기 증가 (더 큰 JSON 대비) WebSocketReceiveResult receiveResult = null; while (webSocket.State == WebSocketState.Open) { receiveResult = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); if (receiveResult.MessageType == WebSocketMessageType.Text) { string receivedJson = Encoding.UTF8.GetString(buffer, 0, receiveResult.Count); Console.WriteLine($"Received JSON from client ({context.Request.RemoteEndPoint}): {receivedJson}"); try { // 1단계: 기본 메시지 타입 파싱 (어떤 종류의 메시지인지 확인) BaseMessage baseMessage = JsonConvert.DeserializeObject<BaseMessage>(receivedJson); string responseJson = string.Empty; switch (baseMessage.Type) { case "CHAT_MESSAGE": // 2단계: 실제 페이로드 타입으로 파싱 var chatMessage = JsonConvert.DeserializeObject<WebSocketMessage<ChatMessagePayload>>(receivedJson); Console.WriteLine($"[CHAT_MESSAGE] from client: {chatMessage.Payload.Text}"); // CHAT_ACK 응답 생성 및 전송 var chatAckPayload = new ChatAckPayload { OriginalMessage = chatMessage.Payload.Text, Status = "received", ServerTimestamp = DateTime.UtcNow }; var chatAckMessage = new WebSocketMessage<ChatAckPayload> { Type = "CHAT_ACK", Payload = chatAckPayload }; responseJson = JsonConvert.SerializeObject(chatAckMessage); break; case "STATUS_REQUEST": Console.WriteLine("[STATUS_REQUEST] received from client."); // SERVER_STATUS 응답 생성 및 전송 (더미 데이터) var serverStatusPayload = new ServerStatusPayload { CpuUsage = $"{new Random().Next(10, 50)}%", MemoryUsage = $"{new Random().Next(30, 80)}%", ActiveConnections = 1, // 실제 구현에서는 동적으로 관리 ServerTime = DateTime.UtcNow }; var serverStatusMessage = new WebSocketMessage<ServerStatusPayload> { Type = "SERVER_STATUS", Payload = serverStatusPayload }; responseJson = JsonConvert.SerializeObject(serverStatusMessage); break; default: Console.WriteLine($"Unknown message type: {baseMessage.Type}"); responseJson = JsonConvert.SerializeObject(new { type = "ERROR", payload = new { message = "Unknown message type" } }); break; } if (!string.IsNullOrEmpty(responseJson)) { byte[] sendBuffer = Encoding.UTF8.GetBytes(responseJson); await webSocket.SendAsync(new ArraySegment<byte>(sendBuffer, 0, sendBuffer.Length), WebSocketMessageType.Text, true, CancellationToken.None); Console.WriteLine($"Sent JSON to client ({context.Request.RemoteEndPoint}): {responseJson}"); } } catch (JsonSerializationException ex) { Console.WriteLine($"JSON Deserialization Error: {ex.Message}"); // 클라이언트에 오류 메시지 전송 (선택 사항) string errorJson = JsonConvert.SerializeObject(new { type = "ERROR", payload = new { message = $"JSON parsing failed: {ex.Message}" } }); byte[] errorBuffer = Encoding.UTF8.GetBytes(errorJson); await webSocket.SendAsync(new ArraySegment<byte>(errorBuffer), WebSocketMessageType.Text, true, CancellationToken.None); } catch (Exception ex) { Console.WriteLine($"Error processing message: {ex.Message}"); // 클라이언트에 오류 메시지 전송 (선택 사항) string errorJson = JsonConvert.SerializeObject(new { type = "ERROR", payload = new { message = $"Server error: {ex.Message}" } }); byte[] errorBuffer = Encoding.UTF8.GetBytes(errorJson); await webSocket.SendAsync(new ArraySegment<byte>(errorBuffer), WebSocketMessageType.Text, true, CancellationToken.None); } } else if (receiveResult.MessageType == WebSocketMessageType.Close) { // ... (기존 연결 종료 로직 유지) Console.WriteLine($"Client disconnected: {context.Request.RemoteEndPoint} (Code: {receiveResult.CloseStatus}, Reason: {receiveResult.CloseStatusDescription})"); await webSocket.CloseAsync(receiveResult.CloseStatus.Value, receiveResult.CloseStatusDescription, CancellationToken.None); break; } } } catch (WebSocketException ex) { Console.WriteLine($"WebSocket error: {ex.Message}"); } catch (Exception ex) { Console.WriteLine($"General error in ProcessWebSocketRequest: {ex.Message}"); } finally { webSocketContext?.WebSocket?.Dispose(); Console.WriteLine($"Connection closed for client: {context.Request.RemoteEndPoint}"); } }- 팁: Cursor의 Cmd + E (인라인 편집) 기능을 사용하여 특정 코드 블록 (switch 문 내부 등)에 대한 자세한 구현을 요청할 수도 있습니다.
- C# 서버 빌드 및 실행:
- 터미널에서 websocket-server-csharp/MyWebSocketServer 폴더로 이동합니다.
- dotnet build
- dotnet run
2. Node.js 웹소켓 클라이언트 수정 (JSON 생성 및 파싱)
Node.js 클라이언트는 사용자 입력을 JSON 형식으로 변환하여 서버로 전송하고, 서버로부터 수신된 JSON 메시지를 파싱하여 그 내용에 따라 UI를 업데이트할 것입니다.
- index.html 수정 (새 버튼 추가):
- 서버 상태 요청을 위한 버튼을 추가합니다.
- index.html 파일을 열고 <button id="sendButton">Send</button> 아래에 커서를 둔 후 Cmd + K 를 누르고 프롬프트합니다:
"Add a new button with id statusButton and text 'Get Server Status' below the 'Send' button."
<!-- ... (기존 HTML 코드) ... --> <input type="text" id="messageInput" placeholder="Type your message..."> <button id="sendButton">Send</button> <button id="statusButton">Get Server Status</button> <!-- 새로 추가된 버튼 --> <script src="client.js"></script> </body> </html>
client.js 수정 (JSON 처리 로직):
- client.js 파일을 열고, 메시지 전송 및 수신 로직을 JSON 프로토콜에 맞게 변경합니다.
- AI에게 요청 (sendMessage 함수 수정): sendMessage 함수 내부에 커서를 둔 후 Cmd + K를 누르고 프롬프트합니다:
"Modify this sendMessage function to send the message as a JSON object with type: 'CHAT_MESSAGE' and payload: { text: message }. Use JSON.stringify()." - AI에게 요청 (socket.onmessage 함수 수정): socket.onmessage 함수 내부에 커서를 둔 후 Cmd + K를 누르고 프롬프트합니다:
"Modify this onmessage event handler to parse the incoming JSON message using JSON.parse(). Based on the type property ('CHAT_ACK' or 'SERVER_STATUS'), display the relevant payload data in the #messages div. For CHAT_ACK, show the original message and status. For SERVER_STATUS, show CPU, memory, and active connections." - AI에게 요청 (새 버튼 이벤트 리스너 추가): document.addEventListener('DOMContentLoaded', ...) 블록의 끝 부분, 기존 messageInput.addEventListener 뒤에 커서를 둔 후 Cmd + K를 누르고 프롬프트합니다:
"Add an event listener for the statusButton. When clicked, it should send a JSON message {\"type\": \"STATUS_REQUEST\", \"payload\": {}} to the server via WebSocket." - 아래는 Cursor가 제안한 내용을 바탕으로 완성된 client.js 코드입니다.
document.addEventListener('DOMContentLoaded', () => {
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
const statusButton = document.getElementById('statusButton'); // 새로 추가된 버튼 참조
const socket = new WebSocket('ws://localhost:8080/ws/');
socket.onopen = (event) => {
console.log('WebSocket connection opened:', event);
addMessage('System: Connected to WebSocket server.');
};
socket.onmessage = (event) => {
console.log('Message from server:', event.data);
try {
const receivedData = JSON.parse(event.data); // JSON 파싱
switch (receivedData.type) {
case 'CHAT_ACK':
const chatAckPayload = receivedData.payload;
addMessage(`Server ACK: "${chatAckPayload.originalMessage}" - Status: ${chatAckPayload.status} (at ${new Date(chatAckPayload.serverTimestamp).toLocaleTimeString()})`);
break;
case 'SERVER_STATUS':
const serverStatusPayload = receivedData.payload;
addMessage(`Server Status: CPU ${serverStatusPayload.cpuUsage}, Mem ${serverStatusPayload.memoryUsage}, Connections ${serverStatusPayload.activeConnections} (at ${new Date(serverStatusPayload.serverTime).toLocaleTimeString()})`);
break;
case 'ERROR': // 서버에서 보낸 오류 메시지 처리
addMessage(`Server Error: ${receivedData.payload.message}`);
break;
default:
addMessage(`Server (Unknown Type): ${event.data}`);
break;
}
} catch (e) {
console.error('Failed to parse JSON message from server:', e);
addMessage(`Server (Raw Text Error): ${event.data}`);
}
};
socket.onerror = (event) => {
console.error('WebSocket error:', event);
addMessage('System: WebSocket error occurred.');
};
socket.onclose = (event) => {
console.log('WebSocket connection closed:', event);
addMessage(`System: Disconnected from WebSocket server. Code: ${event.code}, Reason: ${event.reason}`);
};
sendButton.addEventListener('click', () => {
sendMessage();
});
messageInput.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
sendMessage();
}
});
// 상태 요청 버튼 이벤트 리스너 추가
statusButton.addEventListener('click', () => {
if (socket.readyState === WebSocket.OPEN) {
const statusRequestMessage = {
type: "STATUS_REQUEST",
payload: {}
};
socket.send(JSON.stringify(statusRequestMessage));
addMessage('You: Requested server status.');
} else {
addMessage('System: Not connected to server to request status.');
}
});
function sendMessage() {
const message = messageInput.value.trim();
if (message && socket.readyState === WebSocket.OPEN) {
const chatMessage = {
type: "CHAT_MESSAGE",
payload: {
text: message,
timestamp: new Date().toISOString()
}
};
socket.send(JSON.stringify(chatMessage)); // JSON으로 변환하여 전송
addMessage(`You: ${message}`);
messageInput.value = '';
} else if (socket.readyState !== WebSocket.OPEN) {
addMessage('System: Not connected to server. Please wait or refresh.');
}
}
function addMessage(message) {
const p = document.createElement('p');
p.textContent = message;
messagesDiv.appendChild(p);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
});
- Node.js 웹 서버 실행:
- websocket-client-nodejs 폴더의 터미널에서 npm start 실행.
🧪 테스트 및 확인
이제 JSON 프로토콜이 정상적으로 동작하는지 확인해봅시다.
- C# 윈도우 서버 실행:
- websocket-server-csharp/MyWebSocketServer 폴더의 터미널에서 dotnet run 실행.
- Node.js 웹 클라이언트 서버 실행:
- websocket-client-nodejs 폴더의 터미널에서 npm start 실행.
- 웹 클라이언트 접속:
- 웹 브라우저를 열고 http://localhost:3000으로 접속합니다.
- 메시지 전송 및 확인:
- 채팅 메시지 전송: 메시지 입력란에 "Hello AI Server!"라고 입력하고 "Send" 버튼을 클릭합니다.
- 클라이언트 UI에 "You: Hello AI Server!"가 표시되고, 곧이어 "Server ACK: "Hello AI Server!" - Status: received..."와 같은 응답이 표시될 것입니다.
- C# 서버 콘솔에는 수신된 JSON ({"type":"CHAT_MESSAGE", ...}) 및 발송된 JSON ({"type":"CHAT_ACK", ...}) 로그가 찍힙니다.
- 서버 상태 요청: "Get Server Status" 버튼을 클릭합니다.
- 클라이언트 UI에 "You: Requested server status."가 표시되고, 곧이어 "Server Status: CPU XX%, Mem YY%, Connections Z..."와 같은 응답이 표시될 것입니다.
- C# 서버 콘솔에는 수신된 STATUS_REQUEST 및 발송된 SERVER_STATUS JSON 로그가 찍힙니다.
- 채팅 메시지 전송: 메시지 입력란에 "Hello AI Server!"라고 입력하고 "Send" 버튼을 클릭합니다.
💡 Cursor IDE와 AI의 역할 강조
이 과정을 통해 우리는 Cursor IDE가 어떻게 복잡한 프로토콜 구현을 도왔는지 명확히 알 수 있습니다:
- 초기 코드 스캐폴딩: 클래스 정의, 기본적인 이벤트 핸들러 구조 등을 AI가 빠르게 생성해주어 시작 시간을 단축했습니다.
- 라이브러리 및 종속성 관리: Newtonsoft.Json 같은 필요한 라이브러리 설치 명령어를 AI가 제안하여 개발자가 직접 검색할 필요가 없었습니다.
- JSON 직렬화/역직렬화 로직: 복잡한 JsonConvert.DeserializeObject 및 SerializeObject 사용법, 그리고 switch 문을 이용한 메시지 라우팅 로직을 AI가 정확한 구문으로 제안해주었습니다.
- 크로스 플랫폼 언어 지원: C#과 JavaScript/Node.js라는 다른 언어 환경에서도 AI가 각 언어의 특성과 라이브러리(JSON.NET, JSON.parse/stringify)에 맞춰 적절한 코드를 생성하여 언어 간의 장벽을 낮춰주었습니다.
- 오류 처리 및 예외 메시지: JSON 파싱 오류와 같은 예외 처리 로직까지 AI가 제안하여 코드의 안정성을 높이는 데 기여했습니다.
- 반복적인 코드 작성 감소: addMessage와 같은 유틸리티 함수나 UI 업데이트 로직 등 반복적인 UI/통신 로직을 AI가 대신 작성해주어 개발자는 핵심 로직에 집중할 수 있었습니다.
맺음말
Cursor IDE의 AI 기능을 활용하여 Node.js 웹 클라이언트와 C# 윈도우 서버 간에 JSON 기반의 웹소켓 통신 프로토콜을 성공적으로 구현했습니다. 이 과정은 AI가 단순한 코드 자동 완성기를 넘어, 복잡한 시스템 설계와 구현 과정 전반에 걸쳐 강력한 파트너가 될 수 있음을 보여줍니다.
JSON 프로토콜은 매우 유연하기 때문에, 여기에 requestId를 추가하여 요청-응답 매칭을 강화하거나, statusCode를 추가하여 더 세밀한 오류 처리를 구현하는 등 여러분의 필요에 따라 얼마든지 확장할 수 있습니다. 그때마다 Cursor IDE의 AI에게 필요한 로직 추가를 요청해보세요!
Cursor IDE와 함께라면, 여러분은 더 이상 단순 반복적인 코딩 작업에 시간을 낭비하지 않고, 문제 해결과 창의적인 설계에 집중할 수 있게 될 것입니다.
'AI의 활용 가이드 > AI 코딩' 카테고리의 다른 글
| 구글 드라이브와 Cloudflare로 내 블로그에 '끊김 없는' 오디오 플레이어 만들기 (무료) (0) | 2025.08.02 |
|---|---|
| Gemini TTS API에서 음성 속도 (0) | 2025.07.27 |
| (AI 코딩) Cursor IDE와 AI 로 크로스 플랫폼 웹소켓 통신 구현하기 (0) | 2025.06.16 |
| (AI 코딩) Cursor IDE: AI 네이티브 개발 환경에서 코딩 생산성 극대화하기 (0) | 2025.06.16 |
| (AI 코딩) VS Code에서 GitHub Copilot과 함께 코딩 생산성 극대화하기 (0) | 2025.06.16 |
댓글