【问题标题】:Can you write to a sockets input and output stream at the same time?您可以同时写入套接字输入和输出流吗?
【发布时间】:2013-01-07 19:06:43
【问题描述】:

我有一个 Java 应用程序,它是 Voip。我正在使用一个套接字通过线程同时发送和接收信息。代码如下所示..

Socket clientSocket = sockList.accept();
OutputStream outSock = clientSocket.getOutputStream();
InputStream inSock = clientSocket.getInputStream();
new Thread( new Capture(outSock)).start();
new Thread( new PlayAudio(inSock)).start();
outSock.close();
clientSocket.close();

我发现的问题是,当我写入输出流时,它会在第一次写入时阻塞。我发送的字节不多。下面是我写的代码。

private class Capture implements Runnable{

    private OutputStream out;
    public Capture(OutputStream out){
        this.out = out;
    }
    @Override
    public void run() {
        try{
            int numBytesRead;
            TargetDataLine outLine = getMic();
            outLine.open();
            outLine.start();

            byte[] data = new byte[outLine.getBufferSize() / 5];
            byte[] test = {0x1,0x1,0x1};

            while(true) {       
                //numBytesRead =  outLine.read(data, 0, data.length);
                //System.out.println(numBytesRead);
                out.write(test, 0, test.length);
                out.flush();
                /*if(numBytesRead > 0){
                    out.write(data, 0, data.length);
                    System.out.println("C");
                }*/
            }
        }catch(Exception ex){}
    }
}

另一个读取声音代码的线程是……

private class PlayAudio implements Runnable{

    private InputStream in;
    public PlayAudio(InputStream in){
        this.in = in;
    }
    @Override
    public void run() {
        int write;
        try{
        SourceDataLine inLine = getSpeaker();
        inLine.open();
        inLine.start();
        byte[] data = new byte[inLine.getBufferSize()];
        byte[] test = new byte[3];
        while(true){
            System.out.println(1);
            //write = in.read(data, 0, data.length);
            in.read(test, 0 , test.length);
            System.out.println(2);
            /*if(write > 0){
                inLine.write(data, 0, write);
                System.out.println(3);
                System.out.println(write);
            }*/
        }
        } catch(Exception ex){}
    }

}

我已经注释了大部分实际代码,因为我只是想让它工作。我的写入功能在第一次写入时无限期阻塞。这可能是我的线程的问题吗?我唯一的想法是输出和输入流共享我的套接字对象,这可能会导致死锁或其他东西。请让我知道发生了什么。

