오늘 한일
오늘 아침 7시부터 일어나서 공을 찼다. 6월 말이어서 그런지 진짜 너무 덥고 밥도 안먹고 뛰어서 그런지 현기증이 제대로 났다.
역시 여름은 실내에서 운동하는게 맞는 것 같다.
그리고 어제 아주 치명적인 에러를 만났는데 윈도우 프로그램에서 해당 페이지에서 사용하는 이벤트를 일으킬 좌표를 갱신하는 함수가 좌표 자체가 전역 변수이기에 다른 이벤트로 넘어가면 해당 창이 넘어가도 그 좌표를 계속 유지하고 있어 그 위에 좌표가 쌓여 이벤트가 뒤죽박죽이 되는 문제가 발견되었다.
이게....... 전역변수로 쓰고 있어서 문제가 생겨서 구조를 바꿔야한다. 당장 내가 일주일 좀 넘게 짠 부분이 5000라인이 넘어가는데 유지보수 용이하게 분할하고 각 구조를 다시 바꾼다고 하니까 아직 그림이 안느껴진다.
이게 신입개발자의 한계인가 싶기도 하고 벽을 느껴버렸다....ㅋㅋㅋ
일단 어제는 금요일이라 다음주의 내가 해야지 하고 퇴근하긴했지만 주말인 지금 다음주가 두렵다.
개인 공부
UDP로 송수신하는 것은 연결에 대한 개념이 없어서 보낼때마다 보내는 곳에대한 정보를 달아서 보내야한다.
TCP에서 데이터를 보낼때는 연결된다면 해당 소켓의 디스크립터나 핸들로 연결된 소켓정보만 보낸다.
UDP는 데이터를 송수신 할 떄 = "이 목적지로 이 데이터를 보내라" 요청한다고 생각하면된다.
즉 이는 서버 소켓과 클라이언트 소켓의 구분이 없다는 뜻이 된다.
연결의 개념이 존재하지 않으니 하나의 소켓으로 둘 이상의 영역과 데이터 통신이 가능하다.
UDP 기반의 데이터 입출력 함수
ssize_t sendto(int sock, void p_buff, size_t nbytes, int flags, struct sockaddr p_to , socklen_t addrlen);
성공시 전송된 바이트 수 반환 , 실패시 - 1
sock = 데이터 전송에 사용될 UDP 소켓의 파일 디스크립터
buff = 전송할 데이터를 저장하고 있는 버퍼의 주소 값 전달.
nbytes = 전송할 데이터 크기를 바이트 단위로 전달.
flags = 옵션 지정에 사용되는 매개변수, 지정할 옵션이 없담녀 0 전달
to = 목적지 주소정보를 담고 있는 sockaddr 구조체 변수의 주소 값 전달.
addrlen = 매개변수 to 로 전달된 주소 값의 구조체 변수 크기 전달.
ssize_t recvfrom(int sock, void p_buff, size_t nbytes ,int flags, struct sockaddr p_from, socklen_t p_addrlen);
성공시 수신한 바이트수 , 실패시 -1 반환
sock = 데이터 수신에 사용될 UDP 소켓의 파일 디스크립터를 인자로 전달
buff = 데이터 수신에 사용될 버퍼의 주소값 전달
nbytes = 수신할 최대 바이트 수 전달, 때문에 매개변수 buff가 가리키는 버퍼의 크기를 넘을수 없다.
flags = 옵션 지정에 사용되는 매개변수 , 지정할 옵션이 없다면 0전달
from = 발신자 정보를 채워 넣음 sockaddr 구조체 변수의 주소 값 전달
addrlen = 매개변수 from으로 전달된 주소에 해당되는 구조체 변수의 크기정보를 담고 있는 변수의 주소값 전달
UDP 기반의 에코 서버와 클라이언트가 있다고 보면
서버는 UDP 에코 서버 코드에서는 수신한 데이터의 전송지 정보를 참조하여 데이터를 에코 한다.
클라이언트는 UDP가 데이터의 경계가 존재하지 않기 때문에 한번의 recvfrom 함수호출을 통해서 하나의 메시지를 완전히 읽어 들인다.
그리고 sendto 함수 호출 시 IP와 port번호가 자동으로 할당 되기 때문에 일반적으로 UDP의 클라이언트 프로그램에서는 주소정보를 할당하는 별도의 과정이 필요없다.
데이터의 경계각 있다는 것은 만약 3개의 데이터를 sendto 를 하면 recvfrom을 하면 데이터가 정확히 읽어진다는 것과 같다.
connected UDP
UDP 개념적으로 연결이 없다. 그러면 connected UDP란 뭘 까?
unconnected UDP 소켓에 sendto 함수 호출과정은 추상적으로 다음 3단계로 표현할 수 있다.
1단계 = UDP소켓에 목적지에 IP와 PORT 번호 등록
2단게 = 데이터 전송
3단계= UDP 소켓에 등록된 목적지 정보 삭제
연결의 개념이 존재하지 않으니 그냥 필요한 데이터를 보내고 주기적으로 삭제한다.
왜 삭제할까? -> 삭제하는 이유를 생각하면 간단하다.
UDP는 다시 거기로 보낸다는 보장을 하지 않기 때문에 그렇다.
이런거에서도 공학자들은 아주아주 불편해한다.
여기서 다시 삭제하는 부분에서 많은 비용이 들기에 둘만 통신하는데 할당하고 해제하고를 계속 반복하면 비효율적이기 때문이다.
비효율을 굉장히 싫어하는 공학자들은 다음과 같은 생각을 했을 것이다.
"TCP에서 호스트의 IP와 Port 번호를 등록하듯이 목적지에 대한 IP와 PORT번호를 등록할수는 없을까? "
해당 생각을 해결하기 위해 만든게 Connected UDP이다.
코드를 봐보자
기존 UDP 코드 예시
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
socklen_t adr_sz;
struct sockaddr_in serv_adr, from_adr;
if(argc!=3){
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_DGRAM, 0);
if(sock==-1)
error_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
while(1)
{
fputs("Insert message(q to quit): ", stdout);
fgets(message, sizeof(message), stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;
sendto(sock, message, strlen(message), 0,
(struct sockaddr*)&serv_adr, sizeof(serv_adr));
adr_sz=sizeof(from_adr);
str_len=recvfrom(sock, message, BUF_SIZE, 0,
(struct sockaddr*)&from_adr, &adr_sz);
message[str_len]=0;
printf("Message from server: %s", message);
}
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
데이터를 UDP소켓의 코드인데
소켓 번호 하나를 할당받고 데이터를 sendto로 데이터를 보내고 데이터를 recvfrom으로 데이터를 받는 기본적인 메커니즘을 가진다.
실은 이 코드를 짜면서 recvfrom에서 갑자기 연결이 끊기는 해프닝이 있었는데 sock()함수로 소켓을 생성할 때,
SOCK_DGRAM으로 선언하는게 아닌 SOCK_STREAM으로 생성했다.
어쩐지 보내도 계속 연결이 끊어지더라니 ;;
연결지향적 소켓이 생성되서 데이터를 보내도 사실상 연결된 상태가 아닌데 생각없이 데이터를 보낼라고 하니까 그냥 아무곳에서 데이터 보내고 비정상적으로 연결이 종료되는 것이었다.
애초에 sendto나 recvfrom 둘 다 기본적인 send처럼 보내고 recv 처럼 받는 로직은 같으나 목적지 자체가 TCP에서는 의미가 없어서 무시되기도 하고 연결대상이 아니어서 올바른 로직이 아니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
socklen_t adr_sz;
struct sockaddr_in serv_adr, from_adr;
if(argc!=3){
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_DGRAM, 0);
if(sock==-1)
error_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
// 다른 udp 코드와 다른점
connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
while(1)
{
fputs("Insert message(q to quit): ", stdout);
fgets(message, sizeof(message), stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;
/*
sendto(sock, message, strlen(message), 0,
(struct sockaddr*)&serv_adr, sizeof(serv_adr));
*/
write(sock, message, strlen(message));
/*
adr_sz=sizeof(from_adr);
str_len=recvfrom(sock, message, BUF_SIZE, 0,
(struct sockaddr*)&from_adr, &adr_sz);
*/
str_len=read(sock, message, sizeof(message)-1);
message[str_len]=0;
printf("Message from server: %s", message);
}
// while(1)이어서 큰 의미없긴하다
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
UDP가 Connect를 사용한다고 진짜 연결이 된건 아니다. UDP로 주기적으로 같은 곳을 사용하기에 미리 설정해놨다고 볼 수 있다.
즉 connect를 사용하지만 이거는 나 스스로 사용하기 위해 등록하기만 한것이다. 즉 상대의 connect를 받았다라는 정보를 받지 않는 것이다.
connected UDP 소켓 대상으로는 read, write가 가능하다.
왜 일까? -> 이미 목적지가 정해져 있기에 쓰고 읽기만 하면 되기 때문이다.
'일상,취미' 카테고리의 다른 글
다시 시작하는 TIL 19일차 (1) | 2024.07.03 |
---|---|
다시 시작하는 TIL 18일차 (0) | 2024.07.02 |
다시 시작하는 TIL 16일차 (0) | 2024.06.28 |
다시 시작하는 TIL 15일차 (0) | 2024.06.27 |
다시 시작하는 TIL 14일차 (0) | 2024.06.26 |