一、通过服务器转发消息实现群组聊天
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();
}
}
}