✅🥥❌
기록 문제 해결
불친절한 설명
: 이건 나중에 블로그에 한꺼번에 정리하면 될듯. 이건 문제가 아니다.
과도한 시간 소모
: 아무거나 막 쓰는게 문제인 것 같다. 내가 나중에 보면 좋겠다 싶을만한
1. 개념에 대한 정리
2. trouble shooting
3. 내가 한 것에 대한 설명
만 적자. 1.이랑 3.의 비중이 낮았던 것도 문제지만,
trouble shooting을 두서 없이 적었던게 진짜 문제.
생각하면서 정리하려면 오히려 시간이 더 걸릴수도?
그래도 의미 있는 기록을 남기는 것이니 괜찮을듯.
주간 회고의 부재
매주 목요일 공부 시작 전에
1. 이번 주차에 무엇을 했는지
2. 느낀 점이 무엇이었는지
3. 이번 주차에 대한 피드백
-> 노력에 비해 리턴이 별로일 것 같음. 하지 말자.
Time tracking?
시간 기록하느라 비효율이 있긴 한데 기상이랑 수면 시간 조절하고
비는 시간 줄이도록 노력할 수 있을듯. 일단 3일 정도만 해보자
-> 막상 해보니까 마음이 살짝 조급해지기도 하고 시간 관리도 잘 할 수 있게 되는듯. 계속 해도 될 것 같다.
경쟁 조건은 여러 프로세스나 스레드가 공유 자원에 동시에 접근하려고 할 때 발생하는 문제입니다. 경쟁 조건이 발생하면 프로세스들의 접근 순서에 따라 의도치 않게 실행 결과가 달라질 수 있습니다.
데드락은 두 개 이상의 프로세스나 스레드가 서로가 소유하고 있는 자원을 기다리면서 영원히 대기 상태에 빠지는 상황입니다. 데드락이 발생하려면 네 가지 조건이 필요합니다.
자원을 여러 프로세스가 동시에 사용할 수 없는 상호 배제(Mutual exclusion), 자원을 점유한 상태에서 다른 자원을 기다리는 점유 상태로 대기(Hold and wait), 할당된 자원을 뺏어올 수 없는 선점 불가(No preemption), 모든 프로세스가 원형으로 물고 물리면서 서로의 자원을 기다리는 상황을 순환성 대기(Circular wait)가 모두 충족되면 교착 상태, 즉 데드락이 일어납니다.
-> 방금 나무위키를 보다가 깨달은 건데, 아무리 내가 정리한 걸 잘 외웠다고 하더라도 이 정도의 얕은 지식을 읊어봤자 꼬리 질문 몇 개로 털릴 것 같다. 한 주제에 대해서 deep하게 정리하는게 나을듯? 알고리즘 스터디에 책 읽고 구현까지 해야 할텐데 시간이 너무 모자람. 그렇다고 하더라도 얕게 넓은 지식은 그닥 쓸모가 없을 것 같음. 일단 게임 프로그래머 면접 질문 주제들을 자세하게 정리한 글을 쓰는 식으로 선회해야 함. 조금씩이라도 써보자. 일단 매일 최소 30분은 투자하자.
아 근데 또 너무 정글 과정 이외에 것에 과투자해버리는게 아닐지 생각이 들긴 한다. 어차피 나만무 끝나면 시간 주어질거고, 지금 열심히 해놓고 나중에 지금 쌓아놓은 지식 기반으로 금방 학습하면 되는데 굳이 여기에 시간을 할애해야 하나? 라는 생각이 들어서 안하기로 함. 심심하거나 밥 먹을 때 찾아보는 선에서 끝낼 것. 퀴즈까지는 정글에 있던거 놓친거니까 공부해보면 좋을지도?
arr.sort(key=lambda x: x[1])
arr.sort(key=lambda x: x[1])
이거 람다 꼭 넣어야 정렬됨? key=x[1] 이런 느낌으로 하면 안됨?
파이썬의 sort() 메서드에서 key 매개변수는 함수를 기대합니다. 이 함수는 각 요소에 대해 호출되어 반환된 값을 기준으로 정렬을 수행합니다. lambda 함수를 사용하면 간단하게 인라인으로 함수를 정의할 수 있습니다.
컴퍼스 내용 중에
accept() : read/write 또는 receive/send 메소드를 짝으로 사용하여 요청/응답 처리. 단, listen()에서의 스레드와 다른 스레드를 만들어 진행한다.
→ 스레드 분리 이유: 서버는 여러 요청을 받아야 하기에, listen() 스레드는(母) 놔두고 계속 기다리게 하고, accept() 스레드는 (子) 요청을 처리하도록 만들어 둠
라는게 있었음. 살짝 이해가 안돼서 물어봄.
listen()한테 악수 요청(연결 요청) 여러 개 오면 accept()라는 손(연결 수락)을 새로 하나 만들어서 악수(연결)한다는 뜻인듯.
위 설명은 다중 클라이언트 요청을 처리하기 위해 서버가 스레드를 어떻게 사용하는지를 설명하고 있습니다. 이를 이해하기 위해서는 서버 소켓 프로그래밍의 기본 개념과 스레드의 역할을 알아야 합니다.
서버 소켓:
socket()
, bind()
, listen()
, accept()
등의 함수가 이 과정에 사용됩니다.스레드:
accept()
함수는 클라이언트의 연결 요청을 수락하고, 클라이언트와의 통신을 위한 새 소켓을 반환합니다.read/write
또는 recv/send
함수는 이 새 소켓을 통해 클라이언트와 데이터를 주고받습니다.주 스레드 (listen()
스레드):
listen()
함수를 호출하여 클라이언트의 연결 요청을 기다립니다.자식 스레드 (accept()
스레드):
accept()
함수를 호출하여 클라이언트의 연결 요청을 수락합니다.read/write
또는 recv/send
함수로 클라이언트와 통신합니다.https://www.youtube.com/watch?v=LtXEMwSG5-8
소켓 : 네트워크로 정보 전송하기 위한 low-level 도구.
HTTP나 FTP같은 네트워킹 프로토콜들이 연결을 위해 소켓을 사용한다.
원래 "클라이언트" 소켓 같은 건 없다. 소켓은 데이터 주고 받기 둘 다 가능.
정보 받기만 하는 클라이언트 소켓은
#include <sys/types.h>
: size_t
, ssize_t
, pid_t
등 데이터 타입이 정의돼있음
#include <sys/socket.h>
: 소켓 관련 함수들과 데이터 타입들.
ex. socket(), bind(), listen(), accept(), connect()
#include <netinet/in.h>
: 인터넷 주소 패밀리(AF_INET)와 관련된 구조체와 상수들.
ex. sockaddr_in 구조체와 htons(), htonl(), ntohs(), ntohl() 함수들
// 소켓 만들기
int network_socket;
network_socket = socket(AF_INET, SOCK_STREAM, 0);
int network_socket;
: 소켓을 나타내는 정수형 변수 선언network_socket = socket(AF_INET, SOCK_STREAM, 0);
// 소켓의 주소를 입력
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_port = htons(9002);
server_address.sin_addr.s_addr = INADDR_ANY;
struct sockaddr_in server_address;
: sockaddr_in 구조체를 사용하여 서버 주소 정보를 저장할 server_address 변수를 선언함.server_address.sin_family = AF_INET;
: 주소 패밀리를 IPv4로 설정server_address.sin_port = htons(9002);
: 포트 번호를 9002로 설정. htons()
함수는 호스트 바이트 순서에서 네트워크 바이트 순서(빅 엔디안)로 변환함. 네트워크에서 데이터가 올바르게 해석될 수 있도록 하기 위함임.server_address.sin_addr.s_addr = INADDR_ANY;
: 모든 IP 주소와 연결될 수 있도록 주소를 설정. INADDR_ANY는 로컬 머신의 모든 네트워크 인터페이스를 의미."htons() 함수는 호스트 바이트 순서에서 네트워크 바이트 순서(빅 엔디안)로 변환함." 이 뭔소린데?
호스트와 네트워크 바이트 순서
htons() 함수
// 0이면 잘 작동하고 있다는 뜻, 1이면 뭔가 오류가 일어났다는 뜻
int connection_status = connect(network_socket, (struct sockaddr *)&server_address, sizeof(server_address));
// 연결 잘 됐는지 확인
if (connection_status == -1)
{
printf("There was an error making a connection to the remote socket \n\n");
}
connect()
connect()
함수는 클라이언트 소켓을 서버 주소에 연결함. 이 함수가 성공하면 클라이언트 소켓은 서버 소켓과 연결되어 데이터를 주고받을 수 있음.errno
를 통해 에러의 원인을 알 수 있음.매개변수
network_socket
: 클라이언트 소켓의 파일 디스크립터. socket() 함수 호출 시 반환된 값임.(struct sockaddr *)&server_address
: 서버 주소를 나타내는 sockaddr_in 구조체의 주소. connect 함수는 sockaddr 구조체 포인터를 인수로 받기 때문에, 타입 캐스팅을 통해 sockaddr_in 구조체 포인터를 sockaddr 포인터로 변환해야 함.sizeof(server_address)
: sockaddr_in 구조체의 크기. connect 함수는 주소 구조체의 크기를 필요로 함.// 서버에서 데이터 받아오기
char server_response[256];
recv(network_socket, &server_response, sizeof(server_response), 0);
서버에서 데이터 받아오기:
- char server_response[256];
: 서버로부터 받을 데이터를 저장할 배열 server_response 선언.
- recv(network_socket, &server_response, sizeof(server_response), 0);
: 서버로부터 데이터를 수신함.
// 서버의 응답을 출력함
printf("The server sent the data: %s\n", server_response);
// 그리고 소켓 닫음
close(network_socket);
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main()
{
// 소켓 만들기
int network_socket;
network_socket = socket(AF_INET, SOCK_STREAM, 0);
// 소켓의 주소를 입력
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_port = htons(9002);
server_address.sin_addr.s_addr = INADDR_ANY;
// 0이면 잘 작동하고 있다는 뜻, 1이면 뭔가 오류가 일어났다는 뜻
int connection_status = connect(network_socket, (struct sockaddr *)&server_address, sizeof(server_address));
// 연결 잘 됐는지 확인
if (connection_status == -1)
{
printf("There was an error making a connection to the remote socket \n\n");
}
// 서버에서 데이터 받아오기
char server_response[256];
recv(network_socket, &server_response, sizeof(server_response), 0);
// 서버의 응답을 출력함
printf("The server sent the data: %s\n", server_response);
// 그리고 소켓 닫음
close(network_socket);
return 0;
}
이게 기본적인 클라이언트 소켓 프로그램을 구현한 것임
socket(): 소켓 생성
bind(): 소켓을 IP 주소와 포트에 바인딩
listen(): 소켓을 수신 대기 상태로 만듦
accept(): 들어오는 연결을 수락
send() / recv(): 데이터를 전송하거나 수신
위 코드는 서버 소켓을 설정하고 클라이언트의 연결을 받아들인 후, 클라이언트에게 메시지를 보내는 과정을 보여줍니다. 각 줄의 역할을 설명하겠습니다.
// IP랑 포트를 바인드
bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));
bind()
함수는 소켓을 특정 IP 주소와 포트에 바인딩함server_socket
: 이전에 생성한 서버 소켓의 파일 디스크립터(struct sockaddr *)&server_address
: 서버의 IP 주소와 포트 정보가 들어 있는 sockaddr_in 구조체를 sockaddr 구조체로 캐스팅한 것입니다.listen(server_socket, 5);
listen()
함수는 소켓을 수신 대기 상태로 설정함5
: 대기열의 최대 길이입니다. 즉, 동시에 대기할 수 있는 연결 요청의 최대 개수입니다.perror()
등을 사용하여 오류를 출력합니다.int client_socket;
client_socket = accept(server_socket, NULL, NULL);
accept()
함수는 대기열에서 클라이언트의 연결 요청을 수락하고, 새로 생성된 소켓의 파일 디스크립터를 반환.NULL, NULL
: 원래 이 칸에는 클라이언트의 주소 정보를 저장할 변수를 지정할 수 있음. 지금은 주소 정보를 안 써서 NULL로 설정.// 메세지 보내기
send(client_socket, server_message, sizeof(server_message), 0);
send()
함수는 데이터(메시지)를 소켓을 통해 클라이언트로 보냄server_message
: 전송할 데이터(메시지)가 저장된 버퍼0
: 기본 플래그 값입니다.#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main() {
// 서버 소켓 생성
int server_socket;
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
perror("socket failed");
exit(1);
}
// 서버 주소 설정
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_port = htons(9002);
server_address.sin_addr.s_addr = INADDR_ANY;
// IP와 포트 바인딩
if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) < 0) {
perror("bind failed");
close(server_socket);
exit(1);
}
// 연결 대기
if (listen(server_socket, 5) < 0) {
perror("listen failed");
close(server_socket);
exit(1);
}
// 클라이언트 연결 수락
int client_socket;
client_socket = accept(server_socket, NULL, NULL);
if (client_socket < 0) {
perror("accept failed");
close(server_socket);
exit(1);
}
// 클라이언트에게 메시지 보내기
char server_message[] = "Hello from the server!";
if (send(client_socket, server_message, sizeof(server_message), 0) < 0) {
perror("send failed");
close(client_socket);
close(server_socket);
exit(1);
}
// 소켓 닫기
close(client_socket);
close(server_socket);
return 0;
}
기본적인 서버 소켓 프로그램
➜ TCPclient make tcp_server
cc tcp_server.c -o tcp_server
tcp_server.c: In function ‘main’:
tcp_server.c:35:5: warning: implicit declaration of function ‘close’; did you mean ‘pclose’? [-Wimplicit-function-declaration]
35 | close(server_socket);
| ^~~~~
| pclose
코드 똑같이 작성했는데 make 했더니 오류 남. 내 잘못인가? 했는데 아님.
다른 사람도 그런가? 했는데 댓글 창 검색해보니 아님.
일단 경고 무시하고 테스트 진행해봤는데 정상 작동 함. 메시지 잘 받아짐.
close는 파일 디스크립터를 닫는 거고,
pclose는 popen으로 연 파이프 스트림을 닫는 거임.
아까 client_socket이 성공 시 새로 생성된 소켓의 파일 디스크립터를 반환
한다고 했으니 close가 맞는듯.
https://www.youtube.com/watch?v=mStnzIEprH8
HTTP Client : 요청할 resource과 사용한 method를 담아서 request를 보낸다
HTTP Server : response body와 status code와 함께 응답을 보낸다
// 보낼 파일을 연다
FILE *html_data;
html_data = fopen("index.html", "r");
FILE *html_data;
: 파일 포인터 선언html_data = fopen("index.html", "r");
: 현재 디렉토리에서 index.html 파일을 읽기 모드("r")로 연다.fopen
함수는 파일을 열고 파일 포인터를 반환한다. 파일을 열 수 없는 경우, NULL을 반환한다.char response_data[1024];
fgets(response_data, 1024, html_data);
char response_data[1024];
: 파일 내용을 저장할 배열 선언fgets(response_data, 1024, html_data);
: html_data 파일에서 최대 1024바이트를 읽어서 response_data 배열에 저장 fopen
이 실패했을 경우를 처리해야 함. (ex. 파일이 존재하지 않거나 읽기 권한이 없을 때)if (html_data == NULL) {
perror("Failed to open file");
return 1;
}
if (fgets(response_data, sizeof(response_data), html_data) == NULL) {
perror("Failed to read file");
return 1;
}
char http_header[2048] = "HTTP/1.1 200 OK\r\n\n";
strcat(http_header, response_data);
char http_header[2048] = "HTTP/1.1 200 OK\r\n\n";
: 응답 헤더를 저장할 배열을 선언하고 초기화HTTP/1.1 200 OK
: HTTP 버전 1.1을 사용하고, 상태 코드가 200(OK)임을 나타냄.\r\n\n
: 헤더와 본문 사이를 구분하기 위한 빈 줄.strcat(http_header, response_data);
: response_data의 내용을 http_header 배열 뒤에 이어 붙임. 이렇게 하면 HTTP 응답 헤더와 HTML 파일의 내용이 결합됨.int client_socket;
while (1)
{
client_socket = accept(server_socket, NULL, NULL);
send(client_socket, http_header, sizeof(http_header), 0);
close(client_socket);
}
무한 루프를 통해 클라이언트의 연결 요청을 지속적으로 수락하고, HTTP 응답을 보냄
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
int main()
{
// 보낼 파일을 연다
FILE *html_data;
html_data = fopen("index.html", "r");
// 응답할 때 사용할 데이터를 준비한다
char response_data[1024];
fgets(response_data, 1024, html_data);
// HTTP 헤더를 준비한다
char http_header[2048] = "HTTP/1.1 200 OK\r\n\n";
strcat(http_header, response_data);
// 소켓 만들기
int server_socket;
server_socket = socket(AF_INET, SOCK_STREAM, 0);
// 주소를 정의한다
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_port = htons(8001);
server_address.sin_addr.s_addr = INADDR_ANY;
bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));
listen(server_socket, 5);
// 클라이언트의 연결 요청을 지속적으로 수락한다
int client_socket;
while (1)
{
client_socket = accept(server_socket, NULL, NULL);
send(client_socket, http_header, sizeof(http_header), 0);
close(client_socket);
}
return 0;
}
코드 잘못 썼나 숨은 그림 찾기 해봤는데 틀린 부분 없었음.
댓글창을 보니 바로 정답을 알려줌.
내 vscode에선 의문의 코드 포매터가 저장할 때마다 강제로 형식을 바꿔버리고 있음.
c에서도 자꾸 그러길래 찾으려고 해봤는데 아무리 봐도 없음.
Prettier나 이런거 싹 다 Disable 해놨는데도 없음.
이유를 모르겠음.
아무튼 댓글에 따르면 이렇게 한 줄로 해야 한다고 함.
영상에도 한 줄로 나와있음.
vim은 신인가? (신 아님)
char *address;
address = argv[1];
argv[1]
: 프로그램 실행 시 제공된 첫 번째 인수 // 주소로 연결
struct sockaddr_in remote_address;
remote_address.sin_family = AF_INET;
remote_address.sin_port = htons(80);
inet_aton(address, &remote_address.sin_addr.s_addr);
line 1-3 : 서버 주소 저장할 구조체 선언하고 주소 체계는 IPv4, 포트 번호는 80로 설정.
inet_aton(address, &remote_address.sin_addr.s_addr);
: address 문자열(IP 주소)을 네트워크 주소 구조체로 변환하여
remote_address.sin_addr.s_addr에 저장함.
inet_aton() 함수 : 문자열 형식의 IPv4 주소를 네트워크 바이트 순서의 32비트 이진 값으로 변환함. 네트워크 프로그래밍에서 IP 주소를 처리할 때 사용됨.
char request[] = "GET / HTTP/1.1\r\n\r\n";
char response[4096];
char request[] = "GET / HTTP/1.1\r\n\r\n";
:
: HTTP GET 요청 문자열을 정의함. 이 요청은 루트 경로(/
)에 대한 HTTP/1.1 GET 요청임.
char response[4096];
: 서버로부터의 응답을 저장할 버퍼를 선언
send(client_socket, request, sizeof(request), 0);
recv(client_socket, &response, sizeof(response), 0);
send(client_socket, request, sizeof(request), 0);
: 서버에 HTTP GET 요청recv(client_socket, &response, sizeof(response), 0);
: 서버로부터의 응답 수신#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
char *address;
address = argv[1];
int client_socket;
client_socket = socket(AF_INET, SOCK_STREAM, 0);
// 주소로 연결
struct sockaddr_in remote_address;
remote_address.sin_family = AF_INET;
remote_address.sin_port = htons(80);
inet_aton(address, &remote_address.sin_addr.s_addr);
connect(client_socket, (struct sockaddr *)&remote_address, sizeof(remote_address));
char request[] = "GET / HTTP/1.1\r\n\r\n";
char response[4096];
send(client_socket, request, sizeof(request), 0);
recv(client_socket, &response, sizeof(response), 0);
printf("response from the server: %s\n", response);
close(client_socket);
}
기본적인 HTTP 클라이언트 구현
명령줄 인수로 제공된 IP 주소의 서버에 HTTP GET 요청을 보내고,
서버로부터의 응답을 받아 출력함.
모든 네트워크 응용 프로그램들은 동일한 프로그래밍 모델에 기초하고 있으며, 논리 구조도 비슷하며, 프로그래밍 인터페이스도 동일하다.
모든 네트워크 응용 프로그램은 클라이언트-서버 모델에 기초함.
클라이언트-서버 모델
클라이언트-서버 트랜잭션
클라이언트와 서버는 프로세스임. 호스트 또는 머신이 아님.
한 개의 호스트는 동시에 여러 개의 클라이언트와 서버를 실행할 수 있음.
호스트 입장에서 네트워크는 데이터를 보내고 받는 또 다른 입출력 장치일 뿐임.
네트워크 어댑터가 I/O 버스의 확장 슬록에 꽂혀 네트워크와 물리적 인터페이스를 제공함.
네트워크에서 수신된 데이터는 I/O와 메모리 버스를 거쳐 어댑터에서 메모리로 복사된다.
반대로 메모리에서 네트워크로 복사될 수도 있음.
네트워크는 지리적 근접성에 따라 계층적으로 구성됨.
가장 Low-level은 로컬 영역 네트워크(LAN, Local Area Network)로, 빌딩이나 캠퍼스에 설치됨.
가장 인기 있는 LAN 기술은 이더넷(Ethernet)으로, 1970년대 개발됨.
이더넷 세그먼트는 허브들로 구성됨. 방이나 빌딩의 층 같이 작은 지역에 설치함.
호스트들은 허브에 연결되고, 허브들은 브리지로 연결됨. 브리지는 다른 브리지랑 연결됨.
각 이더넷 어댑터는 자신의 비휘발성 메모리에 저장된, 전 세계적으로 고유한 48비트 주소를 가지고 있음.
호스트는 프레임이라고 불리는 비트 덩어리들을 다른 호스트한테 보낼 수 있음.
각 프레임은 출발지와 목적지의 주소, 프레임 길이를 식별하는 헤더 비트와 데이터 비트로 구성됨.
모든 호스트 어댑터는 프레임을 수신하지만, 목적지 호스트만이 실제로 프레임을 읽음.
이거 설마 허브가 자기한테 연결된 모든 호스트들한테 프레임을 쫙 뿌린다는 거임?
네, 정확히 이해하셨습니다. 허브는 연결된 모든 호스트들에게 프레임을 브로드캐스트합니다. 허브는 네트워크 계층에서 레이어 1 장치로 동작하여, 들어오는 데이터 패킷을 모든 포트로 전송합니다. 이 과정에서 특정 목적지 호스트를 지정하지 않고, 네트워크에 연결된 모든 장치에게 데이터를 전달합니다.
따라서, 네트워크에 있는 모든 호스트가 프레임을 수신하게 되지만, 프레임의 목적지 주소를 확인하여 실제로 해당 프레임을 읽고 처리하는 것은 목적지 호스트만입니다. 다른 호스트들은 자신과 무관한 프레임이므로 이를 무시하게 됩니다.
이와 달리 스위치는 네트워크 계층에서 레이어 2 장치로 동작하여, MAC 주소 테이블을 이용해 프레임을 목적지 호스트에게만 전달합니다. 이로 인해 네트워크 트래픽이 줄어들고, 보안성도 향상됩니다.
여러 이더넷 세그먼트는 브리지(bridge)라는 작은 박스를 사용해서 브리지드 이더넷으로 연결될 수 있음.
브리지드 이더넷은 전체 건물이나 캠퍼스 규모로 설치될 수 있음.
브릿지의 일부 선은 브릿지를 브릿지로 연결하고, 다른 선들은 브릿지를 허브로 연결함.
당연하지만 브릿지-브릿지 선(1Gb/s)이 허브-브릿지 선(100Mb/s)보다 대역폭이 높음.
브리지는 우수한 분산 알고리즘을 사용해서 어떤 호스트가 어떤 포트에서 접근 가능한지를 자동으로 학습하고 필요한 경우에만 프레임을 다른 포트로 복사함.
여러 호환되지 않는 LAN은 라우터(router)라는 특수 컴퓨터를 사용해서 인터넷(internet)으로 연결될 수 있다.
라우터는 각 네트워크에 대한 어댑터를 가지고 있고, WAN(Wide-Area Network)도 연결할 수 있음.
WAN은 LAN보다 지리적으로 더 넓은 지역에서 운용되므로 붙은 이름임.
라우터는 임의의 LAN과 WAN들로 internet을 구축할 수도 있음.
인터넷은 매우 다양하고 비호환적인 기술을 갖는 여러 LAN과 WAN들로 이루어져 있음.
이러한 어려움을 극복하고 데이터를 전송하기 위해, 네트워크 프로토콜 소프트웨어에서 두 가지 기본 기능을 제공함.
네트워크 프로토콜 소프트웨어 : 호스트와 라우터에서 실행됨
명명법 (Naming Scheme) : 서로 다른 LAN 기술은 호스트에 주소를 할당하는 방법도 다름. 인터넷 프로토콜은 호스트 주소를 위한 통일된 포맷을 정의해서 차이점을 완화한다. 각 호스트는 자신을 유일하게 식별하는 internet 주소를 최소한 1개 할당받는다.
전송 매커니즘 (delivery mechanism) : 서로 다른 네트워킹 기술은 비트를 인코딩하고 프레임으로 패키징하는 방법이 다르다. 인터넷 프로토콜은 데이터 비트를 패킷으로 묶어서 이러한 차이를 완화한다. 패킷은 패킷 크기와 출발지 및 목적지 호스트의 주소를 포함하는 헤더와 데이터 비트를 포함하는 페이로드로 구성된다.
□ 호스트 A에 있는 클라이언트가 LAN1에 있는 호스트 B의 서버로 데이터를 보내는 과정
근데 이 과정에서 여러 문제가 발생하곤 함.
각 인터넷 호스트는 TCP/IP 프로토콜(Transmission Control Protocol/Internet Protocol)을 구현한 소프트웨어를 실행한다.
인터넷 클라이언트와 서버는 소켓 인터페이스 함수와 Unix I/O 함수의 혼합을 사용하여 통신한다.
소켓 함수는 일반적으로 커널 모드의 TCP/IP 함수들을 호출하는 시스템 콜로 구현됨.
이 시스템 콜들은 커널에서 트랩을 발생시킴.
TCP/IP는 사실 프로토콜의 집합으로, 각각 서로 다른 기능을 제공함.
IP 매커니즘 : 기본적인 Naming Scheme과 데이터그램이라고 하는 패킷을 한 인터넷 호스트에서 다른 호스트로 보낼 수 있는 전달 매커니즘 제공. 네트워크에서 데이터그램이 손실되거나 중복되는 경우 복구하려고 노력하지 않으므로 안정적이지 않음
UDP (Unreliable Datagram Protocol) : IP를 살짝 확장해서 호스트 간 전송 뿐만 아니라 프로세스 간의 데이터그램 전송을 가능하게 함
TCP : IP를 기반으로 프로세스 간의 안전한 완전 양방향 연결을 제공하는 복잡한 프로토콜
IPv4와 IPv6
32비트 주소를 이용하는 오리지널 인터넷 프로토콜은 Internet Protocol Version4 (IPv4)임. 1996년에 Internet Engineering Task Force (IETF)는 128비트 주소를 갖는 Internet Protocol Version 6 (IPv6)를 만들어 IPv4를 대체하고자 했지만, 거의 20년이 지난 2015년에도 거의 대부분의 인터넷 트래픽이 IPv4 네트워크로 전송되고 있음. 그래서 책에서도 별로 얘기 안 할거임. 라고 적혀있음.
IP 주소는 부호 없는 32비트 정수임.
(내일 이어서)
import sys
input = sys.stdin.readline
import heapq
N=int(input())
ho=[]
# 좌표 정보 힙에 넣기
for _ in range(N):
a, b = map(int,input().split())
heapq.heappush(ho,(min(a,b),max(a,b)))
L = int(input())
start=[]
max=0
while(ho):
se = heapq.heappop(ho)
s,e=se[0],se[1]
# 처음이거나 start 좌표가 달라졌고, L보다 길이가 작은 선분이라면 추가
if not start or start[-1][0] != s:
if e-s <= L:
start.append([s,0])
# 자신의 종료점부터 L만큼보다 앞에 있는 점들 전까지 cnt+1
for i in range(len(start)-1,-1,-1):
if e-L <= start[i][0]:
start[i][1]+=1
else:
break
m=0
for i in range(len(start)):
if start[i][1] > m:
m=start[i][1]
print(m)
여기까지 풀었는데 시간 초과 뜸
import sys
input = sys.stdin.readline
import heapq
N=int(input())
ho=[]
# 좌표 정보 힙에 넣기
for _ in range(N):
a, b = map(int,input().split())
heapq.heappush(ho,(min(a,b),max(a,b)))
L = int(input())
m=0
cnt=[]
while(ho):
se = heapq.heappop(ho)
s,e=se[0],se[1]
heapq.heappush(cnt,s)
# 자신의 종료점부터 L만큼보다 앞에 있는 점들 빼내기
while(cnt and cnt[0] < e-L):
heapq.heappop(cnt)
m=max(m,len(cnt))
print(m)
답지 보고 수정했는데도 계속 틀려서 봤더니
import sys
import heapq
# 입력값 받기
n = int(sys.stdin.readline().strip())
locations = [] # ([(시작 좌표, 끝 좌표) ...]
for _ in range(n):
h, o = map(int, sys.stdin.readline().strip().split())
# 집과 사무실 중 좌표값이 낮은 것을 앞에, 좌표값이 높은 것을 뒤에 넣어줌
locations.append((min(h, o), max(h, o)))
d = int(sys.stdin.readline().strip())
# 선분의 끝점을 기준으로 오름차순 정렬한 다음, 앞점을 기준으로 오름차순 정렬
locations.sort(key=lambda x: (x[1], x[0]))
heap = []
max_cnt = 0
for location in locations: # 각 사람의 좌표를 기준으로 반복문 돌기
start, end = location
heapq.heappush(heap, start) # 최소 힙. pop했을 때 start가 작은 것부터 나올 수 있도록
line_start = end - d # 현재 사람의 끝 좌표를 기준으로 철로의 시작 지점 계산
while heap and heap[0] < line_start: # heap[0] = 힙에 저장된 시작 지점 중 최소값
heapq.heappop(heap) # 철로의 시작 지점보다 시작 좌표가 작은 경우 철로를 벗어나므로 pop
max_cnt = max(max_cnt, len(heap)) # 현재 철로에 포함되는 사람 수가 최대값인 경우 갱신
print(max_cnt)
답지는 좌표 정보를 힙에 넣는게 아니라 정렬을 하고 있었음. 어째서?
-> 힙으로 할 수야 있을 것 같긴 함. 근데 이 경우엔 끝 점에 대해 최소 힙을 만들어야 하는데 심지어 시작 점의 정보도 보존해야 하므로 힙으로 구현하기 힘들듯.
import sys
input = sys.stdin.readline
import heapq
N=int(input())
ho=[]
# 좌표 정보 넣기
for _ in range(N):
a, b = map(int,input().split())
ho.append((min(a,b),max(a,b)))
# 좌표 정보 정렬하기
ho.sort(key=lambda x: x[1])
L = int(input())
m=0
cnt=[]
for se in ho:
s,e=se
heapq.heappush(cnt,s)
# 자신의 종료점부터 L만큼보다 앞에 있는 점들 빼내기
while(cnt and cnt[0] < e-L):
heapq.heappop(cnt)
m=max(m,len(cnt))
print(m)
답지에서 불필요하게 시작점에 대해 정렬하길래 이상해보여서 뺐는데 잘 작동함.
힙에 넣어서 이전 선분들에서 계산했던 정보들을 재활용할 수도 있을거라는 생각까지는 했는데 구체적으로 구현을 어떻게 해야할지 생각이 닿지 못했다. 조금만 더 잘했으면 혼자 풀 수 있었을 것 같은데 ㄲㅂ. (근데 좌표 정보를 정렬해야 한다는 걸 전혀 모르고 있었어서 힘들긴 했을듯)
import sys
input = sys.stdin.readline
N = int(input())
A = list(map(int,input().split()))
m=0
for i in range(N-1,-1,-1):
n = A[i]
if m < n:
A[i] = -1
m = n
else:
A[i] = m
for i in range(N):
print(A[i],end=' ')
문제 쓱 보고 뭐야? 개쉬운 문제네? 하고 코드 쓱 적었다가
오른쪽에서 자기보다 큰 수 중에서 '가장 왼쪽에 있는' 수를 골라야 한다는 걸 알아버림
import sys
input = sys.stdin.readline
N = int(input())
A = list(map(int,input().split()))
stack=[]
# 오른쪽에서 왼쪽으로 쭉 진행한다
for i in range(N-1,-1,-1):
n = A[i]
# 오큰수랑 비교해서 원래 수열에선 오큰수로 기록해버린 다음
noks = True
for j in range(len(stack)-1,-1,-1):
if stack[j] > n:
noks = False
A[i] = stack[j]
break
if noks:
A[i] = -1
# 스택에서 자기보다 약하거나 같은 놈들 다 죽여버린다
while(len(stack)>1 and stack[-1] <= n):
stack.pop()
# 스택에 냅다 넣는다
stack.append(n)
# 스택 맨 위 peek하면 그게 오큰수
for a in A:
print(a,end=' ')
이거 탑이랑 비슷한 문제네? 하고 로직 똑같이 접근함.
맞긴 했음. 근데 이게 최적화된 로직인진 모르겠음. 백준 채점도 느리게 됨.
그리고 다른 사람들 답을 봤는데...
import sys
N = int(sys.stdin.readline())
A = list(map(int, sys.stdin.readline().split()))
NGE= [-1]*N
stack = [0] # 0번 인덱스
for i in range(1, N):
# 오큰수 : A[i]의 오른쪽에 있으면서 A[i]보다 큰 수 중 가장 왼쪽 값
while stack and A[stack[-1]] < A[i]:
NGE[stack.pop()] = A[i] # 해당 인덱스 칸은 A[i]
stack.append(i)
print(*NGE)
말도 안되게 간결했음.
애초에 스택에서 뺄 이유도 없었던 거임.
다른 사람들보다 메모리 조금 덜 쓰고 연산 살짝 빠른가? 싶었는데
메모리 덜 쓰는 건 맞는데 연산은 비슷한듯.
시간 다른 코드들도 다 똑같다. 그날 백준 서버 상태에 따라 달라지는듯.
어떤 알고리즘으로 풀어야 하는지, 어느 정도 난이도인지 모르고 푸니까,
거기에 아무 생각 없이 빨리 풀어야겠다는 생각까지 드니 이런 일이 생기는 것 같다.
생각 좀만 더 해서 탑의 로직을 쓰지 않아도 된다는 사실을 깨달았으면
30분 걸리는게 아니라 10분, 아니 5분만에 풀었을듯.
-> 다시 보니 아니었다. 아래 참고.
좋은 경험이 되었다.
코드에서 noks 부분이 좀 더러워서 다른 코드는 어떻게 했나 봤더니
NGE= [-1]*N
을 선언해놔서 코드가 깔끔해졌다.
초기화 값을 미리 깔아놓으면 한 쪽 조건만 판별해도 되도록 코드를 간결하게 만들 수 있다.
import sys
input = sys.stdin.readline
N = int(input())
A = list(map(int,input().split()))
stack=[]
# 오른쪽에서 왼쪽으로 쭉 진행한다
for i in range(N-1,-1,-1):
n = A[i]
noks = True
for j in range(len(stack)-1,-1,-1):
if stack[j] > n:
noks = False
A[i] = stack[j]
break
if noks:
A[i] = -1
# 스택에 냅다 넣는다
stack.append(n)
for a in A:
print(a,end=' ')
스택에서 자기보다 낮은 수 지워버리는 과정 없애고 돌렸더니 시간 초과가 뜬다.
뭔가 이상해서 다시 봤더니 단순히 스택에서 값을 안 빼는게 아니라
스택에 인덱스를 넣어놓는다.
지금은 졸려서 이해가 바로 안되니까 내일 다시 와서 봐야겠다.