2015-04-15 2 views
0

Je travaille sur une application pour mon téléphone android afin que je puisse importer des sms, les lire et répondre aux sms. Tout a fonctionné comme je l'avais prévu lors de la programmation du serveur et du client. Si j'avais des problèmes, la recherche Google m'a donné des solutions, mais cette fois-ci, pour la première fois de ma vie, je vous demande de l'aide.Comment puis-je envoyer une chaîne Java avec des caractères Unicode en C++ via socket, sans caractères étranges?

Le problème:

Le problème est, lorsque le client (Java) envoie le contenu SMS qui contient des caractères unicode tels que "å, ä, ö", C++ ne peut pas les lire.

Mon programme fonctionne qu'il envoie d'abord la taille de paquet pour rendre l'autre conscient de la taille du paquet qui viendra. Ainsi, par exemple, Java calcule que le paquet sera de 121 octets et l'envoie au serveur. Mais si le paquet contient peu de caractères non ANSI, C++ ne recevra pas 121 octets, mais 123 octets, et les caractères non-ANSI deviendront étranges.

J'ai été googling toute la journée sans réponses. J'ai essayé wchar_t en C++, j'ai essayé de tout mettre en Java pour l'envoyer en utilisant UTF-8, j'ai débogué pendant des heures pour recréer le problème et essayer différentes choses, mais sans succès!

Alors, que se passe-t-il ici? Comment puis-je obtenir le texte de Java en C++ dans la bonne taille et la représentation comme en Java? Les paquets sans caractères Unicode fonctionnent correctement.

Merci les gars! Un peu fatigué atm, j'espère que je n'ai rien manqué. Le code pourrait être un peu brouillon, ce n'est qu'un prototype pour le moment.

P: S, Ceci est une connexion TCP.

-Server C++ recv Fonction-

bool Receive(std::string& msg) 
{ 
    zReadMutex.lock(); 

    try 
    { 
     int errCode; 
     unsigned int packetSize = 0; 
     char packetSizeBuffer[4]; 

     //Get packet size 
     errCode = recv(zSocket, packetSizeBuffer, sizeof(packetSizeBuffer), 0); 

     if (errCode == SOCKET_ERROR || errCode == 0) 
     { 
      throw NetworkException("Failed Receiving Packet Size!", WSAGetLastError()); 
     } 

     //Convert 
     packetSize = CharArrayToUnsignedInt(packetSizeBuffer); 

     if (packetSize == 0) 
     { 
      throw NetworkException("Connection Closed!"); 
     } 

     //Calculate chunks 

     //Total bits received 
     unsigned int totalBits = 0; 
     //Calculate number of chunks that will arrive 
     int chunks = CaculateChunks(packetSize); 
     //Counter for the chunk loop 
     int count = 0; 
     //Add to message for every chunk received 
     std::string message = ""; 

     //Just a temp check 
     if (chunks > 15) 
     { 
      throw NetworkException("Connection Closed!"); 
     } 

     //Get Chunks 
     while (count < chunks) 
     { 
      char* buffer = new char[zMaxChunkSize]; 

      if ((errCode = recv(zSocket, buffer, zMaxChunkSize, 0)) <= 0) 
      { 
       if (errCode < 0) 
       { 
        delete [] buffer; 
        throw NetworkException("Failed Receiving Packet Data!", WSAGetLastError()); 
       } 
       else 
       { 
        delete [] buffer; 
        throw NetworkException("Connection Closed!"); 
       } 

      } 

      totalBits += errCode; 
      count++; 
      message += buffer; 

      delete [] buffer; 

     } 

     if (packetSize != totalBits) 
     { 
      throw NetworkException("Message is not expected size!"); 
     } 

     message.resize(totalBits); 
     msg = std::string(message); 

    } 
    catch(...) 
    { 
     zReadMutex.unlock(); 
     throw; 
    } 

    zReadMutex.unlock(); 
    return true; 
} 

- Client Java Envoyer Fonction -

public boolean InitSender() 
{ 
    if(mSocket == null) 
     return false; 

    try { 
     //Auto flush is false, but it auto flush anyways 
     out = new PrintStream(mSocket.getOutputStream(), false, "UTF-8"); 

    } catch (IOException e) { 
     e.printStackTrace(); 
     return false; 
    } 

    return true; 
} 

public synchronized void SendMessage(final String a) 
{ 
    int size = 0; 
    size = a.length(); 

    //Send size 
    out.print(size); 

    //Chunk it 
    int chunks = CalculateChunks(a); 
    String[] data = SplitToChunks(a, chunks); 

    for (String message : data) 
    { 
     //Send data 
     out.print(message); 
    } 
} 
+0

probablement la cause racine de votre confusion: Sockets ne pas envoyer des caractères, ils envoient octets. – immibis

+0

