ترتبط المشكلة التي تواجهها بطبيعة تدفق TCP.
حقيقة أنك أرسلت 100 بايت (على سبيل المثال) من الخادم لا يعني أنك ستقرأ 100 بايت في العميل في المرة الأولى التي تقرأ فيها. ربما تصل البايتات المرسلة من الخادم إلى العديد من قطاعات TCP إلى العميل.
تحتاج إلى تنفيذ حلقة تقرأ فيها حتى يتم استلام الرسالة بأكملها. اسمحوا لي أن أقدم مثالا مع DataInputStream
بدلاً من BufferedinputStream
. شيء بسيط للغاية لإعطائك مجرد مثال.
لنفترض أنك تعرف مسبقًا أن الخادم سترسل 100 بايت من البيانات.
في العميل ، تحتاج إلى الكتابة:
byte[] messageByte = new byte[1000];
boolean end = false;
String dataString = "";
try
{
DataInputStream in = new DataInputStream(clientSocket.getInputStream());
while(!end)
{
int bytesRead = in.read(messageByte);
dataString += new String(messageByte, 0, bytesRead);
if (dataString.length == 100)
{
end = true;
}
}
System.out.println("MESSAGE: " + dataString);
}
catch (Exception e)
{
e.printStackTrace();
}
الآن ، عادةً ما يكون حجم البيانات المرسلة بواسطة عقدة واحدة (الخادم هنا) غير معروف مسبقًا. ثم تحتاج إلى تحديد بروتوكولك الصغير للاتصال بين الخادم والعميل (أو أي عقدتين) يتواصل مع TCP.
الأكثر شيوعًا وبسيطًا هو تحديد TLV: النوع والطول والقيمة. لذلك تحدد أن كل رسالة مرسلة من خادم نموذج إلى العميل تأتي مع:
- 1 بايت يشير إلى النوع (على سبيل المثال ، يمكن أن يكون أيضًا 2 أو أي شيء آخر).
- 1 بايت (أو أي شيء آخر) لطول الرسالة
- بايت N للقيمة (يشار إلى N في الطول).
لذلك تعلم أنه يتعين عليك الحصول على ما لا يقل عن 2 بايت ومع البايت الثاني ، فأنت تعرف عدد البايتات التالية التي تحتاج إلى قراءتها.
هذا مجرد اقتراح لبروتوكول محتمل. يمكنك أيضا التخلص من "النوع".
لذلك سيكون شيئًا مثل:
byte[] messageByte = new byte[1000];
boolean end = false;
String dataString = "";
try
{
DataInputStream in = new DataInputStream(clientSocket.getInputStream());
int bytesRead = 0;
messageByte[0] = in.readByte();
messageByte[1] = in.readByte();
int bytesToRead = messageByte[1];
while(!end)
{
bytesRead = in.read(messageByte);
dataString += new String(messageByte, 0, bytesRead);
if (dataString.length == bytesToRead )
{
end = true;
}
}
System.out.println("MESSAGE: " + dataString);
}
catch (Exception e)
{
e.printStackTrace();
}
الكود التالي يجمع ويبدو أفضل. يفترض أن أول بايتان يوفران الطول يصل إلى تنسيق ثنائي ، في الشبكة الإنديرية (Big Endian). لا تركز على أنواع الترميز المختلفة لبقية الرسالة.
import java.nio.ByteBuffer;
import java.io.DataInputStream;
import java.net.ServerSocket;
import java.net.Socket;
class Test
{
public static void main(String[] args)
{
byte[] messageByte = new byte[1000];
boolean end = false;
String dataString = "";
try
{
Socket clientSocket;
ServerSocket server;
server = new ServerSocket(30501, 100);
clientSocket = server.accept();
DataInputStream in = new DataInputStream(clientSocket.getInputStream());
int bytesRead = 0;
messageByte[0] = in.readByte();
messageByte[1] = in.readByte();
ByteBuffer byteBuffer = ByteBuffer.wrap(messageByte, 0, 2);
int bytesToRead = byteBuffer.getShort();
System.out.println("About to read " + bytesToRead + " octets");
//The following code shows in detail how to read from a TCP socket
while(!end)
{
bytesRead = in.read(messageByte);
dataString += new String(messageByte, 0, bytesRead);
if (dataString.length() == bytesToRead )
{
end = true;
}
}
//All the code in the loop can be replaced by these two lines
//in.readFully(messageByte, 0, bytesToRead);
//dataString = new String(messageByte, 0, bytesToRead);
System.out.println("MESSAGE: " + dataString);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}