Skip to content

기능 테스트 (socket)

Hyein Jeong edited this page Nov 5, 2024 · 1 revision

1. Socket 사용하여 테스트

  • Socket 사용하여 테스트

image

- 위치의 제한 사항 → 로컬호스트로 테스트 중이다보니 여러명의 위치를 확인할 수는 없었다
- 접속중인 사용자 수를 나타내는 코드를 구현하여 보이게 만들었다
- 창을 3개 열고 구현한 결과

## 1. 서버 코드 수정

```jsx

// server.js
const users = {}; // 사용자 위치를 저장하는 객체

io.on('connection', (socket) => {
  console.log('A user connected:', socket.id);

  // 접속한 사용자 수를 모든 클라이언트에게 브로드캐스트
  io.emit('userCount', Object.keys(users).length + 1);

  // 사용자 위치 수신 및 저장
  socket.on('sendLocation', (data) => {
    users[socket.id] = data;
    io.emit('updateLocations', users); // 모든 클라이언트에 위치 정보 전송
  });

  // 사용자 연결 해제 시 위치 정보 제거
  socket.on('disconnect', () => {
    delete users[socket.id];
    io.emit('updateLocations', users); // 모든 클라이언트에 업데이트된 위치 정보 전송
    io.emit('userCount', Object.keys(users).length); // 접속한 사용자 수 전송
    console.log('User disconnected:', socket.id);
  });
});

server.listen(3001, () => {
  console.log('Server running on http://localhost:3001');
});

```

- `userCount` 이벤트를 추가하여 현재 접속 중인 사용자 수를 모든 클라이언트에 전송합니다.
- 사용자가 연결되거나 연결을 끊을 때마다 `Object.keys(users).length`를 통해 현재 접속 중인 인원 수를 계산합니다.

---

## 2. 클라이언트 코드 수정

클라이언트에서는 `userCount`와 `updateLocations` 이벤트를 통해 접속 인원 수와 위치 정보를 받아 화면에 표시하고, 마커를 동적으로 렌더링합니다.

```jsx

// App.js
import React, { useState, useEffect } from 'react';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import io from 'socket.io-client';

const socket = io.connect('http://localhost:3001');

function App() {
  const [location, setLocation] = useState({ latitude: null, longitude: null });
  const [otherLocations, setOtherLocations] = useState({});
  const [userCount, setUserCount] = useState(0); // 접속 인원 수 상태 추가

  // 현재 위치 추적 및 서버 전송
  useEffect(() => {
    if (navigator.geolocation) {
      navigator.geolocation.watchPosition(
        (position) => {
          const { latitude, longitude } = position.coords;
          setLocation({ latitude, longitude });
          socket.emit('sendLocation', { latitude, longitude });
        },
        (error) => console.error(error),
        { enableHighAccuracy: true, maximumAge: 10000, timeout: 5000 }
      );
    }
  }, []);

  // 서버로부터 업데이트된 사용자 위치 수신
  useEffect(() => {
    socket.on('updateLocations', (users) => {
      setOtherLocations(users);
    });

    // 접속 인원 수를 서버로부터 수신
    socket.on('userCount', (count) => {
      setUserCount(count);
    });

    return () => {
      socket.off('updateLocations');
      socket.off('userCount');
    };
  }, []);

  return (
    <div>
      <h1>실시간 위치 공유</h1>
      <p>접속 중인 사용자 수: {userCount}명</p> {/* 사용자 수 표시 */}
      <MapContainer
        center={[location.latitude || 0, location.longitude || 0]}
        zoom={13}
        style={{ height: '80vh', width: '100%' }}
      >
        <TileLayer
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />

        {/* 내 위치 표시 */}
        {location.latitude && location.longitude && (
          <Marker position={[location.latitude, location.longitude]}>
            <Popup>나의 위치</Popup>
          </Marker>
        )}

        {/* 다른 사용자의 위치 표시 */}
        {Object.keys(otherLocations).map((key) => (
          key !== socket.id && otherLocations[key].latitude && (
            <Marker
              key={key}
              position={[otherLocations[key].latitude, otherLocations[key].longitude]}
            >
              <Popup>상대방의 위치</Popup>
            </Marker>
          )
        ))}
      </MapContainer>
    </div>
  );
}

export default App;

```