Je sais qu'ils n'envoient pas de caractères, mais ils sont convertis en octets bruts avant d'être envoyés. Je ne vois pas vraiment ce que vous voulez dire, – Ediz

+0

La 'taille' que vous envoyez ne correspond pas au nombre réel d'octets que vous envoyez.La 'size' est exprimée en termes de caractères UTF-16, mais le' message' est envoyé en UTF-8 à la place. Ils encodent différemment les caractères non-ASCII, vous devez donc convertir 'message' en UTF-8 avant d'envoyer sa longueur. –

Répondre

2

Ainsi, par exemple Java calcule le paquet sera 121 octets et envoie à le serveur.

size = a.length(); 
//Send size 
out.print(size); 

Ce code ne correspond pas à la description; .length() sur une chaîne Java ne compte pas les octets. Vous envoyez le nombre d'éléments Java char dans la chaîne. Un Java char est deux octets.

 out.print(message); 

message est un Java String. Vous devez regarder comment String est converti en octets à envoyer à travers la connexion réseau. Il n'y a aucune garantie que cette conversion crée le même nombre d'octets que Java char dans la chaîne. En particulier, si la chaîne est convertie en UTF-8, certaines valeurs Java char individuelles seront converties en deux ou trois octets.

Vous devez effectuer la conversion avant d'envoyer les données afin de pouvoir compter le nombre réel d'octets envoyés.

Du côté C de

, un std::string est une séquence de char C++ éléments qui ne sont pas les mêmes que Java char s. C++ char est un octet unique. Dans votre code le std::string contiendra les mêmes données que vous avez lues sur le réseau; Si le client envoie des données UTF-8, le std::string contient les données UTF-8. Pour afficher la chaîne, vous devez utiliser une API qui gère le codage, ou le convertir. Sinon, il semblera que certains des personnages sont «étranges».


est ici un début raisonnable sur l'apprentissage quelques-unes des choses que vous devez savoir:

The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

+0

Plus pour le bénéfice de l'OP que pour le vôtre, comme je suis sûr que vous le savez, un 'char' Java ne contient pas un point de code Unicode logique (un nombre de 0 .. 0x10_FFFF), mais l'un des deux UTF -16 unités de code qui constituent un point de code. En d'autres termes, UTF-16 est tout autant un encodage à largeur variable que UTF-8. Seule la taille des unités de code diffère. – tchrist

1

transmettaient à UTF-8 octets est très bien.

La longueur en octets peut être obtenu comme

byte[] bytes = a.getBytes(StandardCharsets.UTF_8); 
int size = bytes.length; 

Maintenant vient le problème avec des tailles de gros morceau, qui est normalement compris comme compté en octets. Pour ne pas gérer les demi-caractères ou les paires de demi-caractères asiatiques, il peut être préférable de ne pas utiliser un PrintStream, mais d'envoyer des blocs byte[] sur un OutputStream (binaire).

Sur le côté C++ assurez-vous que sizeof(char) == sizeof(byte) == 1 et vous pouvez contenir dans un std::string une séquence d'octets UTF-8. Vous aurez besoin d'un code supplémentaire pour créer un fichier wstring mais vous pouvez aussi le sauvegarder dans un fichier ou une base de données (UTF-8).

+0

Il n'est pas nécessaire de vérifier 'sizeof (char) == 1' en C++; C++ exige que cela soit vrai. (Bien que techniquement, il est possible qu'un octet ne soit pas de 8 bits, ces jours-ci cela n'arrive que sur des plates-formes embarquées bizarres.) – bames53

+0

@ bames53 merci, j'étais évidemment égaré par un article sur la question de char/byte. –

0

J'ai trouvé une solution à corriger afin que je puisse obtenir la représentation correcte d'une chaîne dans l'application C++.

Merci pour votre aide! J'ai essayé tout ce que tu as dit, mais je n'ai pas réussi à résoudre mon problème, mais ça m'a donné des directions. Cependant, un problème persiste. Je ne peux pas obtenir la même taille d'octet sur le serveur, j'ai donc abandonné et refait ma fonction recv pour analyser les chaînes entrantes au lieu de la taille des paquets. J'ai donc détruit l'ancienne façon de penser. Il y a probablement une solution à ce problème, mais je suis fatigué de cela hehe.

J'ai changé le format en ISO-8859-1, et cela a fonctionné pour moi. J'ai trouvé un fil de discussion sur quelqu'un demandant comment convertir une chaîne Java en Cstring, donc j'ai utilisé sa méthode et cela a fonctionné de manière étonnante. J'ai aussi utilisé une mauvaise classe de sortie dans le client Java. J'ai utilisé PrintWriter et aussi avant PrintStreamer. Il semble qu'ils ne fonctionnent qu'avec du texte, donc je pense qu'il m'a donné de mauvais résultats sur le serveur C++. DataOutputStream était le moyen d'envoyer.

