我做了一个类似QQ的聊天项目。所有的信息发送都是用xml发送的,用socket建立的网络通道。我没有把xml写成文件,而是把它写在物理内存中。现在的问题是客户端发送一条信息就必须shutdownoutput,这时候socket的输出端被关闭了,如果下次再发送它就报错--输出流是断开的。如果重新new一个socket就不符合实际情况--一个客户只能有一个端口号。如果不用整个程序就死机。我试过用close,那样信息就无法回过来。
  请问高手们是否有办法解决这个问题,或者用其他办法解决这个问题。
                                                                     非常感谢!

解决方案 »

  1.   

    为什么客户端发送一条信息就必须shutdownoutput呢??
    从设计上找原因,这肯定是可以避免的
      

  2.   

    当用户填写好登陆信息按下确定按钮后,所有的信息都将写入XML,然后通过XMLWriter将信息发给服务器,服务器始终在侦听有无客户进入,如有它将启动run函数,run函数中有个typeDemo()方法,这个方法用来判断是执行LandConn()方法还是其他方法,如果是LandConn()就解析数据,是用dom4j进行解析的,解析出来的数据在与数据库中的数据进行比较,有这客户就给服务器返回真,服务器就写一个XML文件将真的数据发送给客户端,客户端也用dom4j解析数据,如果是真就打开聊天界面。在打开聊天界面时我将登陆界面用的socket和XMLWriter传给了聊天界面。这就是我的大致思想。
           请给出意见,谢谢!!!
      

  3.   

    其实问题本没有那么复杂,你为什么一定要调用 shutdownOutput() 呢?是不是如果不调用这个,对方就收不到数据?首先,你要在发送端做 flush(),否则的话,数据不会真的发送出去;其次,在接收端,不要指望 read() 返回 -1 才认为发送结束,才开始处理收到的数据。作为“流数据”的处理,你必需自己定义数据边界,也就是说,你的接收程序随时都应该明确知道“当前的数据片断是否传输完毕”、“是否要继续接收下面的数据”。
      

  4.   

    看来没我什么事了啊:)
    注意XML大小问题,如果小的话直接readUTF()和writeUTF()就方便多了,如果数据量很庞大的话考虑是否需要缓存,但是我想学生做的话应该很小
      

  5.   

    很感谢您对我的关照。我用标记刚才试了一下,还是老样子。可能我的结束标记用错了吧。我用的结束标记是xml的根作为结束标记</Class>,当服务器的BufferedReader.readline进行循环读数据时就报错--Exception in thread "Thread-3" java.lang.OutOfMemoryError: Java heap space 我不懂这个错误的意思。你如何看?
      

  6.   

    那这个问题如何解决呢?如果将xml写成一个字符串形式,还是要用shutdownoutput,否则就死机
      

  7.   

    > 当服务器的BufferedReader.readline进行循环读数据时就报错把这个循环附近的代码贴上来吧,大家好给看看是怎么回事。
      

  8.   

    //此类为客户端类
    package com.java.client;
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.io.*;
    import java.net.*;
    import java.util.*;
    import java.util.List;import org.dom4j.*;
    import org.dom4j.io.*;import org.dom4j.Element;
    import org.dom4j.io.OutputFormat;
    import org.dom4j.io.XMLWriter;import com.java.server.Node;
    public class ClientLand extends JFrame implements ActionListener{

    private JPanel LandPan1 = new JPanel();
    private JPanel LandPan2 = new JPanel();
    private JPanel jp[] = null;
    private JLabel Name = new JLabel("UserName:");
    private JLabel Password = new JLabel("PassWord:");
    private JLabel AccountNumber = new JLabel("AccountNumber:");
    public  JTextField JName = new JTextField(10);
    public  JTextField JPassword = new JTextField(10);
    public JTextField JAccountNumber = new JTextField(10);
    public JButton JLand = new JButton("Land");
    public JButton JEnroll = new JButton("Enroll");
    public JFrame frame;

    Node client = new Node();

    public ClientLand(){

    Container c = this.getContentPane();
    frame = this;

    c.setLayout(new BorderLayout());
    c.add(LandPan1, BorderLayout.CENTER);
    c.add(LandPan2, BorderLayout.SOUTH);

    LandPan1.setLayout(new GridLayout(3, 2));
    jp = new JPanel[6];
    for(int i=0; i<jp.length; i++){
    jp[i] = new JPanel();
    LandPan1.add(jp[i]);
    }
    jp[0].add(Name);
    jp[1].add(JName);
    jp[2].add(AccountNumber);
    jp[3].add(JAccountNumber);
    jp[4].add(Password);
    jp[5].add(JPassword);

    LandPan2.setLayout(new FlowLayout());
    LandPan2.add(JLand);
    LandPan2.add(JEnroll);

    JLand.addActionListener(this);
    JEnroll.addActionListener(this);
    }

    public void actionPerformed(ActionEvent a){

    if(a.getSource()==JLand){
    this.LandConn();
    }
    else{
    ClientEnroll ec = new ClientEnroll();
    ec.setSize(300,300);
    ec.setLocation(400,400);
    ec.setResizable(false);
    ec.show();
     
    }
    }

    @SuppressWarnings("unchecked")
    private String ClientIP = null;
    public void LandConn(){
    String Lflag = "1";//登陆成功设置数据库的flag为1
    try{
    ClientIP= InetAddress.getLocalHost().toString();
    String ClientName = JName.getText();
    String ClientAccountNumber = JAccountNumber.getText();
    String ClientPassWord = JPassword.getText();

    //发送信息
    client.socket = new Socket("localhost",9999);

    Document doc = DocumentHelper.createDocument();
    Element root = doc.addElement("Class");
    Element type = root.addElement("Types");
    Element classes = root.addElement("Classes");
    Element function = classes.addElement("FunctionLanding");
    type.addAttribute("type","String").addText("LandConn");
    classes.addAttribute("name","SQLParse");
    function.addAttribute("name","LandDemo");
    function.addElement("IPparam").addAttribute("type","String").addText(ClientIP);
    function.addElement("LNparam").addAttribute("type","String").addText(ClientName);
    function.addElement("ANparam").addAttribute("type","String").addText(ClientAccountNumber);
    function.addElement("PWparam").addAttribute("type","String").addText(ClientPassWord);
    function.addElement("Fparam").addAttribute("type","String").addText(Lflag);

    //是否有缩进
    String indent = "   ";
    //是否产生新行(即一行一个元素)
    boolean newline = true;
    XMLWriter writer = new XMLWriter(client.socket.getOutputStream(),new OutputFormat(indent,newline,"gb2312"));
    writer.write(doc);
    writer.flush();

    client.socket.shutdownOutput();//这就是出问题的地方

    //接收信息
    BufferedReader br = new BufferedReader(new InputStreamReader(client.socket.getInputStream()));
    StringBuffer sb = new StringBuffer();
    String line = br.readLine();
    while((line=br.readLine())!=null){
    sb.append(line);
    } Document doc1 = DocumentHelper.parseText(sb.toString());
    Element root1 = doc1.getRootElement();
    List list = root1.selectNodes("//Class/incept");
    ArrayList al = new ArrayList();
    for(Iterator it=list.iterator();it.hasNext();){//得到真假值
    Element el = (Element)it.next();
    al.add(el.getText());
    }
    if(line.toString().equals("true")){
    //打开聊天窗口
    QQCoze qc = new QQCoze(JName.getText(),JAccountNumber.getText(),client.socket);
    qc.setSize(600,300);
    qc.setLocation(300,300);
    qc.setVisible(true);
    frame.dispose();
    }
    else{
    //提示用户输入有误
    JOptionPane.showMessageDialog(null,"输入有误,请重新填写!","警告",JOptionPane.WARNING_MESSAGE);
    }

    }
    catch(Exception e){e.printStackTrace();}
    }

    public static void main(String[] args) {
    ClientLand cd = new ClientLand();
    cd.setSize(300,200);
    cd.setLocation(400,400);
    cd.setResizable(false);
    cd.show();
    }}
      

  9.   

    //此类为服务器启动类
    package com.java.server;
    import java.io.*;
    import java.net.*;import com.java.client.ClientSocket;public class ServerDemo {
    private ServerSocket ss = null;
    Node client = new Node();//这个类中有socket,还有其他的用来绑定客户
    public ServerDemo(int port){
    try{
    ss = new ServerSocket(port);
    }
    catch(Exception e){e.printStackTrace();}
    }
    private void Start(){//服务器侦听
    while(true){
    try{
    client.socket = ss.accept();
    new Thread(new ConnectionDemo(client.socket)).start();
    }
    catch(Exception e){e.printStackTrace();}
    }
    }
    public static void main(String[] args) {
    ServerDemo sd = new ServerDemo(9999);
    try{
    sd.Start();
    }
    catch(Exception e){e.printStackTrace();}
    }}
      

  10.   

    //此类为服务器执行类
    package com.java.server;import java.io.*;
    import java.net.Socket;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Vector;import org.dom4j.Attribute;
    import org.dom4j.Document;
    import org.dom4j.DocumentHelper;
    import org.dom4j.Element;
    import org.dom4j.io.OutputFormat;
    import org.dom4j.io.XMLWriter;import com.java.client.ClientLand;public class ConnectionDemo extends Thread {
    Socket clientSo = null;

    BufferedReader br = null;

    StringBuffer sb = null;

    SQLParse sp = new SQLParse(); ClientLand cl = new ClientLand();//此类为登陆类 public ConnectionDemo(Socket so) {
    this.clientSo = so;
    } public void run() {
    this.Type();
    } @SuppressWarnings("unchecked")
    private void Type() {
    try {
    br = new BufferedReader(new InputStreamReader(clientSo.getInputStream()));
    sb = new StringBuffer();
    String line = null;
    while ((line=br.readLine())!=null) {//这里null可以改成
    </Class>,但会出现我昨天提到的问题
    sb.append(line);
    }
    Document doc = DocumentHelper.parseText(sb.toString());
    Element root = doc.getRootElement();
    List list = root.selectNodes("//Class/Types");
    ArrayList al = new ArrayList();
    for (Iterator it = list.iterator(); it.hasNext();) {
    Element el = (Element) it.next();
    al.add(el.getText());
    }
                               //如果al.get(0).toString()的值是LandConn就调用下面的方法
    if (al.get(0).toString().equals("LandConn")) {
    this.LandConn(clientSo);
    } else if (al.get(0).toString().equals("EnrollConn")) {
    this.EnrollConn(clientSo);
    } else if (al.get(0).toString().equals("SendConn")) {
    this.SendConn(clientSo);
    } else if (al.get(0).toString().equals("ExitConn")) {
    this.ExitConn(clientSo);
    }
    } catch (Exception e) {e.printStackTrace();} }

    //登陆
    @SuppressWarnings("unchecked")
    private void LandConn(Socket so) {
    try {
    // 接收信息

    Document doc1 = DocumentHelper.parseText(sb.toString());
    Element root1 = doc1.getRootElement();// 有并列的两个子集时,只要写其中一个,而不要都写
    List list = root1.selectNodes("//Class/Classes/@name");
    List list1 = root1.selectNodes("//Class/Classes/FunctionLanding/@name");
    List list2 = root1.selectNodes("//Class/Classes/FunctionLanding/IPparam");
    List list3 = root1.selectNodes("//Class/Classes/FunctionLanding/LNparam");
    List list4 = root1.selectNodes("//Class/Classes/FunctionLanding/ANparam");
    List list5 = root1.selectNodes("//Class/Classes/FunctionLanding/PWparam");
    List list6 = root1.selectNodes("//Class/Classes/FunctionLanding/Fparam");
    ArrayList al = new ArrayList();
    for (Iterator io = list.iterator(); io.hasNext();) {// 得到类名
    Attribute at = (Attribute) io.next();
    al.add(at.getValue());
    }
    for (Iterator io = list1.iterator(); io.hasNext();) {// 得到方法名
    Attribute at = (Attribute) io.next();
    al.add(at.getValue());
    }
    for (Iterator io = list2.iterator(); io.hasNext();) {// 得到客户IP
    Element el = (Element) io.next();
    al.add(el.getText());
    }
    for (Iterator io = list3.iterator(); io.hasNext();) {// 得到客户名
    Element el = (Element) io.next();
    al.add(el.getText());
    }
    for (Iterator io = list4.iterator(); io.hasNext();) {// 得到客户帐号
    Element el = (Element) io.next();
    al.add(el.getText());
    }
    for (Iterator io = list5.iterator(); io.hasNext();) {// 得到客户密码
    Element el = (Element) io.next();
    al.add(el.getText());
    }
    for(Iterator io = list6.iterator(); io.hasNext();){//得到标记1
    Element el = (Element) io.next();
    al.add(el.getText());
    }
    boolean flagLand = false;//从数据库返回true或false
    if (al.size() == 7) {
    if (al.get(0).toString().equals("SQLParse")&& al.get(1).toString().equals("LandDemo")) {
    // 将数据传给QQParse,与数据库中的值比较
    flagLand = sp.LandDemo(al.get(2).toString(), al.get(3).toString(), al.get(4).toString(), al.get(5).toString(),al.get(6).toString());
    }
    }
    // 返回信息

    Document doc = DocumentHelper.createDocument();
    Element root = doc.addElement("Class");
    Element incept = root.addElement("incept");
    incept.addAttribute("type", "boolean").addText(String.valueOf(flagLand));
    String lint = "   ";
    boolean newline = true;
    XMLWriter writer = new XMLWriter(clientSo.getOutputStream(),new OutputFormat(lint, newline, "gb2312"));
    writer.write(doc);
    writer.flush();
    clientSo.shutdownOutput();
    } catch (Exception e) {e.printStackTrace();}
    }
    }
      

  11.   

    > while ((line=br.readLine())!=null) {//这里null可以改成</Class>,但会出现我昨天提到的问题
    >     sb.append(line);
    > }先说这个吧,把 null 改成 </Class> 肯定是不合适的,字符串的比较最好用 equals(),用 == 和 != 往往不是你预期的效果。至于为什么会 OutOfMemory,你想 line=br.readLine() 事实上已经返回 null 了(因为你在对端关闭了输出流),但你却去判断 line != "</Class>",当然总是成立的,然后就死循环了。
      

  12.   

    您说的对,字符串比较不能用 == ,我给弄晕了,还是基本功不扎实.您说的第二个问题调试其实我一直在用,知道出错的地方,但不知道如何解决,辛好有你帮忙,非常感谢!!!
    大哥你搞java很久了吧?我测试成功后就给你加分.
      

  13.   

    maquan大哥您好,这两天我在家进行了测试,是我经验太少,始终无法解决服务器的循环问题,还有自己定义的结束标记,当然这属于同一个问题。我发现服务器不作循环时可以接收一条数据客户端数据,当然它会报错因为数据没有接收全。您是否有相应的代码让我学习一下,我将非常感谢!您可以将代码发往
      

  14.   

    我做了两段程序,不长,而且基本是用你的代码改的,应该比较容易看懂。//-------- test\Land\Client.java --------
    package test.Land;import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.Socket;import org.dom4j.Document;
    import org.dom4j.DocumentHelper;
    import org.dom4j.Element;
    import org.dom4j.io.OutputFormat;
    import org.dom4j.io.XMLWriter;public class Client {    public static void main(String[] args) throws Exception {
            test();
        }    public static void test() throws Exception {
            Socket socket = new Socket("localhost", 9999);        // 发送请求
            Document doc = DocumentHelper.createDocument();
            Element root = doc.addElement("Class");
            Element type = root.addElement("Types");
            Element classes = root.addElement("Classes");
            Element function = classes.addElement("FunctionLanding");
            type.addAttribute("type", "String").addText("LandConn");
            classes.addAttribute("name", "SQLParse");
            function.addAttribute("name", "LandDemo");
            function.addElement("IPparam").addAttribute("type", "String").addText("ClientIP");
            function.addElement("LNparam").addAttribute("type", "String").addText("ClientName");
            function.addElement("ANparam").addAttribute("type", "String").addText("ClientAccountNumber");
            function.addElement("PWparam").addAttribute("type", "String").addText("ClientPassWord");
            function.addElement("Fparam").addAttribute("type", "String").addText("Lflag");
            // 是否有缩进
            String indent = "   ";
            // 是否产生新行(即一行一个元素)
            boolean newline = true;
            XMLWriter writer = new XMLWriter(socket.getOutputStream(), new OutputFormat(indent, newline, "gb2312"));
            writer.write(doc);
            writer.flush();
            System.out.println("发送完毕");        // 接收响应
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            StringBuffer sb = new StringBuffer();
            String line;
            // 接收数据,直至 socket 被对方 close(或者 shutdownOutput)
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            System.out.println("接收到的内容: " + sb.toString());        socket.close();
        }
    }//-------- test\Land\Server.java --------
    package test.Land;import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.ServerSocket;
    import java.net.Socket;import org.dom4j.Document;
    import org.dom4j.DocumentHelper;
    import org.dom4j.Element;
    import org.dom4j.io.OutputFormat;
    import org.dom4j.io.XMLWriter;public class Server {    public static void main(String[] args) throws Exception {
            test();
        }    public static void test() throws IOException {
            ServerSocket ss = new ServerSocket(9999);
            Socket socket = ss.accept();        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            StringBuffer sb = new StringBuffer();
            String line = null;
            // 接收数据,直至收到一行 "</Class>"
            while (!"</Class>".equals(line = br.readLine())) {
                sb.append(line);
            }
            sb.append("</Class>");
            System.out.println("接收到的内容: " + sb.toString());        // 返回信息
            Document doc = DocumentHelper.createDocument();
            Element root = doc.addElement("Class");
            Element incept = root.addElement("incept");
            incept.addAttribute("type", "boolean").addText(String.valueOf(true));
            String lint = "   ";
            boolean newline = true;
            XMLWriter writer = new XMLWriter(socket.getOutputStream(), new OutputFormat(lint, newline, "gb2312"));
            writer.write(doc);
            writer.flush();
            System.out.println("发送完毕");        socket.close();
            ss.close();
        }}
      

  15.   

    maquan大哥您好,我想问一下如何将登陆类clientLand中的socket传给聊天类QQCose而不至    close或shutdownoutput。你知道的每一个客户只能有一个端口号。我现在虽然可以将clientLand中的socket传给QQCose,但它不是关闭就是断开,也就是说QQCose无法使用clientLand传过来的socket。你有何高见!!!
      

  16.   

    我没太看明白你的问题是什么。QQCose 也是你写的程序吧?它怎么使用传给它的 socket,还不是你说了算?!
      

  17.   

    当我登陆成功之后,登陆类中的socket传给聊天类,我在聊天类中发送信息时就报错--Socket Output is shutdown。因为我在登陆时使用了shutdownoutput,如果不用程序就死机。后来我重新用了另一个做法,一个客户使用两个socket,一个socket在登陆时用,登陆成功之后就关闭--close。第二个socket在聊天时用,但只能发送一条信息,第二条就报错--Socket Output is shutdown。我不知道如何解决这个问题,请问您有何意见?
      

  18.   

    > 因为我在登陆时使用了shutdownoutput,如果不用程序就死机根本没有“不用 shutdownOutput 就死机”的说法,你的程序“死机”一定是另有原因,shutdownOutput 并不是用来解决这个问题的。至于怎么解决“死机”问题,你仔细看看前面的回帖,以及我给你的那段程序,应该能弄清楚正确的用法是什么样的。你开两个 socket 的做法也是多余的,如果不解开“不用 shutdownOutput 就没法正常传送数据”这个结,你的 socket 程序永远也写不好。
      

  19.   

    maquan大哥您好,很感谢您对我的指导,上面的shutdownoutput的问题是解决了,现在我可以把socket传给任何一个类使用,但是新的问题出现了。当我把socket传给聊天类QQCose后,我想用这个以建立好的通道发送其它数据时服务器却没有反应。比如说在QQCose中有一个Exit_button的关闭按钮,当我按下按钮时会向服务器发送一些客户的信息给服务器,然后由服务器将客户数据交给数据库类,执行相应的操作。我将客户数据发向服务器时,服务器一点反应都没有,确切的说服务器一步都没执行。更糟的是我的eclipse好像出问题了,每次使用单步调试时,总是出现类编辑器这样的提示,我现在只能用System.out.println来做简单的调试。我感觉是我的思路出问题了,我并没有使用建立好的socket通道,而是继续让服务器在侦听客户端,服务器不会侦听已经建立好的socket通道,但是让我使用建立好的socket通道,代码我又不知道如何写,请给我点意见,谢谢!!!