### 코드 설명

- **`userCount` 상태**: 서버에서 `userCount` 이벤트를 통해 접속 중인 사용자 수를 받아서 `userCount` 상태에 저장합니다.
- **인원 수 표시**: 접속 중인 사용자 수를 `<p>접속 중인 사용자 수: {userCount}명</p>` 부분에 표시하여 실시간으로 보여줍니다.
- **마커 표시**: `otherLocations`에 저장된 다른 사용자 위치 정보에 따라 각 사용자 마커를 지도에 동적으로 표시합니다.

실시간 위치 파악 : navigator.geolocation.watchPosition

### 작동 과정 요약

1. **위치 감지 및 업데이트**: `watchPosition`은 사용자가 이동할 때마다 `position` 객체를 통해 새로운 `latitude`와 `longitude` 값을 제공합니다.
2. **위치 정보 서버 전송**: 새로운 위치 정보가 서버에 `sendLocation` 이벤트로 전송되고, 서버는 해당 사용자의 위치를 업데이트한 후 모든 클라이언트에 `updateLocations` 이벤트로 새로운 위치 정보를 브로드캐스트합니다.
3. **클라이언트 지도 업데이트**: 각 클라이언트는 `updateLocations` 이벤트를 수신하여, 지도에 표시된 마커 위치를 최신으로 유지합니다. 이로 인해 실시간으로 사용자의 위치가 지도 위에서 변경됩니다.

### 주의사항

- **watchPosition 옵션**: `enableHighAccuracy`와 같은 옵션으로 정확도를 높일 수 있지만, 배터리 소모량이 커질 수 있으므로 주의가 필요합니다.
- **빈번한 위치 업데이트**: 이동 속도와 설정에 따라 위치 업데이트가 매우 빈번하게 일어날 수 있으므로, 서버와 클라이언트에서의 성능을 고려해 필요에 따라 위치 변경을 제한하는 로직을 추가할 수 있습니다.

2. Socket을 사용하지 않는 경우

  • Socket을 사용하지 않는 경우

image

- 다음과 같이 2개를 접속할 경우 2개의 마크가 표시되는 것을 확인할 수 있습니다

Socket을 사용하지 않고도 **HTTP 요청**을 주기적으로 보내어 위치 정보를 업데이트하는 방법으로 구현할 수 있습니다. 이 경우, 클라이언트에서 **정기적인 폴링(polling)**을 통해 서버에 위치 정보를 업데이트하고, 다른 사용자들의 위치 정보를 받아오는 방식을 사용할 수 있습니다.

이 방법은 웹소켓보다 실시간성은 떨어지지만, 서버와의 연결이 간단하고 사용하기 쉬운 장점이 있습니다.

아래는 HTTP 폴링을 사용하여 위치를 업데이트하고, 여러 사용자의 위치를 지도에 표시하는 방법입니다.

---

## 1. 서버 코드 (Express 사용)

간단한 REST API 서버를 만들고, 클라이언트에서 정기적으로 위치 정보를 업데이트하도록 구성합니다.

```jsx
javascript
코드 복사
// server.js
const express = require('express');
const cors = require('cors');

const app = express();
app.use(cors());
app.use(express.json());

let users = {}; // 사용자 위치 정보를 저장할 객체

// 위치 정보 업데이트 API
app.post('/update-location', (req, res) => {
  const { userId, latitude, longitude } = req.body;
  users[userId] = { latitude, longitude }; // 사용자 위치 정보 업데이트
  res.sendStatus(200);
});

// 모든 사용자 위치 가져오기 API
app.get('/locations', (req, res) => {
  res.json(users); // 모든 사용자 위치 정보 반환
});

// 서버 실행
app.listen(3001, () => {
  console.log('Server running on http://localhost:3001');
});

```

