如何使用 react-native 和 socket.io 从客户端向服务器发送 base64 图像?

How to send a base64 image from client to server with react-native and socket.io?

在一个 react-native 项目中,我正在尝试使用 websockets (socket.io-client/socket.io) 将 base64 编码的字符串从客户端传输到服务器,然后再传输到另一个客户端。

我期待什么:

我遇到了什么:

Server.jsio/socket相关代码:

io.on("connection", socket => {
console.log('\n[ Connected To Client ]\n', socket.id)

  initRoom(socket)

  handleReceivedData(socket)

  socket.on('disconnect', () => {
    console.log('\n[ Client Disconnected ]\n', connectedAccounts)
    delete connectedAccounts
  })

  socket.on('client_send_data', e => {
    console.log(socket.id, 'sent', e)

    socket.to(e.account_id).emit('server_send_data', e.data)
  })
});


const initRoom = socket => {
  socket.emit('server_join_room')

  socket.on('client_join_room', accountId => {
    if (connectedAccounts.hasOwnProperty(accountId)) {
      const socketIds = connectedAccounts[accountId]['socket_ids']
      socketIds.push(socket.id)
    } else {
      connectedAccounts[accountId] = {
        inProgress: true,
        socket_ids: [socket.id]
      }
    }

    socket.join(accountId)
  })
}

const handleReceivedData = socket => {
  socket.on('admin_data_send', e => {
    console.log('[ Data Received ]')
    const { account_id, data } = e

    io.to(account_id).emit('display_data_send', data)
  })
}

照片选择器(发件人):

export default Photo = ({ navigation }) => {
  const [photoURI, onPhotoURI] = useState('')

  const openPicker = () => {
    ImagePicker.openCamera({
      cropping: false,
      includeBase64: true
    })
      .then(async image => {
        onPhotoURI(image.data)
        console.log('[ photo ]:', image)
        await AsyncStorage.removeItem('some_key')
        await AsyncStorage.setItem('some_key', String(image.data))
      })
      .catch(e => alert(e))
  }

  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: 'gray' }}>
      <Text>Photo Data Stored: {photoURI !== '' ? 'True' : 'False'}</Text>
      <Pressable onPress={openPicker}>
        <Text>Take Picture</Text>
      </Pressable>
    </SafeAreaView>
  )
}
sendPic = async () => {
  console.log('[ Attempting to send Data to server ]')
  this.test.emit('admin_data_send', {
    account_id: ACCOUNT_ID,
    data: this.state.photoURI
  })
}

首页(客户端)

class Home extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      socketId: '',
      photoBase64: '',
      readyForSync: false,
      displayData: null
    }
    this.test = null
  }

  componentDidMount() {
    this.initSocket()
  }

  async componentDidUpdate() {
    try {
      const stored = await AsyncStorage.getItem('some_key')
      this.setState({ photoBase64: stored })
    } catch (e) {
      alert(e)
    }
  }

  initSocket = async () => {
    const endpoint = await isEmulator() ? emulator_socket : socket
    this.test = io(endpoint)
    
    this.test.on("connect", async () => {
      console.log(`${await isEmulator() ? '[ Emulator Connected ]' : '[ Physical Connected ]'}`, 'Endpoint:', endpoint, 'Id:', this.test.id)
      this.setState({ socketId: this.test.id })
    });

    this.test.on("disconnect", () => {
      console.log('[ disconnected ]', this.test.id); // undefined
    });

    this.test.on('server_join_room', () => {
      this.test.emit('client_join_room', ACCOUNT_ID)
    })

    this.test.on('display_data_send', e => {
      console.log('[ Display Data Recevied From Server ]')
      this.setState({ displayData: e })
    })
  }

  sync = async () => {
    this.setState({ readyForSync: true })
    console.log('[ Sync Started ]')

    this.test.emit('display_ready', {account_id: ACCOUNT_ID})
  }

  sendPic = async () => {
    const endpoint = await isEmulator() ? emulator_socket : socket
    console.log('[ Attempting to send Data to server ]')

    // divide base64 into 3 segments
    console.log('[ base64 length ]:', this.state.photoURI.length)
    // loop the emit
    this.test.emit('admin_data_send', {
      account_id: ACCOUNT_ID,
      data: this.state.photoBase64
    })
  }

  render() {
    return (
      <SafeAreaView style={{ flex: 1, backgroundColor: '#d1d1d1' }}>
        {this.state.readyForSync ? (
          <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <ActivityIndicator animating={true} color="#333" size="large" />
            <Text>Awaiting Data From Admin Device...</Text>
            {
              this.state.displayData !== null && (
                <View style={{ width: 200, height: 200 }}>
                  <Image src={{ uri: `data:image/jpeg;base64,${this.state.displayData}` }} />
                </View>
              )
            }
          </View>
        ) : (
          <>
            <Text style={{ textAlign: 'center', fontSize: 20, fontWeight: 'bold', marginTop: 20 }}>{Platform.isTV ? 'TV App' : Platform.OS}</Text>

            <Text style={{ textAlign: 'center', fontSize: 20, fontWeight: 'bold', marginTop: 20 }}>Socket ID: {this.state.socketId}</Text>

            <Pressable style={styles.press} onPress={this.sync}>
              <Text> SYNC APP </Text>
            </Pressable>

            <Pressable style={styles.press} onPress={() => this.props.navigation.navigate('Photo')}>
              <Text> TAKE PIC </Text>
            </Pressable>

            <Pressable style={styles.press} onPress={this.sendPic}>
              <Text> SEND PIC </Text>
              <Text>{this.state.photoBase64}</Text>
            </Pressable>
          </>
        )}
      </SafeAreaView>
    )
  }
}

如何确保连接没有丢失并且数据被服务器接收并成功传输回接收客户端?

我怀疑 maxHttpBufferSize,因为默认值只有 1 MB (1e6)。您的 base64 编码图像可能会导致消息超过此限制,根据文档,这将关闭套接字。

io 的初始化中,指定

可能会有所帮助
const io = new Server(httpServer, {
  maxHttpBufferSize: 1e8 // 100 MB
});

请尝试将字符串作为数据包发送,并在您的前端重新收集它们。通过这种方式,您也可以发送大文件而不会出现数据丢失的问题,而且速度和性能方面也会很高效。尝试使用并行处理发送这些数据包。

如果您使用的是 node,请尝试使用 lodash 函数或 bluebird 函数来实现您想要的效果。这也将大大提高文件 send/receive 的性能。

还可以使用 atob() 将其转换回图像。