2015-04-15 3 views
0

Я работаю над приложением для своего телефона Android, поэтому я могу импортировать sms, читать их и отвечать на sms. Все работало так, как я планировал, когда я программировал сервер и клиент. Если у меня были проблемы, поиск Google дал мне решения, но на этот раз, в первый раз в моей жизни, я прошу вас о помощи.Как я могу отправить строку Java с символами Unicode на C++ через сокет, без странных символов?

Проблема:

Проблема заключается в том, когда клиент (Java) отправляет содержимое SMS, который содержит символы Юникода, такие как "а, а, ö", C++ не может читать их.

Моя программа работает с тем, что сначала отправляет размер пакета, чтобы другой знал о том, насколько большой будет пакет. Так, например, Java рассчитывает, что пакет будет равен 121 байт и отправит его на сервер. Но если пакет содержит несколько символов, отличных от ANSI, C++ не получит 121 байт, а 123 байта, а символы, отличные от ANSI, станут странными.

Я весь день пробовал без ответов. Я пробовал wchar_t в C++, я попытался настроить все на Java, которое будет отправлено с использованием UTF-8, я отлаживал часами, чтобы воссоздать проблему и попробовать разные вещи, но безуспешно!

Так что же здесь происходит? Как я могу получить текст с Java на C++ в правильном размере и представлении, как в Java? Пакеты без символов Unicode отлично работают.

Спасибо, ребята! Немного усталый атм, надеюсь, что я ничего не пропустил. Код может быть немного грязным, это пока еще прототип.

P: S, Это TCP-соединение.

-Server C++ RECV FUNCTION-

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; 
} 

- клиент Java Функция отправки -

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

Вероятно, основная причина вашего замешательства: Гнезда не посылают символы, они посылают байты. – immibis

+0

Я знаю, что они не отправляют символы, но перед отправкой они преобразуются в сырые байты. Я не вижу вашу точку зрения, – Ediz

+0

Вы отправляете 'размер', не соответствует фактическому количеству отправляемых байтов.«Размер» выражается в терминах символов UTF-16, но «сообщение» отправляется вместо UTF-8. Они кодируют не-ASCII-символы по-разному, поэтому вам нужно преобразовать 'сообщение' в UTF-8 перед отправкой его длины. –

ответ

2

Так, например, Java вычисляющий пакет будет 121 байт и отправляет его сервер.

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

Этот код не совпадает с описанием; .length() на строке Java не учитывает байты. Вы отправляете число элементов Java char в строке. Java char - это два байта.

 out.print(message); 

message является Java String. Вам нужно посмотреть, как этот String преобразуется в байты, которые должны быть отправлены по сетевому соединению. Нет никакой гарантии, что это преобразование создает столько же байтов, сколько в строке было Java char. В частности, если строка преобразуется в UTF-8, тогда некоторые отдельные значения Java char будут преобразованы в два или три байта.

Вам необходимо выполнить преобразование перед отправкой данных, чтобы вы могли подсчитать фактическое количество отправляемых байтов.


На стороне С ++ std::string представляет собой последовательность из С ++ char элементов, которые не являются такими же, как Java-char с. C++ char - это один байт. В вашем коде std::string будут содержать те же данные, которые вы читаете в сети; Если клиент отправляет данные UTF-8, то std::string содержит данные UTF-8. Чтобы отобразить строку, вам понадобится использовать API, который обрабатывает все, что кодируется, или преобразовать его. В противном случае это будет выглядеть, некоторые из символов «странные».


Вот разумное начало на изучении некоторых вещей, которые вы должны знать:

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

+0

Подробнее для преимущества OP, чем для вашего, как я уверен, вы знаете, Java 'char' не содержит логическую кодовую точку Unicode (число от 0 .. 0x10_FFFF), но одно из двух UTF -16 единиц кода, составляющих кодовую точку. Другими словами, UTF-16 является такой же кодировкой переменной ширины, как UTF-8. Различается только размер блоков кода. – tchrist

1

Передающая в UTF-8 байт в порядке.

Длина в байтах может быть получен, как

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

Теперь возникает проблема с размерами порций, которые обычно понимается как подсчитываются в байтах.

Чтобы не обрабатывать половинные символы или пары азиатских половин пар, возможно, лучше не использовать PrintStream, а отправьте byte[] куски, через (двоичный) OutputStream.

На стороне C++ убедитесь, что sizeof(char) == sizeof(byte) == 1, и вы можете сохранить в std::string последовательность байтов UTF-8. Вам понадобится дополнительный код для создания wstring, но он может также сохранить его в файле (UTF-8) или базе данных.

+0

Нет необходимости проверять 'sizeof (char) == 1' в C++; C++ требует, чтобы это было правдой. (Хотя технически возможно, что байт не будет 8 бит, в наши дни это происходит только на странных встроенных платформах.) – bames53

+0

@ bames53 спасибо, я, очевидно, был сбит с пути статьей о вопросе char/byte. –

0

Я нашел решение для исправления, чтобы получить правильное представление строки в приложении C++.

Thanx для вашей помощи! Я попробовал все, что сказал, но не смог решить мою проблему, но он дал мне указания. Однако одна проблема остается. Я не могу получить тот же размер байта на сервере, поэтому я сдался и переделал свою функцию recv для анализа входящих строк вместо размера пакета. Так что я извратил старый образ мышления. Существует также решение этой проблемы, но я устал от этого хе-хе.

Я изменил формат на ISO-8859-1, и это сработало для меня. Я нашел форум, который кто-то спрашивал, как преобразовать строку Java в Cstring, поэтому я использовал его метод, и он работал потрясающе. Также я использовал неправильный выходной класс в Java-клиенте. Я использовал PrintWriter, а также до этого PrintStreamer. Кажется, они работают только с текстом, поэтому я думаю, что это дало мне неправильные результаты на сервере C++. DataOutputStream - это способ отправить сообщение.

-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++ Сервер-

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

Не было ничего плохого в использовании размеров пакетов, вы просто не отправляли правильный пакет для начала. В двоичном протоколе, как и в вашем оригинальном дизайне, размер отправки позволяет эффективно управлять памятью. Теперь вы переключились на текстовый протокол с менее эффективным (и некорректным) управлением памятью и большими накладными расходами. Вы * все еще путаете строки длины UTF-16 и количество байтов ANSI в вашем Java-коде, так как это * все еще * не кодирует символы, отличные от ASCII. 'int len ​​= msg.length();' просто неправильно для размера закодированного буфера, вам нужно его исправить. –

+0

В любом случае UTF-8 предпочтительнее, чем ISO-8859-1. UTF-8 поддерживает кодировку * whole * Unicode (все текущие и будущие кодовые точки 1,114,111), тогда как ISO-8859-1 поддерживает только очень небольшое подмножество Unicode (191 кодовых точек). –

+0

@RemyLebeau Я полностью осознаю, что этот протокол менее эффективен. Однако сервер предназначен только для того, чтобы иметь максимум один сокет и запускаться локально. Так что это приемлемо. Но для игры, однако, нет. ;) Thnakx для вашего ввода. – Ediz