-java client-

public NetworkSender(Socket s) 
{ 
    mSocket = s; 
    mEnc = Charset.forName("ISO-8859-1").newEncoder(); 
} 

public boolean InitSender(){ 
    if(mSocket == null) 
     return false; 

    try { 
     out = new DataOutputStream(mSocket.getOutputStream()); 

    } catch (IOException e) { 
     e.printStackTrace(); 
     return false; 
    } 

    return true; 
} 

public synchronized boolean SendMessage(final String a) { 

    String str_msg = a; 
    str_msg = START_PACKET_INDICATION + a + END_PACKET_INDICATION; 

    byte[] msg = StringEncodeCString(str_msg, false); 

    try { 
     out.write(msg); 
    } catch (IOException e) { 
     e.printStackTrace(); 
     return false; 
    } 

    return true; 
} 

private byte[] StringEncodeCString(String msg, boolean zeroTeminate) 
{ 
    int zero = 0; 

    if(zeroTeminate) 
     zero = 1; 

    int len = msg.length(); 
    byte b[] = new byte[len + zero]; 
    ByteBuffer bbuf = ByteBuffer.wrap(b); 
    mEnc.encode(CharBuffer.wrap(msg), bbuf, true); 

    if(zeroTeminate) 
     b[len] = 0; 

    return b; 
} 

C++ Serveur-

bool NetworkChannel::Receive(std::string& msg) 
{ 
    zReadMutex.lock(); 

    try 
    { 
     int errCode; 
     char *buffer = new char [zMaxChunkSize]; 
     std::size_t start_pos; 
     std::size_t end_pos; 
     std::string startEnd; 

     //Check buffer 
     if (zSaveBufferString != "") 
     { 

      startEnd = GetStartEndIndicatorSubstr(zSaveBufferString, start_pos, end_pos); 

      if (startEnd == "") 
      { 
       //Nothing inside buffer, continue 
      } 

      else if (!EraseStartEnd(startEnd)) 
      { 
       zReadMutex.unlock(); 
       throw NetworkException("Failed to concat message!"); 
      } 
      else 
      { 
       zSaveBufferString.erase(start_pos, end_pos + start_pos); 
       msg = startEnd; 
       zReadMutex.unlock(); 
       return true; 
      } 

     } 

     errCode = recv(zSocket, buffer, zMaxChunkSize, 0); 

     if (errCode == SOCKET_ERROR || errCode == 0) 
     { 
      zReadMutex.unlock(); 
      throw NetworkException("Failed Receiving Packet Size!", WSAGetLastError()); 
     } 

     std::string temp(buffer); 
     temp.resize(errCode); 

     zSaveBufferString += temp; 

     //Find a Start and end subStr to translate messages 
     startEnd = GetStartEndIndicatorSubstr(zSaveBufferString, start_pos, end_pos); 

     if (startEnd == "") 
     { 
      delete[]buffer; 

      zReadMutex.unlock(); 
      return false; 
     } 

     if(!EraseStartEnd(startEnd)) 
     { 
      delete[]buffer; 

      zReadMutex.unlock(); 
      throw NetworkException("Failed to erase startEnd!"); 
     } 

     zSaveBufferString.erase(start_pos, end_pos + start_pos); 

     msg = startEnd; 

     delete [] buffer; 

    } 
    catch(...) 
    { 
     zReadMutex.unlock(); 
     throw; 
    } 

    zReadMutex.unlock(); 
    return true; 
} 
+0

Il n'y avait rien de mal à utiliser les tailles de paquets, vous n'avez tout simplement pas envoyé le bon paquet pour commencer. Dans un protocole binaire, comme dans votre conception d'origine, l'envoi de tailles permet une gestion efficace de la mémoire. Vous êtes maintenant passé à un protocole textuel avec une gestion de la mémoire moins efficace (et incorrecte) et plus de frais généraux. Vous confondez * encore * les longueurs de chaîne UTF-16 et les comptages d'octets ANSI dans votre code Java, car ils * ne * codent pas correctement les caractères non-ASCII. 'int len ​​= msg.length();' est tout simplement faux pour une taille de tampon encodée, vous devez le réparer. –

+0

De toute façon, UTF-8 est préférable à ISO-8859-1. UTF-8 prend en charge le jeu de caractères Unicode * entier * (tous les 1111111 points de code actuels et futurs) tandis que ISO-8859-1 ne prend en charge qu'un très petit sous-ensemble d'Unicode (191 points de code). –

+0

@RemyLebeau Je suis pleinement conscient que ce protocole est moins efficace. Cependant, le serveur est uniquement destiné à avoir un seul socket au maximum, et est exécuté localement. Donc c'est acceptable. Mais pour un jeu cependant, pas de. ;) Thnakx pour votre contribution. – Ediz