이제 구글 WebRTC 예제(step 5)에 기능을 추가하려고 한다. 

 

  1. SSL 적용
  2. 서버 기능 보강
  3. 오디오 추가
  4. 디자인 수정
  5. 참가자 수 2명->5명 증가   (이 부분은 프로젝트에서 필요한 기능이라 추가)

1. SSL 적용

NodeJS에 SSL을 적용하기 위해, SSL을 생성한다. 

(사실 온라인 마피아 프로젝트를 원활히 서비스하려면 OpenSSL로 충분한지 잘 모르겠다.. )

 

OpenSSL로 개인키(private.pem) 파일을 생성한다.

openssl genrsa 1024 > private.pem

* 여기서 'unable to write 'random state' 에러가 발생하면 windows의 경우 관리자 권한으로 실행해줘야 한다. 나는 현재 windows powershell를 관리자 권한으로 켜는 코드를 실행했다.

 

Start-Process powershell -Verb runAs를 실행하자 관리자 권한으로 실행된 powershell 창이 떴다

 

개인키와 쌍이 되는 공개키(public.pem) 파일을 생성한다.

openssl req -x509 -new -key private.pem > public.pem

 

.... 근데 여기부터 참고한 티스토리 글과 다르게 흘러가기 시작했다. 

보기만 해도 머리가 아프다 SSL에 대해 잘 모르겠어서 그런가 에러 메세지를 보고 서치해도 뭔소린지 ;-;

 

계속 Private Key가 로드되지 않는다는데 서치해보니 다양한 상황의 각종 원인이 나와서 울고싶어졌다. 나는 OpenSSL로 WebRTC 서비스를 원활하게 하고 싶을 뿐인데 뭘 해야 하는걸까...!

 


* SSL/TLS?

SSL에 대해 개념이 전무한 상태로 코딩을 하려니까 죽겠어서 보안기사 책을 펼쳐봤더니 마침 SSL/TLS 내용이 있어서 정리해본다.

 

통신내용을 암호화해주는 프로토콜로 SSL 혹은 TLS를 이용한다. SSL/TLS 상에 HTTP를 올린다. 프로토콜의 이중 구조를 통해 HTTP의 통신은 암호화되어 도청을 방지할 수 있다. SSL/TLS로 통신을 수행할 때의 URL은 http://가 아니고 https://로 시작된다. 

SSL/TLS 보안 서비스
1. 기밀성 서비스 : DES, RC4와 같은 대칭키 암호화 알고리즘을 사용하여 제공되며, 이때 사용되는 비밀키는 Handshake Protocol을 통해 생성된다.
2. 클라이언트와 서버 상호 인증 : 연결 설정 과정에서 서로 간에 신뢰할 수 있도록 인증을 사용하는데, 인증에는 RSA와 같은 비대칭키 암호 알고리즘, DSS와 같은 전자서명 알고리즘와 X.509 공개키 인증서가 사용된다.
3. 메시지 무결성 서비스 : 안전한 해시 알고리즘을 사용해서 메시지 인증코드를 만들어 메시지에 포함시키기 때문에 신뢰성 있는 통신이 가능하다.

 

그니까 저 예제에서 말하는 거는 X.509 공개키 인증서를 쓰는거니까 SSL/TLP 서비스 중 2. 클라이언트와 서버 상호 인증 서비스를 사용하는 거겠지? (확실하지 않다)


7시간 안에 해결해야되는 프로젝트 과제가 있어서 우선 OpenSSL 삽질은 접어두고 aws s3에 정적 웹페이지부터 올려봐야겠다. ec2로 동적 웹페이지 호스팅은 어떻게 하는지 아직도 감이 잘 안 잡힌다.

'개발 프로젝트 > 온라인 마피아' 카테고리의 다른 글

WebRTC - https, SSL (4)  (0) 2020.10.02
WebRTC - AWS 호스팅하기 (3)  (0) 2020.10.02
WebRTC - 기초부터 차근차근 (1)  (0) 2020.10.02

WebRTC의 연결 설정, 통신, 데이터 전송은 JS API를 통해 구현된다. 기본 API는 다음과 같다.

 

WebRTC JS API

- RTCPeerConnection : 피어 투 피어 연결 생성, 탐색

- RTCSessionDescription : 연결의 한쪽 끝, 구성 방법 설명

