마이닝풀에 접속하여 마이닝 하는 과정을 가정하여, 사이트 접속시 웹 소켓이 연결되는것을 peer접속
버튼을 통해 구현했고, 생성
버튼을 통해 블록이 생성되는 것을 구현하고, 갱신
버튼을 통해 갱신이 된 상태를 볼 수 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script
integrity="sha512-aoTNnqZcT8B4AmeCFmiSnDlc4Nj/KPaZyB5G7JnOnUEkdNpCZs1LCankiYi01sLTyWy+m2P+W4XM+BuQ3Q4/Dg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
</head>
<body>
<div>
<button id="peer">peer 접속</button>
</div>
<div>
<label for="">peer</label>
<button id="peerViewBtn">갱신</button>
</div>
<div id="peerView">
</div>
<div>
<label for="">block</label>
<button id="blockViewBtn">갱신</button>
</div>
<div id="blockView">
</div>
<div>
<label for="">블록 생성</label>
<input type="text" id ="blockData">
<!-- 블록 바디 내용 -->
<button id="blockCreate">생성</button>
</div>
</body>
<script>
peer.onclick = () =>{
axios.get("http://localhost:8080/peer/add")
}
const render =async()=>{
const {data:peer} = await axios.get("http://localhost:8080/peer");
peerView.innerHTML = peer.join("|");
}
peerViewBtn.onclick = render;
const blockRender = async()=>{
const {data : block} = await axios.get("http://localhost:8080/chains");
blockView.innerHTML = JSON.stringify(block);
}
blockViewBtn.onclick = blockRender;
const _blockCreate = async()=>{
const _blockData = [blockData.value];
const {data : block } = await axios.post("http://localhost:8080/block/mine",{data : _blockData});
console.log(block);
}
blockCreate.onclick = _blockCreate;
</script>
</html>
import Block from "@core/block/block";
import Chain from "@core/chain/chain";
import {WebSocket,WebSocketServer} from "ws";
// 기본적인 연결 관련된것만 있는 모듈 Ws
enum MessageType{
// 알기 쉽게 사용하려고
//0,1,2 상태를 지정한다했을 때
// 마지막 블록을 요청할 때
latestBlock = 0, // string 문자로 해도 되는데 오타가 발생할수 있어서 number로 오류가 최대한 없게
// 전체 체인을 요청할 때
allBlock = 1,
// 블록이 추가되서 알려줄 때
addBlock = 2,
}
interface IMessage {
// 메시지의 타입
type : MessageType;
// 메시지에 대한 값 데이터
payload : any;
}
class P2P extends Chain{
// Chain 상속받아서 Chain에 있는 메서드를 사용하려고
private sockets : Array<any> // 연결된 socket들 확인.
constructor() {
super();
this.sockets = [];
}
getSockets() : Array<WebSocket> {
return this.sockets;
}
connectSocket(socket : any, type? : MessageType) : void {
// 소켓을 연결하면
// 하나의 포트가 동적으로 생기고 그 포트에서 소켓을 들고 있는데.
// socket에는 고유의 포트가 들어있는 상태 충돌방지를 위해 애플리케이션 or 서비스 연결을 하면
// 동적으로 포트를 지정해준다. (고유 포트)
console.log("+++++++++socket",socket);
this.sockets.push(
`${socket._socket.remoteAddress} : ${socket._socket.remotePort}`
);
console.log("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
console.log(this.sockets);
// socket.send() 메서드를 호출하면 이벤트가 실행된다.
// message이벤트 실행
socket.on("message", (_data : string)=>{
const data = JSON.parse(_data.toString());
console.log("+++++++++++++++++data",data)
switch (data.type) {
case MessageType.latestBlock:
// 0이 들어오면 여기
const message : IMessage = {
// type
type : MessageType.latestBlock, // 모든블록 타입이 실행되는지 확인
// 마지막 블록은 payload에 담아서
payload : [this.latestBlock()],
}
// 완성한 객체를 문자열로 치환해서 보낸다.
socket.send(JSON.stringify(message))
break;
case MessageType.allBlock:
// 1이 들어오면 여기
break;
case MessageType.addBlock:
console.log("2에 들어옴");
// 2이 들어오면 여기
// 검증 로직은 여기에
const isValid = this.replaceChain(data.payload);
if(isValid.isError) break;
// 문제가 있으면 종료
//
const message2: IMessage = {
type : MessageType.addBlock,
payload : data.payload
}
this.sockets.forEach((item)=>{
// 현재 접속한 유저들에게 메시지 전송
item.send(JSON.stringify(message2));
})
break;
default:
break;
}
})
const msg : IMessage = {
type : MessageType.addBlock,
payload : new Chain().get()
}
socket.send(JSON.stringify(msg));
}
listen(port : number) : void{
// 현재 로컬에서 서버 생성
// 웹소켓 포트 오픈 대기상태
const server : WebSocketServer = new WebSocket.Server({port});
server.on("connection", (socket : WebSocket) =>{
// 소켓 연결 시도하면
console.log("new socket connection");
// 연결한 소켓을 배열에도 추가해주고 message 이벤트도 등록
this.connectSocket(socket);
})
}
addToPeer(peer : string) : void {
// 상대방이 내 ip에 접속 했을 때
// 소켓을 생성하고 연결한다.
// console.log("-----------peer",peer);
const socket : WebSocket = new WebSocket(peer);
// console.log("-----------socket",socket);
// 상대 소켓 서버 주소를 받아서 연결을 시도한다.
socket.on("open",()=>{
// 연결이 성공하면 open 이벤트가 실행된다
console.log("연결 성공");
this.connectSocket(socket, MessageType.addBlock);
//
})
}
}
export default P2P;
import Block from "@core/block/block";
import P2P from "./p2p";
import express, {Express, Request, Response} from "express" ;
import os from "os";
import cors from "cors";
const app : Express = express();
const ws : P2P = new P2P();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({extended : false}));
app.get("/chains",(req : Request, res : Response)=>{
res.json(ws.get());
})
app.post("/block/mine",(req : Request, res: Response)=>{
// 블록에 기록할 내용을 받고
const {data} : {data : Array<string>} = req.body;
const newBlock : Block | null = Block.generateBlock(ws.latestBlock(),data, ws.getAdjustmentBlock());
if(newBlock === null) res.send("error");
ws.addToChain(newBlock);
res.json(newBlock);
})
// post 작성을 했었는데 get으로 바꿀거고 오타 이슈 본인 v4확인도 귀찮
app.get("/peer/add",(req: Request, res: Response)=>{
const networkinterface = os.networkInterfaces();
// console.log(networkinterface);
let v4 : string;
for (const key in networkinterface) {
const Array = networkinterface[key];
for (const value of Array) {
// value.internal이 false 일때 && value.family 이 IPv4일 때
if(!value.internal && value.family === "IPv4")
v4 = value.address
// v4 ip 주소
}
}
ws.addToPeer(`ws://${v4}:7545`);
res.end();
})
app.get("/peer", (req : Request, res : Response) =>{
const sockets = ws.getSockets();
res.json(sockets);
});
app.listen(8080, ()=>{
console.log("server on");
ws.listen(7545);
})
//🚩index.html의 peer접속 버튼으로 아래 코드 실행.
axios.get("http://localhost:8080/peer/add")} 로 요청이 가고,
//🚩index.ts의 해당 코드가 실행된다.
app.get("/peer/add",(req: Request, res: Response)=>{
// 내 os의 네트워크 인터페이스를 가져온다.
const networkinterface = os.networkInterfaces();
let v4 : string;
// for in : 순차적으로 키값으로 객체의 값을 Array에 담음.
for (const key in networkinterface) {
const Array = networkinterface[key];
// for of : 순차적으로 배열의 값을 value에 담음.
for (const value of Array) {
// value.internal이 false 일때 && value.family 이 IPv4일 때
if(!value.internal && value.family === "IPv4")
// v4 = ip 주소
v4 = value.address
}
}
// const ws : P2P = new P2P(); 로 생성된
// ws에 addToPeer 메서드가 실행된다.
// ⭐⭐ ws:// 는 웹소켓의 url은 앞에 ws://가 붙어야하는 규칙이고, 웹소켓 서버 포트를 7545로 열어서 포트번호는 7545로 설정.
ws.addToPeer(`ws://${v4}:7545`);
//🚩p2p.ts의 addToPeer 메서드가 실행된다.
addToPeer(peer : string) : void {
// peer의 주소값으로 WebSocket 을 생성
// ⭐⭐ 웹소켓을 생성할 땐, 대개 서버의 주소및 포트를 지정해야 하기때문에
// ⭐⭐ 매개변수로 받은 peer로 생성자 매개변수로 넣어 생성.
// ⭐⭐ 아래코드까지 다 읽고 웹 소켓 연결을 설정하면 이 연결 시도가
// ⭐⭐ server.on("connection",...) 의 이벤트 핸들러로 이어지고 코드가 실행된다.
const socket : WebSocket = new WebSocket(peer);
socket.on("open",()=>{
console.log("연결 성공");
this.connectSocket(socket, MessageType.addBlock);
})
}
//🚩p2p.ts의 server.on("connection",...) 메서드가 실행된다.
// ⭐⭐ 소켓이 연결을 시도했을 때 실행되는 메서드 : server.on("connection",...)
// ⭐⭐ 연결 시도했을 때 socket에는
// ⭐⭐ WebSocket 클라이언트와 서버 간의 연결을 나타내는 WebSocket 객체가 전달된다.
server.on("connection", (socket : WebSocket) =>{
// 연결한 소켓을 배열에도 추가해주고 message 이벤트도 등록
this.connectSocket(socket);
})
//🚩p2p.ts의 connectSocket 메서드가 실행된다.
connectSocket(socket : any, type? : MessageType) : void {
this.sockets.push(
`${socket._socket.remoteAddress} : ${socket._socket.remotePort에는 }`);
// ⭐⭐ remoteAddress 에는 ::ffff:192.168.0.28
// ⭐⭐ remotePort에는 51070 의 값이 담기는데,
// ⭐⭐ ::ffff:192.168.0.28 는 IPv6 주소 표기법에서 IPv4 주소를 나타내기 위한 특별한 표기, 클라이언트 ip
// ⭐⭐ 51070 는 클라이언트에서 연결 시도한 포트 번호
// socket.send() 했을때 실행되는 핸들러
socket.on("message", (_data : string)=>{
const data = JSON.parse(_data.toString());
switch (data.type) {
case MessageType.latestBlock:
const message : IMessage = {
type : MessageType.latestBlock,
payload : [this.latestBlock()],
}
socket.send(JSON.stringify(message))
break;
case MessageType.allBlock:
break;
case MessageType.addBlock:
const isValid = this.replaceChain(data.payload);
if(isValid.isError) break;
const message2: IMessage = {
type : MessageType.addBlock,
payload : data.payload
}
this.sockets.forEach((item)=>{
item.send(JSON.stringify(message2));
})
break;
default:
break;
}
})
}
// ⭐⭐ 서버에 연결이 되었으니 socket.on("open",...)이 실행된다.
// 🚩p2p.ts 의 socket.on("open",...)
socket.on("open",()=>{
// 연결이 성공하면 open 이벤트가 실행된다
console.log("연결 성공");
// ⭐⭐ 매개변수 socket에는 클라이언트의 소켓 객체가 전달된다.
// remoteAddress : 서버의 ip주소, remoteport : 서버의 포트번호
// 위의 connectSocket 메소드가 실행되어 sockets 배열에 push 된다.
this.connectSocket(socket, MessageType.addBlock);
})
res.end();
})
// 🚩indext.html
peerViewBtn.onclick = render;
const render =async()=>{
// ⭐⭐ const {data:peer} 구조분해 할당,
// ⭐⭐ const {peer} = data ; 랑 같음.
const {data:peer} = await axios.get("http://localhost:8080/peer");
// 🚩 index.ts의 /peer 로 요청이 간다.
app.get("/peer", (req : Request, res : Response) =>{
const sockets = ws.getSockets();
// 🚩 p2p.ts의 getSockets() 가 실행된다.
getSockets() : Array<WebSocket> {
return this.sockets;
}
//-----------------------------------
// 리턴 받은 값을 res.json 으로 전달.
res.json(sockets);
});
// 구조분해한 peer의 값들을 join으로 문자열로 표시.
peerView.innerHTML = peer.join("|");
}
// 🚩 index.html
blockViewBtn.onclick = blockRender;
const blockRender = async()=>{
const {data : block} = await axios.get("http://localhost:8080/chains");
}
// 🚩 index.ts 의 /chains 로 요청이 간다.
app.get("/chains",(req : Request, res : Response)=>{
// get() 메서드를 실행하고 그 값을 res.json 으로 전달
res.json(ws.get());
})
// 🚩 chain.ts
// ws는 new p2p로 만들어진 클래스인데 p2p는 chain 클래스를
// 상속받았기때문에 chain클래스의 get() 메서드 접근이 가능.
// 현재 체인 반환
get(){
return this.chain;
}
// ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 객체를 문자열로 표시.
blockView.innerHTML = JSON.stringify(block);
blockCreate.onclick = _blockCreate;
const _blockCreate = async()=>{
// 블록의 data 부분이 string[]; 로 선언되어 있기때문에 []를 씌워 저장.
const _blockData = [blockData.value];
const {data : block } = await axios.post("http://localhost:8080/block/mine",{data : _blockData});
}
// /block/mine 으로 요청이 가서 새로운 블록을 생성
app.post("/block/mine",(req : Request, res: Response)=>{
// 블록에 기록할 내용을 받고
const {data} : {data : Array<string>} = req.body;
const newBlock : Block | null = Block.generateBlock(ws.latestBlock(),data, ws.getAdjustmentBlock());
if(newBlock === null) res.send("error");
// 새 블록이 생성되면 추가.
ws.addToChain(newBlock);
})