- **`/update-location` 엔드포인트**: 클라이언트가 자신의 위치를 업데이트할 때 POST 요청을 보내는 API입니다.
- **`/locations` 엔드포인트**: 모든 사용자의 위치 정보를 JSON 형식으로 반환하는 API입니다.

---

## 2. 클라이언트 코드

클라이언트에서는 `navigator.geolocation.watchPosition`을 사용하여 정기적으로 자신의 위치를 서버에 전송하고, 일정 간격으로 서버에서 모든 사용자의 위치 정보를 가져옵니다.

```jsx
javascript
코드 복사
// App.js
import React, { useState, useEffect } from 'react';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import axios from 'axios';
import { v4 as uuidv4 } from 'uuid';

function App() {
  const [location, setLocation] = useState({ latitude: null, longitude: null });
  const [otherLocations, setOtherLocations] = useState({});
  const [userId] = useState(uuidv4()); // 고유 사용자 ID 생성

  // 현재 위치 추적 및 서버에 전송
  useEffect(() => {
    if (navigator.geolocation) {
      navigator.geolocation.watchPosition(
        async (position) => {
          const { latitude, longitude } = position.coords;
          setLocation({ latitude, longitude });

          // 서버에 위치 정보 업데이트
          await axios.post('http://localhost:3001/update-location', {
            userId,
            latitude,
            longitude,
          });
        },
        (error) => console.error(error),
        { enableHighAccuracy: true, maximumAge: 10000, timeout: 5000 }
      );
    }
  }, [userId]);

  // 일정 간격으로 모든 사용자 위치 정보 가져오기
  useEffect(() => {
    const fetchLocations = async () => {
      try {
        const response = await axios.get('http://localhost:3001/locations');
        setOtherLocations(response.data); // 모든 사용자 위치 정보 업데이트
      } catch (error) {
        console.error('Error fetching locations:', error);
      }
    };

    // 3초마다 위치 정보 가져오기
    const interval = setInterval(fetchLocations, 3000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div>
      <h1>실시간 위치 공유</h1>
      <MapContainer
        center={[location.latitude || 0, location.longitude || 0]}
        zoom={13}
        style={{ height: '80vh', width: '100%' }}
      >
        <TileLayer
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />

        {/* 내 위치 표시 */}
        {location.latitude && location.longitude && (
          <Marker position={[location.latitude, location.longitude]}>
            <Popup>나의 위치</Popup>
          </Marker>
        )}

        {/* 다른 사용자의 위치 표시 */}
        {Object.entries(otherLocations).map(([key, loc]) => (
          key !== userId && loc.latitude && loc.longitude && (
            <Marker
              key={key}
              position={[loc.latitude, loc.longitude]}
            >
              <Popup>상대방의 위치</Popup>
            </Marker>
          )
        ))}
      </MapContainer>
    </div>
  );
}

export default App;

```

### 코드 설명

- **고유 ID 생성**: `uuid` 라이브러리를 사용하여 각 사용자를 식별할 고유 ID를 생성합니다.
- **위치 업데이트**: `watchPosition` 메서드로 사용자의 위치가 변경될 때마다 서버로 위치를 POST 요청으로 전송합니다.
- **폴링으로 위치 정보 가져오기**: `setInterval`을 사용하여 3초마다 모든 사용자 위치 정보를 서버에서 가져와 업데이트합니다.
- **마커 표시**: 내 위치는 하나의 마커로, 나머지 사용자의 위치는 서버에서 가져온 위치 정보를 바탕으로 지도에 표시됩니다.

---

### 폴링 방식의 한계

- **실시간성**: 폴링 주기에 따라 서버의 데이터가 업데이트되므로, 웹소켓보다 위치 업데이트에 약간의 지연이 발생할 수 있습니다.
- **서버 부하**: 많은 사용자가 접속 중인 경우 폴링 요청이 많아져 서버에 부담이 될 수 있습니다.

위와 같은 방식으로 소켓 없이도 실시간에 가까운 위치 공유 기능을 구현할 수 있습니다.
Clone this wiki locally