- navigator.getUserMedia : 오디오/비디오 캡쳐

 

현재 구글 WebRTC 예제 중 네트워크 상에서 비디오를 주고 받는 step 5 예제를 변형할 예정이다.

 

카메라에서 비디오를 추출하고 (getUserMedia - step 1)

대화 상대를 찾고 (signaling - step 4)

네트워크로 연결하여 (RTCPeerConnection - step 2)

비디오를 주고 받는 코드가 step 5이다.

 

구글 예제는 로컬 환경에서 구동되므로 SSL이 따로 필요하지 않았지만, 일반적인 서비스를 만드는 경우 WebRTC는 기본적으로 네트워크 연결시 SSL이 있어야한다. (데이터를 주고 받고, 화상 채팅을 하려면)

또한 서버가 한번만 연결하도록 작성되어 계속 서버를 재가동해야하는 불편함이 있다.

오디오 또한 제공되지 않는다! 

 

* step 5 폴더에 있는 주요 파일은 다음과 같다.

  • index.js - NodeJS로 실행한 웹 서버 및 signaling
  • index.html - 사용자가 보는 웹페이지 (화상채팅 화면)
    • js/main.js - RTCPeerConnection 등 실제 화상채팅을 하는 클라이언트 코드가 모두 여기 있음
    • css/main.css - index.html의 UI 정의

 

현재 나는 

1. WebRTC로 여러명의 비디오를 받아와 동시에 띄우며

2. 해당 웹사이트는 가상서버에서 호스팅을 해야한다. (즉, SSL 필수)

-> 보통 WebRTC는 로컬환경이 아닌 경우 보안 이슈가 있어 SSL 설정이 번거로운 것으로 알고 있다... 열심히 해쳐나가는 과정을 쭉 기록해놓고자 한다.

 

 

 

 

 

* 이 분의 블로그를 많이 참조했다. 보다 정확하고 세부적인 정보를 위해서는 이쪽으로 (forest71.tistory.com/211)

'개발 프로젝트 > 온라인 마피아' 카테고리의 다른 글

WebRTC - https, SSL (4)  (0) 2020.10.02
WebRTC - AWS 호스팅하기 (3)  (0) 2020.10.02
WebRTC - OpenSSL 삽질 (2)  (0) 2020.10.02

문제

방향 없는 그래프가 주어졌을 때, 연결 요소 (Connected Component)의 개수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 정점의 개수 N과 간선의 개수 M이 주어진다. (1 ≤ N ≤ 1,000, 0 ≤ M ≤ N×(N-1)/2) 둘째 줄부터 M개의 줄에 간선의 양 끝점 u와 v가 주어진다. (1 ≤ u, v ≤ N, u ≠ v) 같은 간선은 한 번만 주어진다.

출력

첫째 줄에 연결 요소의 개수를 출력한다.

예제 입력 1

6 5 
1 2 
2 5 
5 1 
3 4 
4 6

예제 출력 1

2

 


 

DFS를 이용한 풀이 (python)

#백준 11724 DFS
import sys
sys.setrecursionlimit(10000)

def dfs(start):
    if(visited[start]):
        return
    visited[start] = True
    for node in adj[start]:
        if not visited[node]:
            dfs(node)


N,M=map(int,input().split(' '))
adj = [ [] for _ in range(N+1)]
visited = [False] * (N+1)
cnt = 0

for _ in range(M):
    x, y = map(int, input().split(' '))
    adj[x].append(y)
    adj[y].append(x)


for node in range(1, N+1):
    if not visited[node]:
        dfs(node)
        cnt +=1


print(cnt)

 


 

BFS를 이용한 풀이 (python)

 

#백준 11724 BFS
from collections import deque

def bfs(start):
    visited[start] = True
    queue.append(start)
    while queue:
        x=queue.popleft()
        for i in range(len(adj[x])):
            y=adj[x][i]
            if not visited[y]:
                visited[y] = True
                queue.append(y)


N,M=map(int,input().split(' '))
adj = [ [] for _ in range(N+1)]
visited = [False] * (N+1)
queue = deque()
cnt = 0

for _ in range(M):
    x, y = map(int, input().split(' '))
    adj[x].append(y)
    adj[y].append(x)


