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 링크를 만들어야 합니다.
네 가지 역할
위의 묘사를 통해 우리는 네 개의 배역을 끌어냈다.
  • 공망 클라이언트, 우리는client라고 이름을 지었다.
  • 공망 서비스 측은 대리의 작용이 있기 때문에 우리는proxyserve라고 이름을 지었다.
  • 로컬 서비스, localServe 이름.
  • 로컬과 서비스 단말기의 socket 길이가 연결되어 있습니다. 이것은 proxyServe와local Serve 이전의 교량으로 데이터의 중전을 책임집니다. 우리는bridge라고 이름을 지었습니다.
  • 그 중에서client와local Serve는 우리의 관심을 필요로 하지 않는다. 왜냐하면client는 브라우저나 다른 것일 수 있기 때문에local Serve는 일반적인 로컬 서비스이기 때문이다.우리는 프록시 서버와 브리지에만 관심을 가지면 된다.우리가 여기서 소개한 것은 여전히 가장 간단한 실현 방식이고 사고방식과 사고를 제공한다. 그러면 우리는 가장 간단한 것부터 시작한다.

    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)
    
    
    데이터 감청의 코드 논리 보기:
  • 요청 데이터를 문자열로 변환합니다.
  • 요청에서 URL을 찾습니다. URL을 찾을 수 없으면 이 요청을 종료합니다.
  • URL을 통해 bridge인지 아닌지를 판단합니다. 만약에 이 bridge를 등록한 사람은 클라이언트 요청이라고 생각합니다.
  • client 요청에 등록된 브리지가 있는지 확인하십시오. 이것은 에이전트 서비스입니다. 등록된 브리지가 없으면 요청이 무효라고 생각합니다.
  • 이번 요청을 캐시합니다.
  • 이어서 요청을 브리지에 보냅니다.
  • 코드와 논리적 정리를 결합하면 알 수 있겠지만 5에 대해 의문이 있을 수도 있으니 하나하나 정리한다.
    코드 블록 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')
    }
    
  • URL을 통해 등록할 bridge의 키를 찾습니다.
  • socket 연결을 캐시합니다.
  • bridge의 데이터 감청 제거 - 코드 블록 1에 있는 모든 socket에는 기본 데이터 감청 회신이 있습니다. 만약 제거하지 않으면 후속 데이터의 혼란을 초래할 수 있습니다.
  • 코드 블록 4: findBridge
    논리가 코드 블록 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 {} }
  • URL에서 프록시할 브리지의 키와 일치하면 대응하는 브리지와 키를 되돌려줍니다.
  • 요청 헤더에 있는referer에서 찾을 수 없습니다. 찾으면 브리지와 키로 돌아갑니다.
  • 도 찾을 수 없습니다. 코드 블록 1리에서 이번 요청을 끝낼 수 있다는 것을 알고 있습니다.
  • 코드 블록 5: cacheClientRequest
    코드가 여기까지 실행된 것은 클라이언트 요청이라는 것을 설명한다. 우리가 먼저 이 요청을 캐시합니다. 캐시를 할 때, 우리는 요청에 대응하는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에 보냅니다.
    브리지의 데이터 응답을 감청하다.
  • 응답 코드 얻기
  • 응답이 200이면 콘텐츠length를 가져옵니다. 만약 있다면, 이번 요청에 대해 초기화 작업을 하겠습니다.요청 길이를 설정하고 전송된 요청 길이를 설정합니다.
  • 만약에 200이 아니라면 우리는 데이터를 클라이언트에게 보내고 이번 요청을 끝내고 이번 데이터 감청을 제거하고sendRequestToBridgeByKey
  • 를 귀속적으로 호출합니다
  • 만약 코드를 얻지 못했다면, 우리는 이번 응답이 처음이 아니라고 생각하기 때문에, 그 길이를 이미 발송된 필드에 누적합니다.
  • 우리는 이어서 이 데이터를 클라이언트에게 보냈다.
  • 응답의 길이가 이미 발송된 데이터의 길이와 일치하는지 판단하고 일치하면client의 데이터 발송 상태는false로 설정하여 데이터 감청을 제거하고 귀속 호출sendRequest ToBridge ByKey로 설정합니다.
  • 이로써 핵심 코드 논리는 모두 끝났다.

    총결산


    이 코드를 이해하면 그 위에 확장되고 풍부한 코드를 만들어 당신을 위해 사용할 수 있습니다.이 코드를 다 이해하면, 너는 그것이 또 어떤 사용 장면이 있는지 생각할 수 있니?이 사고방식도 원격 제어에 사용할 수 있다. 클라이언트를 제어할 때 이 코드를 찾아보면 영감이 나지 않을까.
    이 코드는 어려운 점이 있을 수 있습니다. 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 인터넷 관통 내용은 저희 이전의 글을 검색하거나 아래의 관련 글을 계속 훑어보십시오. 앞으로 많은 응원 부탁드립니다!

    좋은 웹페이지 즐겨찾기