데이터를 받으려면 요청을 보내야 함. http는 주기적으로 요청을 보내야 함. 풀링 방식이라는 것을 함. 30초 정도 텀을 두고 계속 보내는 롱 풀링으로 처리했었음
한 번 요청을 보내면 끊어지지 않고 연결 통로가 연결되어 있음.
헤더 등도 1번만 보내고 딱 1번만 보냄.
ws프로토콜 로 사용함. http 를 대신함
http와 포트 공유가 가능함. app.listen(8005)라고 해도 8005 사용 가능함
서버 센트 이벤트 서버에서 프론트로 계속 데이터 보내는 것
websocket은 자유롭게 활동
WebSocket객체는 브라우저에서 제공을 해줌. http 대신 ws가 들어갈 뿐 ws://localhost:8005
webSocket.onmessage, websocket.send 이것 밖에 없음.
const WebSocket = require('ws');
module.exports = (server) => {
const wss = new WebSocket.Server({ server });
내용
}
이렇게
require로 가져온 이후에
webSocket(server)이걸 app.js에서 실행해서 연결해줌.
위 코드 내용 부분이 실행 되는 부분은
const socket = io.connect('http://localhost:8005');
가 프론트에 호출된 이후가 있음.
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
그나마 프록시 서버를 잡기 위해서 이렇게 아이피를 얻어낼 수 있지만 충분히 변조 가능함.
ws.on('message', (message) => { // 클라이언트로부터 메시지
console.log(message.toString());
});
이런 방식으로 메세지를 수신함. 이벤트 리스너와 유사함
프론트에서
webSocket.send('클라이언트에서 서버로 답장을 보냅니다');
를 한 거를 캐치 가능함.
::1 이건 ipv6의 로컬호스트 임.
ipv4가 사용자가 많아지기에 늘리기 위해서 ipv6으로 늘린 ipv6으로 표현함.
if(ws.readyState===ws.OPEN) 이게 연결되었는지 검사하는 방식이 있음.
ws가 기초고 서버가 보내는 것이 모든 사람에게 다 가버림.
socket.io가 채팅방을 사용하기 좋은 그냥 패키지
json 받으면 json.stringify 로 보내고, json.parse로 다시 json으로 받고 그런 것들을 socket.io가 자연스럽게 해줄 뿐임.
특정 사람한테만 보내는 것들을 socket.io가 구현해둬서 조금 편함.
const io = SocketIO(server, { path: '/socket.io' });
여기 path는 프론트랑 일치 시켜야 됨.
<script src="/socket.io/socket.io.js"></script>
이 파일은 socketio가 넣어줌.
프론트의 script에서는 const socket=io.connect(http://localhost:8005) 라는 걸로 적어야 함.
서버, 프론트 각자에 이벤트와 데이터를 따로 정해서 줌.
websocket은 그냥 메세지만 보낼 수 있지만 json 형식으로 보내고, 파싱하는 방식으로 처리하기에 저런 것들이 가능함.
const req = socket.request;
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
console.log('새로운 클라이언트 접속!', ip, socket.id, req.ip);
socket id는 고유함.
socket.emit("이벤트명","데이터"); 하면 콜백이 실행됨.
통신 방식은
1. 풀링 연결 시도
2. 소켓 아이디 발급
3. 웹소켓 연결
이런 이유가 IE를 위한 방법임. 풀링 방법은 IE도 되고, 그 이후 웹소켓은 IE가 안 되기 때문에. 업그레이드 하는 방법임.
transport:['websocket']로 하는 것.
중간에 네트워크 탭에 있는 2,3이 왔다 갔다 하는데, 이거는 그냥 제대로 연결 됬는지 확인 하는 정도
Socket.IO패키지를 불러와 첫 인자 express server, 2번째 인자 클라이언트와 연결할 경로
connection이벤트는 서버와 연결되었을 때 호출, 콜백으로 소켓 객체 제공
socket.request로 접근, socket.id 아이디
reply는 직접 만들 이벤트
color hash: 랜덤 컬러
네임스페이스의 장점은
const socket = io.connect('http://localhost:8005/room', { // 네임스페이스
path: '/socket.io',
});
딱 room에 관한 것들에만 받게 됨.
const socket = io.connect('http://localhost:8005/chat', {
path: '/socket.io',
});
이거는 당연히 chat에 관해서만 요청을 받게 됨.
여기에 있는 이 path는 프론트에 코드지만 server에서도 똑같은 path 여야지만 작동을 할 수 있다는 점. 위에도 있지만 중요함.
app.set('io', io);
이 부분이 req.ap.get('io') 라우터랑 socketio랑 연결하는 방법임.
이런 느낌으로
const room = io.of('/room');
const chat = io.of('/chat');
room.on('connection', (socket) => {
console.log('room 네임스페이스에 접속');
socket.on('disconnect', () => {
console.log('room 네임스페이스 접속 해제');
});
});
따로 네임스페이스 설정 했으면 내부에서도 네임스페이스별로 따로 설정이 필요함.
IO, 네임스페이스, 방 단위로 특정한 사용자에게 정보를 공유할 수 있다는 특징이 있음.
소켓 아이디로는 한 사람만 처리도 가능함.
방 내에서는 색을 갖게 하기 위해서는 session 을 이용한 방법으로 처리함.
webSocket(server, app, sessionMiddleware);
앱까지 넘겨주는 이유는 라우터에 웹소켓을 연결하기 위해서
그냥 추가로 변수를 넘겨줘도 됨, 그냥 당연한
const { rooms } = io.of("/chat").adapter;
//const rooms=io.of("/chat").adapter.rooms;와 같음
io.of("chat")
if (
rooms &&
rooms[req.params.id] &&
room.max <= rooms[req.params.id].length
) {
return res.redirect("/?error=허용 인원이 초과하였습니다.");
}
이 부분이 되게 중요함.
socket.request.session.color과
req.session.color이 서로 다름. 익스프레스 세션을 장착 했기에 밑에는 존재함. 위는 app.use session을 적용함.
io.use((socket, next) => {
cookieParser(process.env.COOKIE_SECRET)(socket.request, socket.request.res||{}, next);
sessionMiddleware(socket.request, socket.request.res, next);
});
그게 이 과정임.
여기 sessionMiddleware는 app.js에서 넘겨줌.
이런 방식으로 적으면 됨.
socket.to(roomId).emit("join", {
user: "system",
chat: `${req.session.color}님이 입장하셨습니다.`,
});
유저를 시스템 처럼 하는 방법
system이 아닌 경우 user: req.session.color로 처리할 것
console.log
const {
headers: { referer },
} = req;
결국 이 모든 것은 사실 axios 같은 느낌이다라는걸 잊으면 안됨.
브라우저=>서버는 세션 쿠키가 있기에 누구인지 파악 가능하지만
서버=>서버는 처리가 안됨. 그래서 쿠키 파서를 이용한 세션 쿠키를 넣음.
const signedCookie = cookie.sign(
req.signedCookies["connect.sid"],
process.env.COOKIE_SECRET
);
req.signedCookies 안에는 완벽하게 서명이 풀려 있기에 다시 서명하고, connect.sid에 넣을 때는 s%3A를 앞에 붙혀야 됨.
req.app.get("io").of("/chat").to(req.params.id).emit("chat", chat);
req.app.get("io").of("io").emit("chat", chat);
특정 방, 방
to.socketId인 경우에는 특정 사람만 됨
to 대신 broadcast는 저를 제외한 나머지한테 보냄.
이런 여러가지 것들이 있음
db 없이 그냥 왔다갔다 할 수도 있는데 왜 db 를 쓸까? 라는 것.
http라우터를 쓴 이유는 라우터에서 db를 쓰는게 편함.
실무에서는 웹소켓만으로 처리하는게 제일 좋긴 함.
멀터, path, fs 이 3개가 되게 많은 경우 한 번에 쓰임
const upload = multer({
storage: multer.diskStorage({
destination(req, file, done) {
done(null, "uploads/");
},
filename(req, file, done) {
const ext = path.extname(file.originalname);
done(null, path.basename(file.originalname, ext) + Date.now() + ext);
},
}),
limits: { fileSize: 5 * 1024 * 1024 },
});
router.post("/room/:id/gif", upload.single("gif"), async (req, res, next) => {
try {
const chat = await Chat.create({
room: req.params.id,
user: req.session.color,
gif: req.file.filename,
});
req.app.get("io").of("/chat").to(req.params.id).emit("chat", chat);
res.send("ok");
} catch (error) {
console.error(error);
next(error);
}
});
멀터 설정 이후에,
챗 대신에 파일명
git면 image 태그, 챗이면 div태그 로 알아서 만들어 줄 것이다.
서버센트 이벤트는 서버에서만 클라이언트로 보낼 수 있음
패키지 sse socket.io
main.html에다가 ie를 위한 polyfill을 하면 괜찮긴 한데 안 해도 문제는 없음.
setTimeout의 문제는 서버 껐다 키면 날아가는 것과, 시간 부정확의 문제.
node-schedule 은 서버 껐다 키면 날아가지만 정확하긴 함
운영체제의 스케쥴러로 똑같이 할 수는 있음
child_process로 처리함.
const t = await sequelize.transaction();
이걸 { where: { id: good.id }, transaction: t }
이렇게 위치마다 하면
transaction은 모두 동시에 성공하지 않으면 원래 상태로 되돌리는 특징
끝나면 await t.commit();
try catch 로 catch(e){
await t.rollback()을 하면 알아서 롤백해줌
}
2가지
1. 경매 중인 것
2. 경매 끝났지만 서버 종료로 안 된것