for node in range(1,N+1):
    if not visited[node]:
        bfs(node)
        cnt+=1

print(cnt)

 

대부분 DFS를 통해 구현하던데, 그 이유는 알겠다. BFS로 구현하는게 더 귀찮더라...ㅎㅎ

DFS(Depth First Search, 깊이 우선 탐색)은 그래프를 방문하거나 탐색하는 방법 중 하나이다. DFS는 주로 완전 탐색이나 백트래킹과 같이 탐색의 횟수, 즉 그래프의 최대 경로가 정해져 있거나 예측 가능한 경우에 이용한다. 

 

DFS는 선택한 정점을 저장하기 위한 도구로 큐 대신 스택을 이용한다는 점에서 BFS와 차이를 보인다. 다만 스택을 직접 사용하지 않고, 그 원리를 이용하는 재귀 함수를 통해 구현하는 편이다.

 

def dfs(start):
	if(visited[start]): 
    	return
    visited[start] = True
    for node in adj[start]:
        if not visited[node]:
            dfs(node)


N,M=map(int,input().split(' '))
adj = [ [] for _ in range(N+1)]
visited = [False] * (N+1)
start_node = input('시작 노드를 입력하세요 : ')

for _ in range(M):
    x, y = map(int, input().split(' '))
    adj[x].append(y)
    adj[y].append(x)


dfs(start_node)

다음 코드는 백준 11724번 풀이에서 가져와 약간 변형한 코드이다. 다만 보통 재귀 함수를 사용하는 경우 런타임 에러가 나는 경우가 잦아 다음의 코드를 통해 그 횟수를 직접 지정해주는 경우가 많다. 

 

import sys
sys.setrecursionlimit(10000)

BFS(Breadth First Search, 너비우선 탐색)은 그래프를 방문하거나 탐색하는 방법 중 하나이다. BFS를 이용하여 최단거리, 최소비용과 같이 최솟값과 관련된 문제를 해결할 수 있는데, 이때 그래프의 가중치(시간, 비용, 거리 등)가 1이어야만 한다. DFS는 그래프의 최대 경로(최대 깊이)가 예측할 수 있는 유한한 범위여야만 사용할 수 있지만, BFS는 모든 경로에 대한 동시 탐색이 가능하며 최대 경로를 몰라도 사용할 수 있다. 

 

from collections import deque

def bfs(start):
    visited[start] = True
    queue.append(start)
    while queue:
        x=queue.popleft()
        for i in range(len(adj[x])):
            y=adj[x][i]
            if not visited[y]:
                visited[y] = True
                queue.append(y)

#N:정점개수 M:간선개수
N,M=map(int,input().split(' '))
adj = [ [] for _ in range(N+1)]
visited = [False] * (N+1)
queue = deque()
start_node = input('검색을 시작할 노드를 입력하세요 : ')

for _ in range(M):
    x, y = map(int, input().split(' '))
    adj[x].append(y)
    adj[y].append(x)

bfs(start_node)

백준 11724번 코드 중 일부를 가져와 수정했다. 해당 코드는 정점, 간선의 개수를 입력받고 사용자에게 직접 어떤 정점이 연결되어 있는지 입력받는다.

 

BFS의 경우 를 사용하는 경우가 대부분이다. 방문했던 노드를 체크해야 살펴볼 노드를 큐에 넣을 때 중복이 발생하지 않기 때문이다.

 

python에서는 큐를 list 타입으로 사용하는 경우가 많은데, 살펴본 노드를 제거할 때, list.pop() 함수를 사용하는 경우 시간 복잡도가 O(N)이라 시간 복잡도 면으로 매우 비효율적이다.

 

따라서 collections 라이브러리의 deque을 사용하여 시간을 절약하는 편이 좋다. deque는 양 끝에서 접근이 가능하며, append(), extend(), pop(), popleft() 등의 함수를 통해 자료를 넣고 뺄 수 있다. 

동적 계획법(DP)은 어렵거나 큰 문제를 간단하고 작은 여러개의 문제로 나누어 풀고 작은 문제의 답들을 이용하여 원래 문제의 답을 구하는 방식이다.

 

다음의 특징을 갖고 있다,

1. 최적 부분 구조
: 문제의 정답이 작은 문제에 대해서도 정답이어야 한다. 