【问题讨论】:

    标签: java multithreading sockets thread-safety


    【解决方案1】:

    是的,您可以同时写入套接字输入和输出流。

    来自do-java-sockets-support-full-duplex

    由于输入流和输出流是 Socket 中的独立对象,您唯一可能关心的是,如果您有 2 个线程尝试读取或写入会发生什么(两个线程,相同的输入/输出流)同时? InputStream/OutputStream 类的读/写方法不同步。但是,如果您使用 InputStream/OutputStream 的子类,则您调用的读/写方法可能是同步的。您可以检查 javadoc 以了解您正在调用的任何类/方法,并很快找到。

    【讨论】:

    • 我认为既然输入和输出(两个独立的流)是独立的对象,同时对它们进行操作应该没关系。所以你说我还是应该担心同步?
    【解决方案2】:

    是的,您可以在读取时在套接字上写入,但您必须在独立线程中读取套接字。我正在使用这个概念。这里的例子是(仔细阅读它也支持多个客户端):

    public class TeacherServerSocket {
    
    private Logger logger = Logger.getLogger(TeacherServerSocket.class);
    public static Map<String, TeacherServerThread> connectedTeacher = new HashMap<String, TeacherServerThread>();
    ServerSocket serverSocket;;
    
    @Override
    public void run() {
        // starting teacher server socket
        this.serverSocket = startServer();
        // if unable to to start then serverSocket would have null value
        if (null != this.serverSocket) {
    
            while (true) {
                 //listening to client for infinite time
                Socket socket = listenToClient();
                if (null != socket) {
    
                    TeacherServerThread teacherServerThread = new TeacherServerThread(socket);
                    Thread thread = new Thread(teacherServerThread);
                    thread.start();
    
                    //putting teacher ip address and teacher object into map
                    connectedTeacher.put(teacherServerThread.getTeacherIp(),teacherServerThread);
                    System.out.println("INFO: Teacher is connected with address "+ teacherServerThread.getTeacherIp());
    
                }
    
            }
    
    
        }
    
    }
    
    @Override
    public ServerSocket startServer() {
         //port number on which teacher server will  be run.
         int port=12345;
    
        try {
            // throw an exception if unable to bind at given port
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("Teacher server socket started on port no :"+port);
            return serverSocket;
    
        } catch (IOException e) {
    
            logger.error("Unable to start Teacher Server socket");
            e.printStackTrace();
    
        }
    
        return null;
    
    }
    
    @Override
    
    public Socket listenToClient() {
    
        if (this.serverSocket != null) {
    
            try {
                // throw an exception is unable to open socket
                Socket socket = this.serverSocket.accept();
                return socket;
    
            } catch (IOException e) {
    
                logger.error("Unable to open socket for teacher");
                e.printStackTrace();
    
            }
        } 
        else {
    
            logger.error("TeacherServerSocket has got null value please restart the server");
    
        }
    
        return null;
    }
    
    
    
    
    
    @Override
    public Map getConnectedDevicesMap() {
    
    return  TeacherServerSocket.connectedTeacher;
    
    }
    
    /**
     * This method will send message to connected teacher which comes form  student
     * @author rajeev
     * @param   message, which comes form student
     * @return  void
     *   * */
    @Override
    public void publishMessageToClient(String message) {
        if(TeacherServerSocket.connectedTeacher.size()>0){
            System.out.println("Total Connected Teacher: "+TeacherServerSocket.connectedTeacher.size());
            for (String teacherIp : TeacherServerSocket.connectedTeacher.keySet()) {
    
                TeacherServerThread teacherServerThread=TeacherServerSocket.connectedTeacher.get(teacherIp);
                teacherServerThread.publishMessageToTeacher(message);
    
            }
        }
    
    }
    
    
    
    @Override
    public void stopServer() {
    
        if (this.serverSocket != null) {
    
            try {
    
                serverSocket.close();
    
            } catch (Exception e) {
    
                e.printStackTrace();
    
            }
        }
    
    }
    
    
    }
    

    在一个独立的线程中读取多个客户端:

    public class TeacherServerThread implements Runnable {
    
    
    Logger logger=Logger.getLogger(TeacherServerThread.class);
    Socket socket;
    String teacherIp;
    
    public TeacherServerThread(Socket socket) {
     this.socket=socket;
     this.teacherIp=socket.getInetAddress().toString();
    }
    
    
    @Override
    public void run() {
        //starting reading
        ReadFromTeacherAndPublishToStudent messageReader=new ReadFromTeacherAndPublishToStudent();
        Thread thread=new Thread(messageReader);
        thread.start();
    }
    
    
    
    
    
    private class ReadFromTeacherAndPublishToStudent implements Runnable {
    
        @Override
        public void run() {
            String message=null;
            try {
                BufferedReader readTeacherData=new BufferedReader(new InputStreamReader(socket.getInputStream()));
    
                StudentServerSocket studentServerSocket=new StudentServerSocket();
                //sending message to student which is read by teacher
                while((message=readTeacherData.readLine())!=null){
                    //System.out.println("Message found : "+message);
                   //  studentServerSocket.publishMessageToClient(message); // do more stuff here 
    
                }
                // if message has null value then it mean socket is disconnected.
          System.out.println("INFO: Teacher with IP address : "+teacherIp+" is disconnected");
          TeacherServerScoket.connectedTeacher.remove(getTeacherIp());
            if(null!=socket){
                socket.close();
            }
    
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
    
    
    
    }
    
    } //class
    
    
    
    public void publishMessageToTeacher(String message){
    
        if(this.socket!=null){
    
            try {
    
            PrintWriter writeMessageToTeacher=new PrintWriter(this.socket.getOutputStream());
            writeMessageToTeacher.println(message);
            writeMessageToTeacher.flush();
            System.out.println(" Message published to teacher"+message);
            }catch(Exception e){
            logger.error(e.toString());
            logger.error("Exception In writing data to teacher");
    
            }
    
    
        }else {
            logger.error("Unable to publish message to teacher .Socket has Null value in publishMessageToTeacher");     
            System.out.println("ERROR: socket has null value can not publish to teacher");
        }
    
    
    
    }
    
    public String getTeacherIp()
    {
        return teacherIp;
    
    }
    }
    

    根据您的要求更改代码......

    【讨论】:

    • 我正在使用线程进行读写。一个线程读取,另一个线程写入。我也不能使用 PrintWriter 因为我必须发送原始字节数组,这就是我的音频数据所在。
    【解决方案3】:

    我的 write() 似乎阻塞的原因是我愚蠢地关闭了 Socket() 而我的输入流没有意识到这一点。因此,永远不会发送任何数据。代表我犯了愚蠢的错误。

    【讨论】: