使用套接字发送和接收数据

35

我正在使用socket连接我的Android应用程序(客户端)和Java后端服务器。从客户端发送数据时,我想一次传输两个变量:

1) 消息类型(由用户通过界面定义)

2) 消息语言(由用户通过界面定义)

我该如何发送这些数据,以便服务器将它们解释为独立的实体?

在服务器端读取数据并作出适当的判断后,我希望能向客户端返回单个消息。(我认为这部分没有问题)

所以我的两个问题是:如何确保发送的两个字符串(客户端到服务器)在客户端端是唯一的,以及如何在服务器端将这两个字符串分开。(我考虑使用字符串数组,但无法确定是否可行或合适。)

我打算贴一些代码,但不确定它会有多大帮助。

3个回答

73
我假设您正在使用TCP套接字进行客户端-服务器交互? 一种将不同类型的数据发送到服务器并使其能够区分这些数据的方法是将第一个字节(如果您有超过256种消息,则可以使用更多字节)作为某种标识符。 如果第一个字节是1,则它是A消息,如果是2,则是B消息。 将此通过套接字发送的一种简单方法是使用:

客户端:

Socket socket = ...; // Create and connect the socket
DataOutputStream dOut = new DataOutputStream(socket.getOutputStream());

// Send first message
dOut.writeByte(1);
dOut.writeUTF("This is the first type of message.");
dOut.flush(); // Send off the data

// Send the second message
dOut.writeByte(2);
dOut.writeUTF("This is the second type of message.");
dOut.flush(); // Send off the data

// Send the third message
dOut.writeByte(3);
dOut.writeUTF("This is the third type of message (Part 1).");
dOut.writeUTF("This is the third type of message (Part 2).");
dOut.flush(); // Send off the data

// Send the exit message
dOut.writeByte(-1);
dOut.flush();

dOut.close();

服务器:

Socket socket = ... // Set up receive socket
DataInputStream dIn = new DataInputStream(socket.getInputStream());

boolean done = false;
while(!done) {
  byte messageType = dIn.readByte();

  switch(messageType)
  {
  case 1: // Type A
    System.out.println("Message A: " + dIn.readUTF());
    break;
  case 2: // Type B
    System.out.println("Message B: " + dIn.readUTF());
    break;
  case 3: // Type C
    System.out.println("Message C [1]: " + dIn.readUTF());
    System.out.println("Message C [2]: " + dIn.readUTF());
    break;
  default:
    done = true;
  }
}

dIn.close();

显然,你可以发送各种类型的数据,而不仅限于字节和字符串 (UTF)。

请注意,writeUTF 写入的是改良后的 UTF-8 格式,前面加上一个长度指示符,一个无符号的二字节编码整数,给出了 2^16 - 1 = 65535 字节来发送。这使得 readUTF 能够找到编码字符串的结束位置。如果你自己决定记录结构,那么你应该确保记录的结束和类型要么被知道,要么是可检测到的。


3
@user671430,您是否需要其他信息来标记此问题已回答? - Nick Banks
嗨,玩家,有一个小问题。在服务器端,我正在使用ServerSocket而不仅仅是Socket。在这种情况下,DataInputStream似乎不被支持。 - London Student
2
你的客户端是如何连接的?我猜你在某个地方执行了socket.accept()来等待客户端连接?该函数返回一个Socket,你可以调用getInputStream和getOutputStream方法。 - Nick Banks

5
最简单的方法是使用ObjectInput/OutputStreams包装套接字并发送序列化的Java对象。您可以创建包含相关数据的类,然后不必担心处理二进制协议的细节问题。只需确保在写入每个对象“消息”后刷新对象流即可。

尽管这条消息是很久以前写的,但我有一个问题,如何区分发送字符串消息还是元素列表?我发布了我的[问题](http://stackoverflow.com/questions/25699232/java-chat-sending-users-list-from-server-to-clients-and-update-it) - Nikolas
1
这是一个糟糕的想法,你应该避免使用对象序列化来创建低级协议。最好使用DataInputStream / DataOutputStream 来编写整数。 - Maarten Bodewes
1
@MaartenBodewes - 您的观点当然值得尊重。就像生活中的所有事情一样,编程也是一系列的权衡取舍。您可以通过交换方便性(Java序列化)来获得额外的代码/复杂性(手写序列化协议),或者通过学习曲线更附加库(基于JSON/XML的序列化库)。这完全取决于您想要投入时间和精力的地方。 - jtahlborn
1
我们正在谈论两个相当不同的Java环境,它们可能没有太多同步。因此,您可能需要使用不同类的不同版本进行工作。如果您需要更新它们,那么您必须确保所有客户端同时切换或使用版本号等方式解决问题。如果您想在服务器或客户端上切换运行时,则可能需要重新设计协议。Java序列化确实有其优点,但我主要在严格控制的系统(例如在同一服务器集群中的服务器之间)上使用它。 - Maarten Bodewes
@MaartenBodewes 实际上,Java序列化具有大量内置支持来迁移类(它非常强大和灵活)。您可以使用Java序列化确保多个客户端版本的支持。使用DataInput和DataOutput编写自定义序列化协议并不能神奇地解决您提到的任何问题。 - jtahlborn

2
    //Client

    import java.io.*;
    import java.net.*;

    public class Client {
        public static void main(String[] args) {

        String hostname = "localhost";
        int port = 6789;

        // declaration section:
        // clientSocket: our client socket
        // os: output stream
        // is: input stream

            Socket clientSocket = null;  
            DataOutputStream os = null;
            BufferedReader is = null;

        // Initialization section:
        // Try to open a socket on the given port
        // Try to open input and output streams

            try {
                clientSocket = new Socket(hostname, port);
                os = new DataOutputStream(clientSocket.getOutputStream());
                is = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            } catch (UnknownHostException e) {
                System.err.println("Don't know about host: " + hostname);
            } catch (IOException e) {
                System.err.println("Couldn't get I/O for the connection to: " + hostname);
            }

        // If everything has been initialized then we want to write some data
        // to the socket we have opened a connection to on the given port

        if (clientSocket == null || os == null || is == null) {
            System.err.println( "Something is wrong. One variable is null." );
            return;
        }

        try {
            while ( true ) {
            System.out.print( "Enter an integer (0 to stop connection, -1 to stop server): " );
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            String keyboardInput = br.readLine();
            os.writeBytes( keyboardInput + "\n" );

            int n = Integer.parseInt( keyboardInput );
            if ( n == 0 || n == -1 ) {
                break;
            }

            String responseLine = is.readLine();
            System.out.println("Server returns its square as: " + responseLine);
            }

            // clean up:
            // close the output stream
            // close the input stream
            // close the socket

            os.close();
            is.close();
            clientSocket.close();   
        } catch (UnknownHostException e) {
            System.err.println("Trying to connect to unknown host: " + e);
        } catch (IOException e) {
            System.err.println("IOException:  " + e);
        }
        }           
    }





//Server




import java.io.*;
import java.net.*;

public class Server1 {
    public static void main(String args[]) {
    int port = 6789;
    Server1 server = new Server1( port );
    server.startServer();
    }

    // declare a server socket and a client socket for the server

    ServerSocket echoServer = null;
    Socket clientSocket = null;
    int port;

    public Server1( int port ) {
    this.port = port;
    }

    public void stopServer() {
    System.out.println( "Server cleaning up." );
    System.exit(0);
    }

    public void startServer() {
    // Try to open a server socket on the given port
    // Note that we can't choose a port less than 1024 if we are not
    // privileged users (root)

        try {
        echoServer = new ServerSocket(port);
        }
        catch (IOException e) {
        System.out.println(e);
        }   

    System.out.println( "Waiting for connections. Only one connection is allowed." );

    // Create a socket object from the ServerSocket to listen and accept connections.
    // Use Server1Connection to process the connection.

    while ( true ) {
        try {
        clientSocket = echoServer.accept();
        Server1Connection oneconnection = new Server1Connection(clientSocket, this);
        oneconnection.run();
        }   
        catch (IOException e) {
        System.out.println(e);
        }
    }
    }
}

class Server1Connection {
    BufferedReader is;
    PrintStream os;
    Socket clientSocket;
    Server1 server;

    public Server1Connection(Socket clientSocket, Server1 server) {
    this.clientSocket = clientSocket;
    this.server = server;
    System.out.println( "Connection established with: " + clientSocket );
    try {
        is = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        os = new PrintStream(clientSocket.getOutputStream());
    } catch (IOException e) {
        System.out.println(e);
    }
    }

    public void run() {
        String line;
    try {
        boolean serverStop = false;

            while (true) {
                line = is.readLine();
        System.out.println( "Received " + line );
                int n = Integer.parseInt(line);
        if ( n == -1 ) {
            serverStop = true;
            break;
        }
        if ( n == 0 ) break;
                os.println("" + n*n ); 
            }

        System.out.println( "Connection closed." );
            is.close();
            os.close();
            clientSocket.close();

        if ( serverStop ) server.stopServer();
    } catch (IOException e) {
        System.out.println(e);
    }
    }
}

1
仅编写代码注释通常不受欢迎,请在代码上方解释您的操作;代码应用于解释答案,而不应成为答案本身。 - Maarten Bodewes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接