<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>heesoo.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 10 Mar 2022 13:48:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. heesoo.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/gml_01" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[오직 공부에만 집중할 수 있는 캠스터디 - 1]]></title>
            <link>https://velog.io/@gml_01/aws</link>
            <guid>https://velog.io/@gml_01/aws</guid>
            <pubDate>Thu, 10 Mar 2022 13:48:29 GMT</pubDate>
            <description><![CDATA[<h3 id="딥러닝-이미지-인식-기반-공부-시간-자동-측정-캠스터디-플랫폼">딥러닝 이미지 인식 기반 공부 시간 자동 측정 캠스터디 플랫폼</h3>
<h4 id="오직-공부에만-집중할-수-있는-캠스터디">오직 공부에만 집중할 수 있는 캠스터디</h4>
<hr>
<p>졸업 프로젝트 주제와 부제는 위와 같다.</p>
<p>본 프로젝트에서 프론트 개발, 웹 클라이언트 서버 배포를 담당하면서 해결했던 것들을 정리해보고자 한다.</p>
<h4 id="1-배포-후-로그인-안되는-세션-문제-해결">1. 배포 후 로그인 안되는 세션 문제 해결</h4>
<p>STUDY-DO 프로젝트를 배포하려고
domain 하나에는 s3으로 react를 올리고,
다른 domain 하나에는 ec2로 express를 올렸다.</p>
<p>그런데 문제가 생겼다.
우리는 &#39;참여한 스터디&#39; 페이지를 볼 때 서버에서 session으로 사용자의 아이디를 확인해 클라이언트로 데이터를 보내줬다.
여기서 사용자의 session(req.session.user) 확인이 안되었다.</p>
<p>그래서 구글링했는데 인프런에 어떤 분이 올린 <a href="https://www.inflearn.com/questions/342672">질문</a>이 우리 문제랑 비슷하다.</p>
<p>프론트랑 백엔드가 도메인이 다른 경우에는 쿠키 설정이 현실적으로 어렵다고 한다.....
나는 프론트를 서버와 다른 도메인에 배포했기 때문에 배포를 새로 하려고 한다...!</p>
<p>ec2에 nodejs 서버를 올리는데 502 bad gateway도 나고, target group이 unhealthy하다는 문제도 계속 있었다.
한 세번 계속 ec2 새로 인스턴스 생성했다가 지우고 하다가 왜 이런 에러들이 났는지 알아냈다...
다음에 배포할 때 또 이런 일이 일어날 수 있기 때문에 나중에 참고하고자 정리하려고 한다.</p>
<h2 id="ec2에-nodejs-서버-배포하기">ec2에 nodejs 서버 배포하기</h2>
<h3 id="1-인스턴스-생성">1. 인스턴스 생성</h3>
<p>처음은 인스턴스 생성을 해야한다. </p>
<p><img src="https://images.velog.io/images/gml_01/post/d6b29bc4-d860-4ae5-9623-ee6b18195655/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/gml_01/post/5e87f693-b359-48f8-bb64-881a99884296/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/gml_01/post/655bf6e8-c5b1-4fca-bc01-d02892dd4f68/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/gml_01/post/b1beeb1e-f6b6-4a44-a668-4bfdd52e54f4/image.png" alt=""></p>
<p>보안그룹은 포트 22, 80, 8000을 지정해주자
로컬에서 돌릴 때 8000 포트로 해서 그대로 8000해줬다. </p>
<p><img src="https://images.velog.io/images/gml_01/post/94b78f02-a887-4515-981c-d0ed1675389e/image.png" alt=""></p>
<p>시작하기 누르면 키 페어를 새로 생성해서 잘 저장해두자
나중에 서버 접속할 때 필요하다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/94417548-ae62-44ea-a7f8-29980a63608b/image.png" alt=""></p>
<p>그럼 이렇게 일단 인스턴스는 생성했다.</p>
<h3 id="2-탄력적-ip-할당">2. 탄력적 IP 할당</h3>
<p>도메인이 인스턴스를 가리키도록 도메인에 대한 DNS 레코드에 탄력적 IP주소를 지정해야한다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/8330dc09-4610-48d3-a5ea-457289d39bc3/image.png" alt=""></p>
<p>탄력적 IP 주소 할당 버튼 누른다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/ae542a35-7506-46ad-b5a1-6fe9cdaa1b30/image.png" alt=""></p>
<p>설정은 이런식으로 해서 IP주소를 할당 받으면,</p>
<p><img src="https://images.velog.io/images/gml_01/post/52f5da5d-8733-4330-b34d-aa1170407324/image.png" alt=""></p>
<p>이렇게 고정 IP 주소가 생긴다.</p>
<h3 id="3-인스턴스에-탄력적-ip-연결">3. 인스턴스에 탄력적 IP 연결</h3>
<p>탄력적 IP 만들었으니까 인스턴스에 연결하자!
<img src="https://images.velog.io/images/gml_01/post/f949f627-b7d4-4465-b85a-9a82435bdf69/image.png" alt=""></p>
<p>탄력적 IP 주소 연결 누른다</p>
<p><img src="https://images.velog.io/images/gml_01/post/f917871b-11dc-41a0-b50d-e952fd858a70/image.png" alt=""></p>
<p>연결하고 싶은 인스턴스가 보인다. 그대로 연결해주자
그럼 인스턴스 요약에서 퍼블릭 IPv4주소로 탄력적 IP주소가 연결된 것을 확인할 수 있다.</p>
<h3 id="4-pem키로-ubuntu에-접속하기">4. pem키로 ubuntu에 접속하기</h3>
<p>1번에서 새로 생성한 키페어 지금 필요하다.
윈도우 쓰니까 git bash 열어줬다.
해당 키페어가 있는 곳으로 cd하자.
그 다음에 그 루트에서</p>
<pre><code>ssh -i &quot;키페어 이름(확장자까지)&quot; ubuntu@(탄력적IP주소)</code></pre><p>이거 치고 엔터하면
<img src="https://images.velog.io/images/gml_01/post/41e879ec-f645-4988-9854-274bd8fe7746/image.png" alt=""></p>
<p>이런 식으로 우분투에 접속할 수 있다.</p>
<p>여기에 nodejs 서버를 올릴 것이기 때문에, nodejs 관련 모듈을 install하면 된다.</p>
<pre><code>sudo apt update 
sudo apt install npm 
sudo apt install nodejs
sudo apt install git</code></pre><p>이렇게 하면 아래 코드로 잘 설치가 되었는지 확인하자</p>
<pre><code>node -v
npm -v
git --version</code></pre><p><img src="https://images.velog.io/images/gml_01/post/e5d409c0-da94-4039-b45c-0c0b69c4a2af/image.png" alt=""></p>
<h3 id="5-git-clone">5. git clone</h3>
<p>이제 배포할 코드를 클론하자</p>
<pre><code>git config --global user.name (아이디)
git config --global user.email (이메일)
git clone https://github.com/CSE-Final-Project/backend.git</code></pre><p>이렇게 하면</p>
<p><img src="https://images.velog.io/images/gml_01/post/69cb4a29-e348-4e92-bb65-b0191a5dd5fc/image.png" alt=""></p>
<p>깃 클론 된거 확인할 수 있다.</p>
<p>이제 프로젝트 관련 모듈 설치하려고 npm i 했는데 node 구버전이라서 안됐다.
node 더 높은 버전으로 업데이트하자.</p>
<pre><code>curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt install nodejs
sudo apt install build-essential</code></pre><p>이렇게 하면</p>
<p><img src="https://images.velog.io/images/gml_01/post/7db2dcfd-09c6-4c59-8bd4-c8aa1ef06e3d/image.png" alt=""></p>
<p>아까 v8.10.0에서 v14.19.0으로 업데이트 됐다.</p>
<p>다시 npm i 해보자
<img src="https://images.velog.io/images/gml_01/post/2e9897ce-8549-4188-a291-ca303a24947e/image.png" alt=""></p>
<p>된다!
<br/>
<br/></p>
<p>이제 코드도 올려봤으니 npm run start해서 서버가 잘 돌아가는지 확인하자
<img src="https://images.velog.io/images/gml_01/post/ea1f9f96-3508-4913-832e-60555631a1b9/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/gml_01/post/8e35d090-8d36-4be1-80f1-81338edf9d88/image.png" alt=""></p>
<p>http://탄력적IP주소:8000/ 에서 되는걸 확인할 수 있다.</p>
<h3 id="6-인바운드-규칙-https-443-추가">6. 인바운드 규칙 https 443 추가</h3>
<p>http를 https로 전환하기 위해서는 인바운드 규칙을 추가해줘야 한다.
인바운드 규칙 추가는 인스턴스에서 할 수 있다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/a0e1fa24-1151-4786-bdc8-285c1b820393/image.png" alt=""></p>
<p>HTTPS 443 포트를 추가해줬다.</p>
<h3 id="7-80번-포트---8000번-포트로-redirect하기">7. 80번 포트 -&gt; 8000번 포트로 redirect하기</h3>
<p>지금은 http://탄력적IP주소:8000 주소에 들어갈 수 있다.
이제 8000 포트를 뒤에 붙여주지 않고 http://탄력적IP주소 이렇게만 해도 들어갈 수 있도록 만들어주자</p>
<pre><code>sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8000</code></pre><p>그러면
<img src="https://images.velog.io/images/gml_01/post/0e8c6782-447c-4007-a3bd-7cea4dc451da/image.png" alt=""></p>
<p>이렇게 http://탄력적IP주소 만 쳐도 들어가는 걸 확인할 수 있다</p>
<h3 id="8-로드밸런서-생성">8. 로드밸런서 생성</h3>
<p><img src="https://images.velog.io/images/gml_01/post/f18811e3-5af3-4798-9d1f-a2c562be0000/image.png" alt=""></p>
<p>로드 밸런서 생성 버튼을 누른다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/c0129dbc-0775-4696-be34-7be968f00659/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/gml_01/post/2ee04c4e-c2e3-41eb-b8ad-72d7816989ef/image.png" alt=""></p>
<p>Listeners and routing에서 HTTPS 설정을 해줘야 한다.
443 포트의 HTTPS 프로토콜을 추가해주자.</p>
<p>옆에 target group도 설정해줘야 한다.
Create target group을 누르자.</p>
<h3 id="9-create-target-group">9. Create target group</h3>
<p><img src="https://images.velog.io/images/gml_01/post/8bb57e88-e03a-4fd6-a4be-535edf0b6327/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/gml_01/post/895ac85b-b8f0-48d2-a62a-1c0133436788/image.png" alt=""></p>
<p>이름 설정해주고 HTTP 프로토콜의 80번 포트를 지정해주자. 
(지금 http://탄력적IP주소 로 들어가고 있으니까)</p>
<p><img src="https://images.velog.io/images/gml_01/post/fc711cc8-3094-4fee-b050-371861886446/image.png" alt=""></p>
<p>Health checks의 path 부분은 &#39;/&#39; 루트로 지정해줬다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/416e2681-a11b-4488-872f-e620268ffbbe/image.png" alt=""></p>
<p>Next 누르면</p>
<p><img src="https://images.velog.io/images/gml_01/post/54782086-2915-4651-ac3e-0eadc373e65c/image.png" alt=""></p>
<p>인스턴스를 연결할 수 있다.</p>
<h3 id="10-acm-ssl-인증서-만들기">10. ACM SSL 인증서 만들기</h3>
<p>https로 리다이렉트 해야하니까 SSL 인증서도 필요하다.
<img src="https://images.velog.io/images/gml_01/post/5d728b14-1cfa-4097-a6c2-fa7c05be03f8/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/gml_01/post/6684560f-47ed-4d3f-9b73-08eeaaacc641/image.png" alt=""></p>
<p>도메인 이름에는 이 nodejs 서버를 올릴 도메인의 이름을 적어주면 된다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/2510e84f-b61b-418c-9d26-7bbbde71b645/image.png" alt=""></p>
<p>발급받는데 시간이 조금 걸린다.
이때 도메인 부분의 &#39;Route 53에서 레코드 생성&#39;을 눌러준다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/941e1873-26a0-4b84-86e5-58904df13140/image.png" alt=""></p>
<p>레코드 생성을 해주자</p>
<p>9번이랑 10번에서 만든 대상그룹, ACM SSL를 넣어서 로드밸런서를 만들면 된다.
<img src="https://images.velog.io/images/gml_01/post/3d29c65c-fab5-456e-8028-88e1b9e57e9d/image.png" alt=""></p>
<h3 id="11-route53에-로드밸런서-연결">11. route53에 로드밸런서 연결</h3>
<p><img src="https://images.velog.io/images/gml_01/post/125256bc-3bd1-4cc9-9c5f-936e98b843f5/image.png" alt=""></p>
<p>이런식으로 nudo-study.cf에 레코드를 편집해줬다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/816a76d3-ea99-4ffc-9129-90f5ee3e7a54/image.png" alt=""></p>
<p><a href="https://nudo-study.cf">https://nudo-study.cf</a> 로 접속이 가능하다!</p>
<h3 id="에러-이유">에러 이유</h3>
<p>ec2에 nodejs를 배포하면서 <strong>502 bad gateway</strong> 에러가 진짜 많이 났다.
내가 배포해보면서 위 같은 에러가 난 이유는 ** target group이 unhealthy해서**였다.</p>
<p>그럼 target group을 healthy하게 바꾸려면
health check path인 &#39;/&#39;에 200 응답을 줄 수 있도록 해야한다.</p>
<p>&#39;/&#39;에 200 응답을 주려면 로드밸런서에 연결하기 전에
올린 서버가 &#39;/&#39; path에서 잘 작동되는지 확인해야한다.
그러려면 <strong>7번의 80번 포트 -&gt; 8000번 포트로 redirect하기</strong>를 한 다음에,
&#39;/&#39; path에서 서버가 잘 켜지는지 확인이 꼭 필요하다.</p>
<hr>
<p>프론트와 백을 같은 도메인에 배포를 완료했으면,
프론트와 백에 withCredentials 옵션을 추가해주자</p>
<p>[이유]
프론트에서 백으로 CORS 요청을 할 때 기본적으로 쿠키를 설정하거나 보내지 않았다.
그래서 프론트에서 백으로 CORS 요청을 할 때 withCredentials 옵션을 추가하여 CORS 요청에 쿠키값을 추가해야 한다.</p>
<p><strong>nodejs 서버</strong></p>
<pre><code>app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}));
app.use(function(req, res, next){
    res.header(&#39;Access-Control-Allow-Origin&#39;, &#39;https://client.nudo-study.cf&#39;);
    res.header(&#39;Access-Control-Allow-Methods&#39;, &#39;GET, POST, PUT, DELETE,PATCH&#39;);
    res.header(&#39;Access-Control-Allow-Headers&#39;, &#39;content-type&#39;);
    res.header(&#39;Access-Control-Allow-Credentials&#39;,&#39;true&#39;);
    next();
})
</code></pre><pre><code>app.use(cors({
    origin : process.env.CLIENT_ORIGIN | &quot;(클라이언트 주소)&quot;,
    credentials : true,
    methods: [&quot;GET&quot;,&quot;POST&quot;,&quot;PATCH&quot;,&quot;DELETE&quot;,&quot;OPTIONS&quot;]
}))</code></pre><p><strong>react 클라이언트</strong></p>
<pre><code>import axios from &#39;axios&#39;

const client = axios.create({
    withCredentials: true
})

export default client;</code></pre><p>아니면</p>
<pre><code>axios.defaults.withCredentials = true;</code></pre><p>위 withCredentials 옵션 설정이 끝나면 CORS 요청을 할 때 쿠키 값도 같이 넣어서 프론트에서 백으로 보내주기 때문에 로그인이 가능해졌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[makesense로 라벨링하기]]></title>
            <link>https://velog.io/@gml_01/makesense%EB%A1%9C-%EB%9D%BC%EB%B2%A8%EB%A7%81%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gml_01/makesense%EB%A1%9C-%EB%9D%BC%EB%B2%A8%EB%A7%81%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 31 Dec 2021 07:19:38 GMT</pubDate>
            <description><![CDATA[<p>캠스터디 영상에서 추출한 이미지에서
&#39;펜 잡은 손&#39;을 라벨링 해보자</p>
<p><a href="https://www.makesense.ai/">MakeSense</a>
위 링크에서 object detection을 위한 라벨링을 진행했다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/1f3e92ec-95be-4f2d-82a5-d3f919532753/image.png" alt="">
Get Started 눌러준다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/ae563284-8e36-4d65-9241-066208fe7be4/image.png" alt="">
해당 알고리즘을 선택한다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/9f98b839-ea5b-49ff-89a9-d5f9568e9f44/image.png" alt="">
라벨 이름을 설정한다.
hand로 설정했다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/18e6c733-5a5d-4757-b64e-74737bec99cb/image.png" alt="">
이렇게 하나하나 다 rect annotation을 만든다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/48cde4e5-ba2d-401c-be0e-09b7279ebc32/image.png" alt="">
라벨링이 되면 상단바의 Actions &gt; Export rect annotations에서 해당 라벨링 파일을 받는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ffmpeg로 동영상에서 프레임 추출하기]]></title>
            <link>https://velog.io/@gml_01/ffmpeg%EB%A1%9C-%EB%8F%99%EC%98%81%EC%83%81%EC%97%90%EC%84%9C-%ED%94%84%EB%A0%88%EC%9E%84-%EC%B6%94%EC%B6%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gml_01/ffmpeg%EB%A1%9C-%EB%8F%99%EC%98%81%EC%83%81%EC%97%90%EC%84%9C-%ED%94%84%EB%A0%88%EC%9E%84-%EC%B6%94%EC%B6%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 31 Dec 2021 06:34:03 GMT</pubDate>
            <description><![CDATA[<p>캠스터디 영상에서 펜 잡은 손을 인식하기 위해
펜 잡은 손 데이터셋이 필요했다.</p>
<p>ffmpeg을 이용해서 캠스터디 동영상의 프레임을 추출했다.</p>
<p><a href="https://zzangwoo.tistory.com/entry/FFMPEG-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95-%EB%B0%8F-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%A0%95%EB%A6%AC-%EB%8F%99%EC%98%81%EC%83%81-%EC%9E%90%EB%A5%B4%EA%B8%B0-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B3%80%ED%99%98-%ED%99%95%EC%9E%A5%EC%9E%90-%EB%B3%80%ED%99%98">FFMPEG 설치방법 및 명령어 정리 - 동영상 자르기, 이미지 변환, 확장자 변환</a></p>
<p>위 블로그를 참고해서 ffmpeg를 설치했다.</p>
<p>bin 폴더에 들어가서 cmd 창을 열어 아래 명령어를 입력했다.</p>
<pre><code>ffmpeg -ss [동영상_시작시간] -i [인풋_동영상_경로] -r [초당_추출할_장수] -f image2 [이미지_이름]</code></pre><p><strong>동영상 경로</strong>와 <strong>이미지 이름</strong>에는 끝에 .mp4, .jpg 같은 파일 형식을 포함해주자</p>
<p>예시</p>
<pre><code>ffmpeg -ss 00:00:0 -i C:\test.mp4 -r 10 -f image2 test-%d.jpg</code></pre><p>프레임 추출이 잘 되면 bin 폴더 안에 이미지가 저장 된 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ModuleNotFoundError: No module named 'plotly']]></title>
            <link>https://velog.io/@gml_01/ModuleNotFoundError-No-module-named-plotly</link>
            <guid>https://velog.io/@gml_01/ModuleNotFoundError-No-module-named-plotly</guid>
            <pubDate>Fri, 26 Nov 2021 05:19:37 GMT</pubDate>
            <description><![CDATA[<p>Jupyter Notebook(anaconda3)에서
<strong>ModuleNotFoundError: No module named &#39;plotly&#39;</strong>
에러가 났다.</p>
<p>cmd창에</p>
<pre><code>pip3 install plotly --upgrade</code></pre><p>했는데도 에러가 계속 나서</p>
<p>anaconda prompt(anaconda3) 관리자 권한으로 실행해서
위에 코드 쳤더니 해결됨.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WebRTC]]></title>
            <link>https://velog.io/@gml_01/webrtc</link>
            <guid>https://velog.io/@gml_01/webrtc</guid>
            <pubDate>Tue, 23 Nov 2021 05:11:16 GMT</pubDate>
            <description><![CDATA[<p>&#39;자리 이탈 감지 캠스터디 관리 웹 서비스&#39;로 주제를 정했다.</p>
<p>캠스터디는 혼자서 공부하는 사람들을 대상으로 각자의 공간에서 공부하는 모습을 캠으로 서로  보여주면서 열심히 공부하자는 의지를 함께 불태울 수 있는 서비스이다.</p>
<p>즉, 캠스터디는 서로가 공부를 열심히 하고 있는지 확인할 수 있는 온라인 독서실이다.</p>
<p>캠스터디는 보통 <strong>공부시간</strong>을 기준으로 캠스터디 방의 규칙을 정하는데,
기존의 캠스터디는 <strong>사용자가 직접 수동으로 타이머를 눌러 공부 시간을 기록하여, 목표 공부시간을 채웠는지 인증해야한다</strong>.</p>
<hr>
<p>그런데 이 공부시간을 직접 기록하는 것에 대해 캠스터디를 애용하는 몇몇 사용자의 의견을 물었는데, 다음과 같다.</p>
<p>&quot;공부하다가 가끔 타이머를 멈추지 않아 공부하지 않은 시간도 함께 기록되는 일이 있었다. 이럴 때마다 어느 정도의 시간을 기록된 시간에서 빼야할지 고민이 된다.&quot;
&quot;5분에서 10분 정도의 짧은 휴식시간을 가질 때마다 타이머를 눌러서 중지하고 다시 눌러서 시작하는 것이 귀찮을 때가 있다.&quot;</p>
<p>위 의견을 바탕으로 캠스터디 서비스인데, 사용자의 자리 이탈을 실시간으로 인식해서 공부시간을 자동으로 기록하여 인증까지 해주는 서비스가 있으면 좋겠다는 생각을 했다. </p>
<hr>
<p>&#39;<strong>자리 이탈 감지 캠 스터디 관리 웹 서비스</strong>&#39;를 구현하려면 크게 두 가지 기술 확인이 필요하다.</p>
<ol>
<li>실시간 영상통화 </li>
<li>캠에 보이는 손의 유무를 인식하여 스톱워치를 자동으로 중지할 수 있는지 </li>
</ol>
<hr>
<p>먼저 WebRTC로 실시간 영상통화 웹 사이트를 구현해보자 
구현한 영상통화 웹 사이트가 다음 두 조건을 만족하는지 확인해야한다.</p>
<ol>
<li>내 기기에서 캠으로 내 모습을 실시간으로 확인할 수 있는지</li>
<li>다른 기기에서도 내 모습을 실시간으로 확인할 수 있는지  </li>
</ol>
<hr>
<pre><code>npm init -y
</code></pre><p>terminal에 위 명령어로 package.json 파일을 생성한다.</p>
<hr>
<p>dependency를 추가하자</p>
<pre><code>npm i express ejs socket.io</code></pre><p>ejs는 embedded javascript의 약자로 express에서 dynamic website를 만들기 위해 template로 사용되는 파일이다. 
socket.io는 양방향 통신에 필요하다.</p>
<hr>
<pre><code>npm i uuid</code></pre><p>uuid는 네트워크 상에서 고유성이 보장되는 id를 만들기 위한 고유 식별자이다.
영상통화의 개별적인 id를 부여해 room을 만들 때 사용된다.
<img src="https://images.velog.io/images/gml_01/post/87b20bc4-7682-4d71-96d7-e168252b1ad7/image.png" alt="">
위처럼 새로운 id를 새로고침할 때마다 부여해준다. </p>
<hr>
<pre><code>npm i --save-dev nodemon</code></pre><p>nodemon(node monitor)는 파일이 수정되면 자동으로 노드를 재시작하는 확장 모듈이다. 노드 애플리케이션의 소스 코드를 수정할 때마다 매번 명령어로 새로 시작할 필요가 없어 편리하다.</p>
<hr>
<p>package.json 파일의 script 부분에 </p>
<pre><code>&quot;devStart&quot; : &quot;nodemon server.js&quot;</code></pre><p>을 추가하여 npm run devStart을 한번 하면 소스코드를 수정할때마다 새로 시작하지 않아도 된다.
<img src="https://images.velog.io/images/gml_01/post/74091d65-c474-40d4-940b-176ac115bc72/image.png" alt=""></p>
<hr>
<p>bash terminal을 열어서 peer 라이브러리를 다운로드하자
<a href="https://peerjs.com/">peerjs</a></p>
<pre><code>npm i -g peer</code></pre><pre><code>peerjs --port 3001</code></pre><p>하면 3001 포트에서 PeerServer가 작동된다.
<img src="https://images.velog.io/images/gml_01/post/556c16fd-385a-4267-8f84-f65ca7a6f6e2/image.png" alt=""></p>
<hr>
<p>코드를 짜는 중에 아래와 같은 에러가 났다.
<strong>TypeError: Cannot read property &#39;emit&#39; of undefined</strong>
(해결방법)
socket.io 버전이 상위버전이라 emit에서 에러가 났다.
socket.io 버전을 낮추자</p>
<pre><code>npm install socket.io@^2.3.0</code></pre><p><img src="https://images.velog.io/images/gml_01/post/b4fa8465-bd30-4759-86fd-1ae4b830b96b/image.png" alt="">
package.json 파일에서 socket.io 버전이 제대로 설치되었는지 확인.</p>
<hr>
<h3 id="소스코드">소스코드</h3>
<p>package.json</p>
<pre><code>{
  &quot;name&quot;: &quot;webrtc_test_2&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;,
    &quot;devStart&quot;: &quot;nodemon server.js&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;dependencies&quot;: {
    &quot;ejs&quot;: &quot;^3.1.6&quot;,
    &quot;express&quot;: &quot;^4.17.1&quot;,
    &quot;socket.io&quot;: &quot;^2.3.0&quot;,
    &quot;uuid&quot;: &quot;^8.3.2&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;nodemon&quot;: &quot;^2.0.15&quot;
  }
}
</code></pre><p>server.js</p>
<pre><code>const express = require(&#39;express&#39;)
const app = express()
const server = require(&#39;http&#39;).Server(app) //socket.io는 http위에서 동작한다.
const io = require(&#39;socket.io&#39;)(server)
const { v4: uuidV4 } = require(&#39;uuid&#39;)

app.set(&#39;view engine&#39;, &#39;ejs&#39;)
app.use(express.static(&#39;public&#39;)) //모든 js코드는 public 폴더 안에 넣는다.

app.get(&#39;/&#39;, (req, res) =&gt; {
    res.redirect(`/${uuidV4()}`) 
    //새로고침할때마다 랜덤 dynamic url을 만들어서 new room 생성
})

app.get(&#39;/:room&#39;, (req, res) =&gt; {
    res.render(&#39;room&#39;, { roomId: req.params.room })
})

io.on(&#39;connection&#39;, socket =&gt; {  
    socket.on(&#39;join-room&#39;, (roomId, userId) =&gt; {   
      socket.join(roomId)
      socket.to(roomId).broadcast.emit(&#39;user-connected&#39;, userId)

      socket.on(&#39;disconnect&#39;, () =&gt; {
        socket.to(roomId).broadcast.emit(&#39;user-disconnected&#39;, userId)
      })
    })
  })


server.listen(3000)</code></pre><p>room.ejs
room의 view를 보여주기위해 작성.</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;script&gt;
        const ROOM_ID = &quot;&lt;%= roomId %&gt;&quot;
    &lt;/script&gt;
    &lt;script
    defer
    src=&quot;https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js&quot;
    &gt;&lt;/script&gt;
    &lt;script src=&quot;/socket.io/socket.io.js&quot; defer&gt;&lt;/script&gt;
    &lt;script src=&quot;script.js&quot; defer&gt;&lt;/script&gt;
    &lt;title&gt;Document&lt;/title&gt;
    &lt;style&gt;
        #video-grid {
          display: grid;
          grid-template-columns: repeat(auto-fill, 300px);
          grid-auto-rows: 300px;
        }

        video {
          width: 100%;
          height: 100%;
          object-fit: cover;
        }
      &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id=&quot;video-grid&quot;&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p>script.js</p>
<pre><code>const socket = io(&#39;/&#39;)
const videoGrid = document.getElementById(&#39;video-grid&#39;)
/*
const myPeer = new Peer(undefined, {
  host: &#39;/&#39;,
  port: &#39;3001&#39;
})
*/
const myPeer = new Peer();

const myVideo = document.createElement(&#39;video&#39;)
myVideo.muted = true
const peers = {}
navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true
}).then(stream =&gt; {
  addVideoStream(myVideo, stream)

  myPeer.on(&#39;call&#39;, call =&gt; {
    call.answer(stream)
    const video = document.createElement(&#39;video&#39;)
    call.on(&#39;stream&#39;, userVideoStream =&gt; {
      addVideoStream(video, userVideoStream)
    })
  })

  socket.on(&#39;user-connected&#39;, userId =&gt; {
    connectToNewUser(userId, stream)
  })
})

socket.on(&#39;user-disconnected&#39;, userId =&gt; {
  if (peers[userId]) peers[userId].close()
})

myPeer.on(&#39;open&#39;, id =&gt; {
  socket.emit(&#39;join-room&#39;, ROOM_ID, id)
})

function connectToNewUser(userId, stream) {
  const call = myPeer.call(userId, stream)
  const video = document.createElement(&#39;video&#39;)
  call.on(&#39;stream&#39;, userVideoStream =&gt; {
    addVideoStream(video, userVideoStream)
  })
  call.on(&#39;close&#39;, () =&gt; {
    video.remove()
  })

  peers[userId] = call
}

function addVideoStream(video, stream) {
  video.srcObject = stream
  video.addEventListener(&#39;loadedmetadata&#39;, () =&gt; {
    video.play()
  })
  videoGrid.append(video)
}
</code></pre><hr>
<h3 id="실행결과">실행결과</h3>
<p>(영상통화 웹 사이트 하나만 열었을 때)
<img src="https://images.velog.io/images/gml_01/post/3c630bf3-c95d-4956-8a19-5f6c9d908a7f/image.png" alt=""></p>
<p>(영상통화 웹 사이트 같은 url로 두개 열었을 때)
<img src="https://images.velog.io/images/gml_01/post/4c319be2-7738-4353-8c02-b97421413268/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/gml_01/post/a6d4b257-0041-4c34-912f-52bcec97e121/image.png" alt=""></p>
<p>두 개 이상의 기기에서도 실시간 영상통화가 가능한지 구현해보자.</p>
<p>OpenSSL로 private.pem, public.pem 파일을 생성하자.</p>
<p>server.js 수정</p>
<pre><code>const express = require(&#39;express&#39;);
const app = express();
const { v4: uuidV4 } = require(&#39;uuid&#39;);
const fs = require(&#39;fs&#39;);
const https = require(&#39;https&#39;);

const server = https.createServer(
  {
    key: fs.readFileSync(&#39;./public/private.pem&#39;),
    cert: fs.readFileSync(&#39;./public/public.pem&#39;),
    requestCert: false,
    rejectUnauthorized: false,
  },
  app
);

const io = require(&#39;socket.io&#39;)(server);

app.set(&#39;view engine&#39;, &#39;ejs&#39;)
app.use(express.static(&#39;public&#39;))

app.get(&#39;/&#39;, (req, res) =&gt; {
    res.redirect(`/${uuidV4()}`) 
})

app.get(&#39;/:room&#39;, (req, res) =&gt; {
    res.render(&#39;room&#39;, { roomId : req.params.room })
})


io.on(&#39;connection&#39;, socket =&gt; {
    socket.on(&#39;join-room&#39;, (roomId, userId) =&gt; {
        socket.join(roomId)

        socket.to(roomId).emit(&#39;user-connected&#39;, userId)

        socket.on(&#39;disconnect&#39;, () =&gt; {
        socket.to(roomId).emit(&#39;user-disconnected&#39;, userId)
    })
    })
})

app.get(&#39;/:room&#39;)
server.listen(3000)</code></pre><p>https://서버IP:3000 으로 접속하자.</p>
<p>서버IP주소는 cmd창에서 다음 명령어로 확인하자.</p>
<pre><code>ipconfig</code></pre><p><img src="https://images.velog.io/images/gml_01/post/a4936f32-0cac-4f04-ad1c-79aca5eff8de/image.png" alt=""></p>
<p>PC에서 https://서버IP:3000 로 들어가게 되면 아래 같은 창이 뜬다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/7bb94051-1dfc-4156-b513-a992de16e886/image.png" alt="">
고급 버튼을 누른다.</p>
<p><img src="https://images.velog.io/images/gml_01/post/7ec3e534-4e4f-4cd6-99b5-249de302decd/image.png" alt="">
localhost(안전하지않음)으로 이동한다.</p>
<p>모바일도 마찬가지로 https://서버IP:3000 로 접속한다.
<img src="https://images.velog.io/images/gml_01/post/c265ac94-b404-4f99-aaaa-0fc244a6ceab/KakaoTalk_20211124_005426218_02.jpg" alt=""></p>
<p><img src="https://images.velog.io/images/gml_01/post/ccfc5d90-6ad6-4e06-818a-a932e2eaf0e9/KakaoTalk_20211124_005426218_01.jpg" alt=""></p>
<h3 id="실행결과-1">실행결과</h3>
<p>PC에서 볼때&gt;
<img src="https://images.velog.io/images/gml_01/post/49ececf8-8a91-4aa6-88f9-54ee0436e0ae/image.png" alt=""></p>
<p>모바일에서 볼때&gt;
<img src="https://images.velog.io/images/gml_01/post/2d205482-2f15-4737-ad43-87f970e3fb24/KakaoTalk_20211124_005426218.jpg" alt=""></p>
<p>참고
<a href="https://www.youtube.com/watch?v=DvlyzDZDEq4&amp;t=145s">How To Create A Video Chat App with WebRTC</a></p>
<p><a href="https://forest71.tistory.com/213">화상채팅예제로 익히는 WebRTC</a></p>
]]></description>
        </item>
    </channel>
</rss>