《互联网程序设计(Java)》——课程笔记8:群组聊天技术

一、通过服务器转发消息实现群组聊天

TCPClient和TCPThreadServer只实现了客户端和服务器聊天,如何做到客户和客户的聊天?如客户A的聊天信息通过服务器转发到客户B和客户C 等其他用户。

程序设计第二步:在TCPThreadServer.java程序中添加其它功能,如转发客户之间的对话。

在服务器端新增记录登陆的客户信息,可用在线方式、用户文件方式或数据库方式。本讲的程序用“在线方式“记录客户套接字,即用户登陆的套接字信息,来跟踪客户连接。

将TCPThreadServer.java程序重构、复制并命名为GroupServer.java

 


//在线记录登录客户的信息,同时发送给其他登录的在线客户
public class GroupServer {
    private int port = 8008;
    private ServerSocket serverSocket;
    private ExecutorService executorService; //线程池
    private final int POOL_SIZE = 100; //单个CPU时线程池中工作线程的数目
    
    private static Set<Socket> Members; //在GroupServer主类中定义全局变量,在线组员集合
    
    public GroupServer() throws IOException
    {
        Members = new HashSet<Socket>();
        
        serverSocket = new ServerSocket(port); //打开服务器端口
        
        //创建线程池,Runtime的Availableprocessor()方法返回当前系统的CPU的数目
        //系统中CPU越多,线程池中工作线程的数目也应该越多
        executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*POOL_SIZE);
        
        
        
        System.out.println("多用户服务器启动");
    }
    
    //在GroupServer中定义群组消息发送方法
    public static void sendToAllMembers(String msg) throws IOException
    {
        PrintWriter pw;
        Socket tempSocket;
        OutputStream out;
        
        Iterator it = Members.iterator();
        while(it.hasNext())//遍历在线成员Set集合
        {
            tempSocket = (Socket)it.next(); //取出一个成员
            out = tempSocket.getOutputStream(); //得到输出流
            //封装为字符流
            pw = new PrintWriter(new OutputStreamWriter(out,"GB2312"),true);
            pw.println("From the Server: " + msg);//发送聊天信息给成员
            
        }//群组发送结束
    }
    
    public static void removeMember(Socket socket)
    {
        Members.remove(socket);//删除一个成员
    }
    
    
    
    public void service()
    {
        while(true)
        {
            Socket socket = null;
            try
            {
                socket = serverSocket.accept(); //监听客户请求,阻塞语句
                
                //将新建立的socket连接加入Members成员列表中
                Members.add(socket);
                
                //接受一个客户请求,从线程池中拿出一个线程专门处理该客户
                executorService.execute(new Handler(socket));
                
            }catch(Exception e)
            {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String args[]) throws IOException
    {
        new GroupServer().service();
    }
    
    
    
    
    //定义内部类,实现线程接口
class Handler implements Runnable
{
    private Socket socket;

    public Handler(Socket socket)
    {
        this.socket = socket;
    }
    private PrintWriter getWriter(Socket socket) throws IOException
    {
        OutputStream socketOut = socket.getOutputStream();
        return new PrintWriter(new OutputStreamWriter(socketOut,"GB2312"),true);
    }
    private BufferedReader getReader(Socket socket) throws IOException
    {
        InputStream socketIn = socket.getInputStream();
        return new BufferedReader(new InputStreamReader(socketIn,"GB2312"));
    }
    
    @Override
    public void run() {
       String ip = socket.getInetAddress().getHostAddress();
       int port = socket.getPort();
       try
       {
           System.out.println("New connection accepted " + ip +"   :  " + port);
           
           BufferedReader br = getReader(socket);//字节流封装为字符流
           PrintWriter pw = getWriter(socket); //字节流封装为字符流
           
           String msg = null;
           while((msg = br.readLine())!= null)
           {
           //    pw.println("From ThreadServer: " + msg); //send to client
             //在内部类中,用下面语句来表示发送给全体成员
               sendToAllMembers(msg);
               
               if(msg.contains("bye".substring(0, 2)))
               {
                   System.out.println(socket.getInetAddress() + " : "  +  socket.getPort()  +":"+ "Exit");
            
                   break;
               }
           }
                   
       }catch(Exception e)
       {
           System.out.println(  " " + ip +"   :  " + port   + "    "  + "Exit.");
         //  e.printStackTrace();
       }finally
       {
           try
           {
               removeMember(socket); //同时从成员列表Members中移除该socket
               if(socket!=null)
               {
                   socket.close();
               }
           }catch(IOException e)
           {
               e.printStackTrace();
           }
       }
    }
}
    
}


二、组播程序设计

组播是指在一群用户范围内发送和接收信息,该信息具有共享性。UDP具有组播功能,而TCP不具有。

组播地址的格式为:224.*.*.*,比如:224.1.10.10。组播地址号唯一标示一群用户(一定网络范围内,仅限于局域网络内或有些自治系统内支持组播)。

组播套接字类:MulticastSocket及其几个重要的方法:

(1)   路由器、交换机一般只转发和终端机一致IP地址和广播地址数据,终端机如何知道要接收组内信息?要先声明加入或退出某一组播组,其方法是:

MulticastSocket  ms=new MulticastSocket(8900);

ms.joinGroup(groupIP);//加入groupIP组

//作用:告知自己的网络层该IP地址的包要收;

//转告上联的路由器这样的IP地址包要转发。

ms.leaveGroup(groupIP);//退出groupIP组

 

(2)   组内接收和发送信息的方法同UDP单播,其方法是:

ms.send(packet);

ms.receiver(packet).

 (3) 独立完成组播程序MulticastQQ.java和MulticastQQJFrame.java,组播套接字为:[224.0.1.8:8900],在组内发言要求以“201********* 姓名*”为信息头。其效果如图4所示,要求每位同学都能看到组内其他同学的留言。

 

 

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class MulticastQQ {
    InetAddress groupIP;// = InetAddress.getByName("224.0.1.8");
    int port = 8900;
    MulticastSocket ms = null; //组播套接字
    
    byte[] inBuff;
    byte[] outBuff;
    
    public MulticastQQ() throws IOException
    {
        groupIP = InetAddress.getByName("224.0.1.8");
        
        ms = new MulticastSocket(port); //开启一个组播端口(UDP端口)
        ms.joinGroup(groupIP); //告诉网卡这样的IP地址数据包要接收
        
        inBuff = new byte[1024];
        outBuff = new byte[1024];
    }
    
    public void send(String msg)
    {
        try
        {
            outBuff = ("20121000000老师 : " + msg).getBytes("GBK");
            DatagramPacket outdp = new DatagramPacket(outBuff, outBuff.length, groupIP, port);
            ms.send(outdp);
        }catch(Exception e)
        {
            e.printStackTrace();
        }
    }
    
    public String receive()
    {
        try
        {
            DatagramPacket indp = new DatagramPacket(inBuff,inBuff.length);
            
            ms.receive(indp);
            String msg = new String(indp.getData(), 0, indp.getLength(), "GBK");
            return indp.getAddress() + "--->" + msg;
                
        }catch(Exception e)
        {
            return null;
           // e.printStackTrace();
        }
    }
    
    public void close()
    {
        try
        {
            ms.leaveGroup(groupIP);
            ms.close();
            
        }catch(Exception e)
        {
            e.printStackTrace();
        }
    }
   
    
}

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值