[C 언어] 소켓통신 코드로 연산결과 가져오기
카테고리: C
처음 풀 때 걸린시간
8시간
1. 문제
- 필요한 결과물 사진
- 클라이언트 에서 연산에 쓰일 연산자의 개수를 입력한다.
- 연산자의 개수만큼 숫자들을 입력하고난 뒤 연산자를 입력한다 ( +, -, * 만 ! 나누기는 제외한다. )
- 서버 에서 연산결과를 받아와서 클라이언트 에서 출력한다.
- 전 문제와 마찬가지로 종료할 때는 count를 0 으로 주고, 서버 는 BACKLOG 만큼 호스트를 받을 수 있어야한다.
반드시 지켜야 할 조건
- char형 배열을 1바이트(count) 4바이트(int형 숫자) … 4바이트(int형 숫자) 1바이트(연산자) -> 이런식으로 크기 할당을 해야한다.
2. 정답
클라이언트 정답
#include <stdio.h>
#include <stdlib.h>
#include <ws2tcpip.h>
#include <winsock2.h>
#define BUF 1024
#define INT 4
void errorHandling(char* message);
int main(int argc, char* argv[])
{
// definition
WSADATA wsadata;
SOCKET hSocket;
SOCKADDR_IN servAddr;
SSIZE_T recvLen, strLen;
int i, j;
char result[INT]; // define to char because of second parameter of recv function -> char* buf
char arr[BUF]; // array
int count, forgetchar; // count, getchar
int temp; // scanf_s
// parameter check
if (argc != 3)
{
printf("Usage: %s <IP> <PORT> \n", argv[0]);
exit(1);
}
// init
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
errorHandling("WSAstartup() error");
// socket
hSocket = socket(PF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET)
errorHandling("socket() error");
// memset init
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &servAddr.sin_addr); // <ws2tcpip.h>
servAddr.sin_port = htons(atoi(argv[2]));
// connect
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
errorHandling("connect() error");
else
printf("connected......................\n");
// input
while (1)
{
// count
printf("operand count ( input 0 to quit ): ");
scanf_s("%c", &arr[0], (unsigned int)sizeof(int));
count = atoi(&arr[0]);
if (count == 0) break;
printf("받은 카운트는 : %d \n", count);
// scanf_s
j = 1;
for (i = 1; i < count * INT; i += INT)
{
printf("operand %d :", j++);
scanf_s("%d", &temp);
forgetchar = getchar();
arr[i + 0] = temp;
arr[i + 1] = temp >> 8;
arr[i + 2] = temp >> 16;
arr[i + 3] = temp >> 24;
}
/* for (i = 1; i < count * INT; i += INT)
printf("입력된 값들: %d \n", *((int*)(arr + i)));*/
// (arr+i) -> 주소값
// (int*) -> 포인터
// *( ) -> 참조값
// 결과 *((int*)(arr + i))
// arr[i] -> 참조값
// (int*) -> 포인터
// 결과: *((int*)&(arr[i])) ㅋㅋㅋ
// operator
printf("operator: ");
scanf_s("%c", &arr[count * INT + 2], (unsigned char)sizeof(char));
forgetchar = getchar();
// send & recv
strLen = send(hSocket, arr, sizeof(int) * count + 3, 0);
if (strLen == -1) errorHandling("send() error");
printf("클라에서 보내는바이트는? %d \n", (int)strLen);
recvLen = recv(hSocket, result, sizeof(result), 0);
if (recvLen == -1) errorHandling("recv() error");
printf("클라에서 받은바이트는? %d \n", (int)recvLen);
// result
printf("Result from server : %d \n", *((int*)result));
}
closesocket(hSocket);
WSACleanup();
return 0;
}
void errorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
서버 정답
#include <stdio.h>
#include <stdlib.h>
#include <ws2tcpip.h>
#include <winsock2.h>
#define BUF 1024
#define INT 4
#define BACKLOG 5
#define CHAR 1
void errorHandling(char* message);
int cal(unsigned char* arr, int cnt, char oper);
int main(int argc, char* argv[])
{
// definition
WSADATA wsadata;
SOCKET hServSock, hClntSock;
SOCKADDR_IN servAddr, clntAddr;
int szClntAddr;
// parameter check
if (argc != 2)
{
printf("Usage : %s <PORT> \n", argv[0]);
exit(1);
}
// init
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
errorHandling("WSAstartup() error");
// socket()
hServSock = socket(PF_INET, SOCK_STREAM, 0);
if (hServSock == INVALID_SOCKET)
errorHandling("socket() error");
// memset init
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(atoi(argv[1]));
// bind()
if( bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR )
errorHandling("bind() error");
// listen()
if (listen(hServSock, BACKLOG) == SOCKET_ERROR)
errorHandling("listen() error");
else
printf("입력 대기 중 ..... \n");
// accept()
int rep;
SSIZE_T strLen, recvLen, recvTot, sendLen;
char arr[BUF];
int count;
int result;
int i, j; // for operand values check
for (rep = 0; rep < BACKLOG; rep++)
{
// size
szClntAddr = sizeof(clntAddr);
// accept
hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
if (hClntSock == INVALID_SOCKET) errorHandling("accept() error");
// recv, write
while ((strLen = recv(hClntSock, arr, CHAR, 0) != 0))
{
if (strLen == SOCKET_ERROR) errorHandling("recv() error");
printf("서버에서 받은 바이트 수 : %d \n", (int)strLen);
// 바꿔서 받아야지 인마
count = atoi(&arr[0]);
printf("받은 카운트 수 : %d \n", count);
recvTot = 1; // 카운트 하나 받았으니까 1개 빼자.
recvLen = 1; // 마찬가지
while (recvTot < count*INT + 2)
{
recvLen = recv(hClntSock, &arr[recvTot], BUF - 1, 0);
if (recvLen == SOCKET_ERROR) errorHandling("recv() error");
printf("서버에서 받은 바이트 수 : %d\n", (int)recvLen);
recvTot += recvLen;
}
// check operands
j = 1;
for (i = 1; i < count * INT; i += INT)
{
printf("들어온 operand %d :", j++);
printf("%d \n", *((int*)(arr + i)));
}
// operator check
printf("operator : %c \n", arr[count * INT + 2]);
// call calculator
// arr의 첫번째 주소값을 줬기 때문에 cal 함수 안에서 i = 1 부터 시작한 것
// 다른 프로젝트에선 &arr[1] 이렇게 주었기 때문에 cal함수 안에서 i = 0으로 시작해도 됐었다는 것.
result = cal(arr, count, arr[count * INT + 2]);
printf("결과값은 : %d \n", result);
sendLen = send(hClntSock, &result, sizeof(result), 0);
if (sendLen == SOCKET_ERROR) errorHandling("send() error");
printf("전달한 바이트는? %d \n", (int)sendLen);
}
// close() -> client
closesocket(hClntSock);
}
// close() -> server
closesocket(hServSock);
WSACleanup();
return 0;
}
void errorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int cal(unsigned char* arr, int cnt, char oper)
{
int i, result = 0;
// check
for (i = 1; i < cnt * INT; i += INT)
printf("입력한 값 %d : %d \n", i + 1, *((int*)(arr + i)));
for (i = 1; i < cnt * INT; i += INT)
{
if (oper == '+') result += *((int*)(arr + i));
else if ( oper == '-') result -= *((int*)(arr + i));
else if (oper == '*')
{
if (i == 1) result = 1;
result *= *((int*)(arr + i));
}
}
return result;
}
3. 피드백
- 프로젝트를 새로 만들어서 해야할 때는 다음 사진과 같이 ws2_32.lib 라이브러리를 추가 해야한다.
3-1. 클라이언트 피드백
- parameter check 까먹지 말 것
-
inet_pton() 은 클라이언트 에서만 쓰이고, 쓰기 위한 헤더는 ws2tcpip.h 라는 것
- *((int*)(arr+i)) 에 대해서
(arr+i) -> 주소값
(int*) -> 포인터
*( ) -> 참조값
결과: *((int*)(arr + i))
arr[i] -> 참조값
(int*) -> 포인터
결과: *((int*)&(arr[i])) // 좋지 않은 방법 ( 가독성 떨어짐 )
3-2. 서버 피드백
- 받을 때 크기가 다르게 했던 이유 ( 꼭 이렇게 하지 않아도 된다. )
배열의 값들을 보면 13번 인덱스에 아무것도 들어있지 않다.
그것은 서버 에서는 변수 count 를 위해서 recv 함수를 미리 한 번 썼기 때문이다.
그래서 2번째 recv() 함수를 읽어올 때는 배열의 첫번째인 0 인덱스가 아닌
두번째인 1인덱스부터 읽기 때문에 한 자리가 비어있는 것.
3-3. 공용 피드백
중요한 keypoint
-
char형 배열에 int만큼의 크기를 넣는 방법은 강제 캐스팅이 아닌
비트 연산자였다는 것.
#define INT 4
int i;
int temp;
int cnt = 3;
for( i = 0 ; i < cnt * INT ; i+=4)
{
scanf_s("%d", &temp);
arr[i + 0] = temp;
arr[i + 1] = temp >> 8;
arr[i + 2] = temp >> 16;
arr[i + 3] = temp >> 24;
}
위처럼 temp라는 int형 변수 에 숫자를 비트별로 나누어서
arr의 char형 배열에 1인덱스씩 4번 각각 대입 한 것
이 방법이 keypoint 였다고 생각한다.
댓글 남기기