2. 부분 문제 반복
: 문제를 여러 개의 작은 문제로 나눌 수 있으며, 나눈 작은 문제들을 전체 문제를 푸는 방법과 같은 방법으로 풀 수 있어야 한다.

 

DP 문제는 하향식, 상향식 두 가지 방법으로 접근할 수 있다.

 

하향식 방법의 경우 큰 문제를 풀 수 있는 작은 문제가 될 때까지 나눈 후, 작은 문제들을 풀어 얻은 정답들을 합쳐가며 큰 문제의 답을 구하는 방식으로, 주로 재귀 함수를 이용하여 구현한다.

 

상향식 방법은 가장 작은 문제부터 시작하여 큰 문제를 풀 수 있을 때까지 차례대로 문제들을 풀어나가는 방식으로 주로 반복문을 이용해 구현한다. 

'Algorithm > 개념 설명' 카테고리의 다른 글

파이썬 소수 판별 알고리즘  (0) 2022.07.02
파이썬 진법 변환  (0) 2022.07.01
DFS(깊이 우선 탐색)  (0) 2020.07.31
BFS(너비 우선 탐색)  (0) 2020.07.31
정렬 알고리즘(sorting algorithm)이란?  (0) 2020.01.16

2020년은 우선 사기업에 올인하기로 하고 공기업은 접었다! 어느 세월에 정보처리기사, 정보보안기사, 컴퓨터일반을 다 훑나 싶어서... ㅠㅠ

 

상반기동안 코로나 핑계로 시간을 많이 날려먹었다. 우선 2021년도 ^^ 취준중일 수 있기 때문에 정보보안기사를 이번 하반기에 준비하기로 했다.

 

8.3 정보보안기사 접수 & 공부 시작해서 합격자 수기 보니까 필기 이론 기준 하루 3~4시간 투자해서 2주 이론, 1주 기출 돌리면 붙는다고 했으니까 이렇게 한달 해볼까 한다! (과연 나도 이렇게 해서 붙을지는 의문이다)

 

8월 한달간

  • 학과 홈페이지의 연장선인 강좌자료실 홈페이지 다듬기 -> 현재 정적 웹페이지라서 아쉬운데, 동적 웹페이지로 굳이 바꿀 필요를 못 느끼겠다. 로그인 기능이 있는 것도 아니고 굳이..? 남들 보여주기에 아쉬워서 바꿀까 싶긴 하다만
  • 코딩테스트 준비 ( 언어 : python )
  • openCV 기반 프로젝트 진행
  • 정보보안기사 준비 ( 오전시간 투자 )

와우 목표한거의 몇퍼센트를 달성할지 모르겠지만 우선 기록은 해놔야겠다! 깃헙도 파서 얼른 해야되는데 끙끙 할일은 많은데 시간은 빨리간다

'취뽀하자! > 계획, 진행상황' 카테고리의 다른 글

뭐하고 살았나?  (0) 2022.06.27

문제

여러분도 알다시피 여러분의 프린터 기기는 여러분이 인쇄하고자 하는 문서를 인쇄 명령을 받은 ‘순서대로’, 즉 먼저 요청된 것을 먼저 인쇄한다. 여러 개의 문서가 쌓인다면 Queue 자료구조에 쌓여서 FIFO - First In First Out - 에 따라 인쇄가 되게 된다. 하지만 상근이는 새로운 프린터기 내부 소프트웨어를 개발하였는데, 이 프린터기는 다음과 같은 조건에 따라 인쇄를 하게 된다.

  1. 현재 Queue의 가장 앞에 있는 문서의 ‘중요도’를 확인한다.
  2. 나머지 문서들 중 현재 문서보다 중요도가 높은 문서가 하나라도 있다면, 이 문서를 인쇄하지 않고 Queue의 가장 뒤에 재배치 한다. 그렇지 않다면 바로 인쇄를 한다.

예를 들어 Queue에 4개의 문서(A B C D)가 있고, 중요도가 2 1 4 3 라면 C를 인쇄하고, 다음으로 D를 인쇄하고 A, B를 인쇄하게 된다.

여러분이 할 일은, 현재 Queue에 있는 문서의 수와 중요도가 주어졌을 때, 어떤 한 문서가 몇 번째로 인쇄되는지 알아내는 것이다. 예를 들어 위의 예에서 C문서는 1번째로, A문서는 3번째로 인쇄되게 된다.

