本帖最后由 yang3wei 于 2010-10-01 20:12:21 编辑

解决方案 »

  1.   

    package org.bruce.socket2;import java.io.BufferedReader;
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.net.SocketException;
    import java.util.ArrayList;
    import java.util.List;public class Server implements Runnable{

    ServerSocket ss = null;
    Socket fatherS = null;
    Socket shutdownSocket = null;
    List<MyClient> clients = new ArrayList<MyClient>();
    boolean keepConnect = false;
    BufferedReader br = null;
    MyClient mc = null;
    String messageFromServer = null;
    boolean isServerAvailable = true;
    boolean allSocketClosed = false;
    int count = 0;

    public Server() {
    this.startServer();
    (new Thread(this)).start();
    this.acceptSocket();
    } public static void main(String[] args) {
    new Server();
    }

    public void startServer() {
    try {
    this.ss = new ServerSocket(8888);
    this.keepConnect = true;
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public void acceptSocket() {
    while(keepConnect) {
    try {
    fatherS = ss.accept();
    if(isServerAvailable) {
    mc = new MyClient(fatherS);
    mc.start();
    clients.add(mc);
    mc = null;
    } else {
    while(shutdownSocket == null) {
    //空转,等到shutdownSocket真正被分配到以后再进行下一步操作
    }
    //结束阻塞的专用socket
    System.out.println(shutdownSocket);
    shutdownSocket.close();
    fatherS.close();
    System.out.println("shutdownSocket关闭了吗?" + shutdownSocket.isClosed());
    //将 keepConnect 的值设置为 false,使跳出 acceptSocket() 阻塞方法!
    keepConnect = false;
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

    //起这个线程的目的就是为了能受控制地关闭服务器
    public void run() {
    br = new BufferedReader(new InputStreamReader(System.in));
    do {
    try {
    messageFromServer = br.readLine();
    } catch (IOException e) {
    e.printStackTrace();
    }
    } while(!messageFromServer.equals("shutdown")); try {
    isServerAvailable = false;
    //构造一个虚假的 socket 连接到服务器自身,用于关闭服务器的 accept() 阻塞性方法!
    shutdownSocket = new Socket("127.0.0.1", 8888);
    } catch (Exception e) {
    e.printStackTrace();
    }
    while(!((shutdownSocket.isClosed())&&(shutdownSocket.isConnected()==true))) {
    //空转,当shutdownSocket连接成功过且关闭了以后,进入到下一步!
    }
    //解决了阻塞性方法和无限循环,进入下一步:
    //向 clients 集合中的每一个客户端所对应的连接对象发送一条断开命令
    for(int i = 0; i < clients.size(); i ++) {
    try {
    ((MyClient)clients.get(i)).dos.writeUTF("shutdown");
    } catch (IOException e) {
    e.printStackTrace();
    }
    } //当clients中所有MyClient对象的socket被关闭之后,再跳出这个while循环进入到下一步!
    while(!allSocketClosed) {
    for(int i = 0; i < clients.size(); i ++) {
    if(((MyClient)clients.get(i)).childS.isClosed()) {
    count ++;
    }
    }
    if(count == clients.size()) {
    allSocketClosed = true;
    } else {
    //归0,要不count的值将会一直增长下去,服务器就永远别想关闭了!
    count = 0;
    }
    }
    //最后一步,关闭 ServerSocket 对象,大功告成!
    try {
    ss.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    class MyClient extends Thread {
    Socket childS = null;
    DataOutputStream dos = null;
    DataInputStream dis = null;
    String message = null;
    boolean bConnected = false;
    MyClient tempMc = null; public MyClient(Socket s) {
    bConnected = true;
    childS = s;
    try {
    this.dos = new DataOutputStream(this.childS.getOutputStream());
    this.dis = new DataInputStream(this.childS.getInputStream());
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public void run() {
    while(bConnected) {
    try {
    message = dis.readUTF();
    if(message.equals("disconnect")) {
    //dis 和 dos 中关掉任意一个,socket连接都会自动关掉
    dos.writeUTF("disconnect");
    clients.remove(this);
    dis.close();
    System.out.println("一个客户端通过键入disconnect的方式温和地退出了!(可以放心,它已经退踢出列表了)");
    bConnected = false;
    } else if(message.equals("shutdown")) {

    //我就是没想通@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    //这个问题花费了我3四个小时!!
    //问题的关键在于:从 clients 集合里移除某些 MyClient 对象了以后,
    //clients 集合里面剩余的对象的次序会发生改变,集合的大小 size 也会发生改变
    //这样就导致了一些客户端根本就没法收到从服务器端发过去的 shutdown 命令!
    //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

    //下面这行代码就是这个问题的罪魁祸首!
    // clients.remove(this);
    dis.close();
    System.out.println("这场关闭狂潮由服务器发起!一个客户端已成功退出!");
    bConnected = false;
    } else {
    //转发给所有的客户端!
    for(int i = 0; i < clients.size(); i ++) {
    tempMc = (MyClient)clients.get(i);
    tempMc.dos.writeUTF(message);
    }

    //只返还给发信息过来的客户端!
    // dos.writeUTF("这是服务器端返还给客户端的信息:" + message);
    }
    } catch (SocketException e) {
    clients.remove(this);
    try {
    dis.close();
    } catch(Exception ex) {
    System.out.println("本来就出错了,在出错的错误处理中又出错了!");
    }
    //以下代码导致该线程终结
    System.out.println("一个客户端没有提前跟 Server端 商量就粗暴地退出了!(可以放心,它已经退踢出列表了)");
    bConnected = false;
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }
      

  2.   

    package org.bruce.socket2;import java.io.BufferedReader;
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.Socket;
    import java.net.UnknownHostException;//写出个类源自改进别人写的Server1的想法
    public class Client implements Runnable {

    Socket s = null;
    DataInputStream dis = null;
    DataOutputStream dos = null;
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    InputStreamReader isr = null;
    boolean isConnected = false;
    String message = null;
    String keyin = null;

    public static void main(String[] args) {
    Client c = new Client();
    c.connect();
    (new Thread(c)).start();
    c.receiveKeyin();
    }

    public void connect() {
    try {
    s = new Socket("127.0.0.1", 8888);
    isConnected = true;
    dis = new DataInputStream(s.getInputStream());
    dos = new DataOutputStream(s.getOutputStream());
    } catch (UnknownHostException e) {
    e.printStackTrace();
    } catch (IOException e) {
    System.out.println("请先打开服务器!!");
    }
    }

    public void receiveKeyin() {
    while(isConnected) {
    try {
    keyin = br.readLine();
    if(keyin.equals("disconnect")) {
    dos.writeUTF("disconnect");
    System.out.println("客户端自行正常关闭!");
    //作用相当于break;
    isConnected = false;
    //暂时就不关了,等发至服务器的disconnect传回来被线程收到停止线程以后再关
    // dos.close();
    } else {
    System.out.println(keyin);
    dos.writeUTF(keyin);
    }
    } catch (IOException e) {
    e.printStackTrace();
    break;
    }
    }
    }

    public void run() {
    while(isConnected) {
    try {
    //在接收键盘输入 disconnect 以后,
    //dos 的关闭导致整个socket连接的关闭
    //此时主方法已经走到尽头,但是还有一个线程因为在执行 readUTF() 方法阻塞在那里
    //socket 连接的关闭导致该线程抛出 SocketException ! 
    message = dis.readUTF();
    if(message.equals("disconnect")) {
    //直接break,判断都省去了
    //其实这里什么都不写也一样的,因为在上面 isConnected 的值已经被设为 false 了
    //dis 关闭后 dos、s 都将关闭!
    dis.close();
    if(br != null) {
    br.close();
    }
    break;
    } else if(message.equals("shutdown")) {
    //将 shutdown 回发给 Server 端 
    dos.writeUTF("shutdown");
    dos.close();
    System.out.println("客户端的socket连接是否已经被关闭:" + s.isClosed());
    //一下这种方式不能关掉前面调用的 readLine() 阻塞性方法。
    //相反,貌似还会阻塞住这支线程的正常终止(while循环后面那行输出迟迟没出现在控制台上)!!!
    // br.close();
    break;
    } else {
    System.out.println(message);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    System.out.println("客户端用于接收从服务器端发过来消息的线程已经被关闭了!");
    }
    }
      

  3.   

    本来以为CSDN很好,但是使用过之后发现并没有自己想地那么完美!我的问题提了好几天了一直都没有一个人给回复,哀莫大于心死!难道我所提出的问题真的有那么难,硬是没有人回答地出来!各位同志们,在没有人愿意帮我的情况下,很不幸,我自己解决了自己的问题,就用个System.exit(0)就解决了,虽然换了一种思路,但终归还是解决了!这是我第一次在CSDN上发帖,想不到竟然收到的是这样一种结局,我真的怀疑这个论坛是否真的有真心愿意帮助菜鸟们的好心人!这是我内心真实的想法,爱喷人的臭嘴不要在这里撒野!
      

  4.   

    LZ不要生气嘛   我第一次发的帖子也是没人回的,或许某段时间正好没大侠在线,然后等他们上来了帖子又被别的压下去了,这都是很正常的。其实CSDN真的是一个学习的好地方
      

  5.   

    在这里是解决细节小问题的,代码过长,看的人有1%。你也别怪csdn、、、
      

  6.   

    虽然我的代码是有点长,但是我在前面已经把思路写的很清晰了啊,包括意图、目标,我相信我的问题前面就已经描述地差不多了,后面只是给出代码辅助分析。而且,代码里面做足了注释,更要注意的是,这是个典型的java socket编程小例子,并没有什么很奇葩的东西呀。没有冒犯的意思,可能是大家心态不同罢了!我现在有进一步的计划:将这段代码写成一个控制台的QQ,就是不做GUI界面不用事件,完全靠多线程和socket以及流程控制实现。可以暴露一下进度,目前我的客户端已经可以进行改名操作了,如果大家有兴趣的话,可以一起研究!我要实现的功能是:客户端可以一对多的公聊、一对一的私聊!目前一对多的公聊已经写好(服务器遍历各个客户端,再转发给每一个就行了,确实简单),令我最满意的功能如下:①客户端正常或非正常退出服务器端均能及时关闭socket连接,并将其从HashMap中移除出去;②在服务器端敲入“shutdown”指令,可以受控制地逐个先关闭掉每个客户端再将服务器端自己关闭;总之,只要不暴力关掉服务器,对各种异常的容错能力都比较不错,当然我还没测试过超长信息长度将带来什么后果。。有兴趣的可以找我来研究,我的QQ88480585。我还有一些别的有意思的项目,比如:使用Robot类结合socket实行远程监视和控制,垃圾邮件发送器,屏幕录像程序...必须声明的是,上述项目均处于开发阶段,大家如果有兴趣,可以加我QQ一起来研究、讨论,如果有个类似经历的大虾们能提供些思路、资料,小子亦将不甚感激!
      

  7.   

    哥们儿,你哪只眼睛看到我说过要别人义务帮助我啊?!如果这个论坛动不动就要别人义务帮助,也没什么存在下去的必要了,试想我如果是大侠,被强迫着要去帮助别人,心里也是很不爽的!CSDN被誉为全国最大的编程技术论坛,只不过和我所想的有所差距而已,我以为发个帖3、5分钟就会有人会,是我想得太天真,我并没有责怪各位的意思..