Nodejs 네트워킹 서비스 구현
1. 랜 에이전트
우리는 먼저 전편을 돌이켜 보면, 어떻게 국역 네트워크 내의 서비스 에이전트를 실현합니까?이것은 매우 간단하기 때문에, 직접 코드를 붙인다.
const net = require('net')
const proxy = net.createServer(socket => {
const localServe = new net.Socket()
localServe.connect(5502, '192.168.31.130') // ip。
socket.pipe(localServe).pipe(socket)
})
proxy.listen(80)
이것은 매우 간단한 서비스 프록시입니다. 코드가 간단명료하고 명확합니다. 의문이 있으면 파이프(pipe)일 것입니다. 간단하게 말해 보세요.cket은 읽을 수도 있고 쓸 수도 있는 데이터 흐름이다.코드에서 socket이 클라이언트의 데이터를 받을 때 데이터를 localSever에 쓰고 localSever가 데이터가 있을 때 데이터를 socket에 쓰고 socket은 클라이언트에게 보냅니다.2. 인라인 관통
랜 에이전트가 간단하고 내망이 뚫리는 것은 이렇게 간단하지 않지만, 그것은 핵심적인 코드이기 때문에 그에 대해 상당한 논리적 처리를 해야 한다.구체적으로 실현하기 전에 우리는 먼저 내부 그물을 정리해서 관통하자.
무엇이 내망이 뚫리는 것입니까?
간단하게 말하면 공공 네트워크 클라이언트로 랜 내의 서비스에 접근할 수 있다.예를 들어 로컬에서 시작한 서비스.공망 클라이언트가 로컬에서 시작한 serve를 어떻게 알 수 있습니까?이곳은 반드시 공망 서비스를 빌려야 한다.그렇다면 인터넷 서비스 측은 또 어떻게 현지 서비스를 알 수 있습니까?로컬과 서버가 socket 링크를 만들어야 합니다.
네 가지 역할
위의 묘사를 통해 우리는 네 개의 배역을 끌어냈다.
bridge
우리는 네 개의 캐릭터 1절에서 알 수 있듯이bridge는proxyServe와 socket 연결이고 데이터의 중간이며 코드를 위로 훑어보는 사고방식이다.
const net = require('net')
const proxyServe = '10.253.107.245'
const bridge = new net.Socket()
bridge.connect(80, proxyServe, _ => {
bridge.write('GET /regester?key=sq HTTP/1.1\r
\r
')
})
bridge.on('data', data => {
const localServer = new net.Socket()
localServer.connect(8088, 'localhost', _ => {
localServer.write(data)
localServer.on('data', res => bridge.write(res))
})
})
코드가 또렷하고 읽을 수 있으며 심지어 낭랑하게 입에 오르내리기도 한다.net 라이브러리를 도입하여 공망 주소를 성명하고bridge를 만들어bridge가 proxyServe에 연결하도록 합니다. 성공한 후에 proxyServe에 로컬 서비스를 등록합니다. 이어서bridge가 데이터를 감청하고 요청이 도착했을 때 로컬 서비스와의 연결을 만듭니다. 성공한 후에 요청 데이터를localServe에 보내고 응답 데이터를 감청하여 응답 흐름을 bridge에 기록합니다.나머지는 설명할 것이 없다. 어쨌든 이것은 예시 코드일 뿐이다.그러나 예시 코드에 세그먼트/regester가 있습니까?키=sq, 이 키는 큰 작용을 하는데, 여기서 키=sq.그러면 캐릭터 클라이언트가 프록시 서비스를 통해 로컬 서비스에 접근하는 것은 경로에 이 키를 붙여야 프록시 서버가 대응하는 브리지를 붙여서local Serve에 대응하는 것이다.
예를 들어 lcoalServe는: http://localhost:8088, rpoxyServe는 example입니다.com, 등록된 키는 sq입니다.그러면 prxoy Serve를 통해local Serve에 접근하려면 다음과 같은 글귀가 필요합니다. example.com/sq .왜 이렇게 써요?물론 하나의 정의일 뿐이다. 이 글의 코드를 읽은 후에 이런 약속을 수정할 수 있다.
그러면 다음 핵심 코드를 살펴보겠습니다.
proxyServe
이곳의proxyServe는 간소화된 예시 코드이지만 말하자면 여전히 복잡하다. 철저하게 이해하고 자신의 업무와 결합하여 사용 가능한 코드를 만들려면 많은 노력을 기울여야 한다.여기에서 나는 코드를 하나하나 나누어 설명하려고 하는데, 우리가 코드 블록에 이름을 지어서 설명하기 편리하도록 해 보자.
코드 블록 1:createServe
이 블록의 주요 기능은 프록시 서비스를 만들고client와bridge와 socket 링크를 만들고socket는 데이터 요청을 감청하며 리셋 함수에서 논리적으로 처리하는 것이다. 구체적인 코드는 다음과 같다.
const net = require('net')
const bridges = {} // bridge socket ,
const clients = {} // client socket , ,
net.createServer(socket => {
socket.on('data', data => {
const request = data.toString()
const url = request.match(/.+ (?<url>.+) /)?.groups?.url
if (!url) return
if (isBridge(url)) {
regesterBridge(socket, url)
return
}
const { bridge, key } = findBridge(request, url)
if (!bridge) return
cacheClientRequest(bridge, key, socket, request, url)
sendRequestToBridgeByKey(key)
})
}).listen(80)
데이터 감청의 코드 논리 보기:코드 블록 2: isBridge
브리지의 등록 요청인지 판단하는 것은 간단하지만, 실제 업무는 더욱 정확한 데이터를 정의할 수 있을 것이다.
function isBridge (url) {
return url.startsWith('/regester?')
}
코드 블록 3: regesterBridge간단하게 코드를 보고 설명합니다.
function regesterBridge (socket, url) {
const key = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key
bridges[key] = socket
socket.removeAllListeners('data')
}
논리가 코드 블록 4에 이르렀을 때 이것은 이미client 요청이라는 것을 설명한다. 그러면 그에 대응하는bridge를 찾아야 한다. bridge가 없으면 먼저 bridge를 등록하고 사용자가 잠시 후client 요청을 해야 한다.코드는 다음과 같습니다.
function findBridge (request, url) {
let key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key
let bridge = bridges[key]
if (bridge) return { bridge, key }
const referer = request.match(/\r
Referer: (?<referer>.+)\r
/)?.groups?.referer
if (!referer) return {}
key = referer.split('//')[1].split('/')[1]
bridge = bridges[key]
if (bridge) return { bridge, key }
return {}
}
코드가 여기까지 실행된 것은 클라이언트 요청이라는 것을 설명한다. 우리가 먼저 이 요청을 캐시합니다. 캐시를 할 때, 우리는 요청에 대응하는bridge,key를 함께 캐시하여 후속 조작을 편리하게 합니다.
클라이언트 요청을 캐시하는 이유는 무엇입니까?
현재의 방안에서, 우리는 요청과 응답이 모두 쌍으로 질서정연하기를 바란다.우리는 네트워크 전송이 모두 분할 전송이라는 것을 알고 있다. 현재로서는 우리가 응용층에서 요청과 응답을 쌍으로 제어하고 질서정연하게 하지 않으면 데이터 패키지 간의 혼란을 초래할 수 있다.또한 다음에 더 좋은 방안이 있으면 응용층에서 데이터의 요청 응답을 강제로 제어하지 않고 tcp/ip층을 신뢰할 수 있습니다.
원인을 말하고 캐시 코드를 살펴보자. 여기는 비교적 간단하고 복잡한 것은 요청을 하나하나 꺼내서 전체 응답을 질서정연하게 되돌려주는 것이다.
function cacheClientRequest (bridge, key, socket, request, url) {
if (clients[key]) {
clients[key].requests.push({bridge, key, socket, request, url})
} else {
clients[key] = {}
clients[key].requests = [{bridge, key, socket, request, url}]
}
}
이 브리지에 대응하는 키 밑에 클라이언트의 요청 캐시가 있는지 판단하고, 있다면push를 넣으십시오.만약 없다면, 우리는 대상을 만들어서 이번 요청을 초기화합니다.
다음은 가장 복잡한 것이다. 요청 캐시를 꺼내서bridge에 보내고bridge의 응답을 감청한다. 이번 응답이 끝날 때까지bridge의 데이터 감청을 삭제하고 다음 요청을 꺼내고client의 모든 요청을 처리할 때까지 위의 동작을 반복한다.
코드 블록 6: sendRequestToBridgeByKey
코드 블록 5의 마지막에 이 블록에 대해 개괄적인 설명을 했다.먼저 조금 이해할 수 있습니다. 아래 코드를 보십시오. 코드에 응답의 완전성에 대한 판단이 있기 때문에 이 부분을 제거하면 코드가 이해하기 쉽습니다.전체 방안에서 우리는 요청의 완전성에 대해 처리하지 않았다. 왜냐하면 요청의 기본은 모두 데이터 패키지 크기에 있기 때문이다. 파일 업로드 인터페이스가 아니라면, 우리는 잠시 처리하지 않을 것이다. 그렇지 않으면 코드가 좀 복잡해질 것이다.
function sendRequestToBridgeByKey (key) {
const client = clients[key]
if (client.isSending) return
const requests = client.requests
if (requests.length <= 0) return
client.isSending = true
client.contentLength = 0
client.received = 0
const {bridge, socket, request, url} = requests.shift()
const newUrl = url.replace(key, '')
const newRequest = request.replace(url, newUrl)
bridge.write(newRequest)
bridge.on('data', data => {
const response = data.toString()
let code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r
/)?.groups?.code
if (code) {
code = parseInt(code)
if (code === 200) {
let contentLength = response.match(/\r
Content-Length: (?<contentLength>.+)\r
/)?.groups?.contentLength
if (contentLength) {
contentLength = parseInt(contentLength)
client.contentLength = contentLength
client.received = Buffer.from(response.split('\r
\r
')[1]).length
}
} else {
socket.write(data)
client.isSending = false
bridge.removeAllListeners('data')
sendRequestToBridgeByKey(key)
return
}
} else {
client.received += data.length
}
socket.write(data)
if (client.contentLength <= client.received) {
client.isSending = false
bridge.removeAllListeners('data')
sendRequestToBridgeByKey(key)
}
})
}
클라이언트에서 브리지 키에 대응하는client를 꺼냅니다.이 클라이언트가 요청이 있는지 판단하고 발송하고 있으며, 있으면 실행을 끝냅니다.없으면 계속해.
이 클라이언트에서 요청이 있는지 판단합니다. 만약 있다면, 계속, 없습니다. 실행을 끝냅니다.
요청된 socket과 캐시된 브리지를 포함하는 첫 번째 대기열에서 꺼냅니다.
약속된 데이터를 교체하고 최종 요청 데이터를bridge에 보냅니다.
브리지의 데이터 응답을 감청하다.
총결산
이 코드를 이해하면 그 위에 확장되고 풍부한 코드를 만들어 당신을 위해 사용할 수 있습니다.이 코드를 다 이해하면, 너는 그것이 또 어떤 사용 장면이 있는지 생각할 수 있니?이 사고방식도 원격 제어에 사용할 수 있다. 클라이언트를 제어할 때 이 코드를 찾아보면 영감이 나지 않을까.
이 코드는 어려운 점이 있을 수 있습니다. tcp/ip에 대해 모든 것을 알고 http에 대해 알아야 하며 관건적인 요청 헤더를 알고 관건적인 응답 정보를 알아야 합니다. 물론 http에 대해 많이 알면 알수록 좋습니다.
교류할 것이 있으면 메시지를 남겨 주십시오.
proxyServe 소스
const net = require('net')
const bridges = {}
const clients = {}
net.createServer(socket => {
socket.on('data', data => {
const request = data.toString()
const url = request.match(/.+ (?<url>.+) /)?.groups?.url
if (!url) return
if (isBridge(url)) {
regesterBridge(socket, url)
return
}
const { bridge, key } = findBridge(request, url)
if (!bridge) return
cacheClientRequest(bridge, key, socket, request, url)
sendRequestToBridgeByKey(key)
})
}).listen(80)
function isBridge (url) {
return url.startsWith('/regester?')
}
function regesterBridge (socket, url) {
const key = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key
bridges[key] = socket
socket.removeAllListeners('data')
}
function findBridge (request, url) {
let key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key
let bridge = bridges[key]
if (bridge) return { bridge, key }
const referer = request.match(/\r
Referer: (?<referer>.+)\r
/)?.groups?.referer
if (!referer) return {}
key = referer.split('//')[1].split('/')[1]
bridge = bridges[key]
if (bridge) return { bridge, key }
return {}
}
function cacheClientRequest (bridge, key, socket, request, url) {
if (clients[key]) {
clients[key].requests.push({bridge, key, socket, request, url})
} else {
clients[key] = {}
clients[key].requests = [{bridge, key, socket, request, url}]
}
}
function sendRequestToBridgeByKey (key) {
const client = clients[key]
if (client.isSending) return
const requests = client.requests
if (requests.length <= 0) return
client.isSending = true
client.contentLength = 0
client.received = 0
const {bridge, socket, request, url} = requests.shift()
const newUrl = url.replace(key, '')
const newRequest = request.replace(url, newUrl)
bridge.write(newRequest)
bridge.on('data', data => {
const response = data.toString()
let code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r
/)?.groups?.code
if (code) {
code = parseInt(code)
if (code === 200) {
let contentLength = response.match(/\r
Content-Length: (?<contentLength>.+)\r
/)?.groups?.contentLength
if (contentLength) {
contentLength = parseInt(contentLength)
client.contentLength = contentLength
client.received = Buffer.from(response.split('\r
\r
')[1]).length
}
} else {
socket.write(data)
client.isSending = false
bridge.removeAllListeners('data')
sendRequestToBridgeByKey(key)
return
}
} else {
client.received += data.length
}
socket.write(data)
if (client.contentLength <= client.received) {
client.isSending = false
bridge.removeAllListeners('data')
sendRequestToBridgeByKey(key)
}
})
}
Nodejs의 인터넷 관통 서비스 실현에 관한 이 글은 여기까지 소개합니다. 더 많은 Node 인터넷 관통 내용은 저희 이전의 글을 검색하거나 아래의 관련 글을 계속 훑어보십시오. 앞으로 많은 응원 부탁드립니다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
NestJs Guard하지만 가드는 ExcutionContext를 사용할 수 있기 때문에 다음에 어떠한 라우트 핸들러가 실행되는지 정확하게 알 수 있다. ExecutionContext는 ArgumentsHost를 상속 받았기 때문에 각 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.