입력

첫 줄에 test case의 수가 주어진다. 각 test case에 대해서 문서의 수 N(100이하)와 몇 번째로 인쇄되었는지 궁금한 문서가 현재 Queue의 어떤 위치에 있는지를 알려주는 M(0이상 N미만)이 주어진다. 다음줄에 N개 문서의 중요도가 주어지는데, 중요도는 1 이상 9 이하이다. 중요도가 같은 문서가 여러 개 있을 수도 있다. 위의 예는 N=4, M=0(A문서가 궁금하다면), 중요도는 2 1 4 3이 된다.

출력

각 test case에 대해 문서가 몇 번째로 인쇄되는지 출력한다.

예제 입력 1

3
1 0
5
4 2
1 2 3 4
6 0
1 1 9 1 1 1

예제 출력 1

1
2
5

 


 

무작정 우선순위 큐를 쓰려고 해서 살짝 헤맸던 문제였다. 우선순위 큐는 값을 넣자마자 알아서 정렬이 되어버려서 문제 내의 '중요도가 맞지 않는 문서의 경우 큐의 맨 뒤로 이동한다'는 조건에 부합하지 않았다. 그렇다고 큐만 써서 문제를 풀자니 가장 우선순위가 높은 문서를 매번 체크해주기 귀찮아서 우선순위 큐, 일반적인 큐 두 가지를 모두 사용하여 문제를 풀었다. 

 

queue는 입력받은 문서가 원래 몇번째 자리에 있었는지 위치값을 함께 저장해주기 위해 pair<int,int>를 사용하였다. first 값이 value(중요도), second값이 pos(위치)이다.

 

함수로는 전체 입력을 받는 input()함수, 우선순위를 고려하여 큐에서 pop하고 최종 인쇄 순서를 반환하는 solve() 함수, queue와 prioirty queue를 초기화해주는 clear() 함수 세 개를 정의하였다. 자세한 설명은 코드에 주석으로 달아놓았다. 

 

* 큐를 초기화해줄때 일반적으로 전체를 순회하며 pop()을 해줘 초기화를 하는 경우가 많은데, 스택 오버플로우에 좀 더 나은 초기화 방법이 있어 clear() 함수는 해당 방법을 사용하였다. 메모리 해제도 해주고 초기화도 해주는 좋은 방법인 것 같다. 

 

 

#include <iostream>
#include <queue>
#include <algorithm>

using namespace std;

int TC, N, M, cnt;
queue <pair <int, int>> q;
priority_queue <int> pq;
queue <int> ans;

void input() {
	int val = 0;
	for (int i = 0; i < N; i++) {
		cin >> val;
		q.push(make_pair(val, i));
		pq.push(val);
	}
}

int solve() {
	cnt = 0;
	int val, pos = 0;
	while(!pq.empty()) {
		// 맨 앞 원소가 우선순위인 경우
		if (pq.top() == q.front().first) {
			// 뽑을 원소가 M번째 원소인 경우
			if (q.front().second == M) {
				cnt++;
				return cnt;
			}
			// 뽑아줌
			pq.pop(); q.pop();
			cnt++;
		}
		// 맨 앞 원소가 우선순위가 아닌경우
		else {
			val = q.front().first;
			pos = q.front().second;
			q.pop(); q.push(make_pair(val, pos));
		}
	}
	return cnt;
}

// q, pq 초기화
void clearQ(queue<pair<int, int>>& q) {
	queue<pair<int, int>> empty;
	swap(q, empty);
}
void clearPQ(priority_queue<int>& pq) {
	priority_queue<int> empty;
	swap(pq, empty);
}
void clear() {
	clearQ(q);
	clearPQ(pq);
}

int main() {
	cin >> TC;

	for (int i = 0; i < TC; i++) {
		cin >> N >> M;
		input();
		ans.push( solve() );
		clear();
	}

	while (!ans.empty()) {
		cout << ans.front() << endl;
		ans.pop();
	}

	return 0;
}

 

 


 

무사히 통과했다

 

 

사실 내 코드가 좋은 코드라고는 말하기 어렵다. 애초에 너무 알고리즘 공부를 안했다ㅠㅠ 기록용으로 남기는 코드이니 참고만 하길 바란다.

+ Recent posts