JAVA实现服务器和客户端的单播/广播消息转发的Socket编程(有图形用户界面)【含源码】

JAVA实现服务器和客户端的单播/广播消息转发的Socket编程



实验要求:

一个服务器,多个客户端,服务器负责消息中转,客户端之间可以互相聊天。(广播/单播)

实验环境:WINDOWS

编程环境:JAVA

 

界面流程展示:

1.   服务器界面展示

服务器监听本机的8288端口,首先需要点击启动按钮,才能通过客户端连接

JAVA实现服务器和客户端的单播/广播消息转发的Socket编程(有图形用户界面)【含源码】_第1张图片

 

2.   客户端界面展示

新建客户端界面后需要先输入用户昵称,服务器ip地址和端口号,点击连接既可以连接服务器。

JAVA实现服务器和客户端的单播/广播消息转发的Socket编程(有图形用户界面)【含源码】_第2张图片

 

3.   服务器启动

点击服务器界面的启动按钮,日志显示服务器启动成功,好友列表显示全体成员标签。

JAVA实现服务器和客户端的单播/广播消息转发的Socket编程(有图形用户界面)【含源码】_第3张图片

4.   客户端连接

客户端(左)单击连接按钮,客户端好友列表中显示了所有人标签(用于广播,可点击),服务器日志显示接收到客户端连接,并为客户端创建线程,同时在在线用户列表中添加客户端用户昵称。

JAVA实现服务器和客户端的单播/广播消息转发的Socket编程(有图形用户界面)【含源码】_第4张图片

 

5.用户上线通知

    再打开两个客户端,服务器收到新的客户端连接的请求,并将用户上线的消息向所有用户转发。服务器和每一个客户端同时也会更新在线用户列表。新上线的用户会收到服务器向其发送的原有在线用户列表信息。

JAVA实现服务器和客户端的单播/广播消息转发的Socket编程(有图形用户界面)【含源码】_第5张图片

 

6.用户进行群发操作(通过服务器广播)

左下角的用户点击其界面上的所有人标签,或者什么都不点击(默认为向所有人发送消息),并在输入框内部输入消息内容,点击发送。服务器接收到用户的消息,并将其向每一个用户进行转发。

 JAVA实现服务器和客户端的单播/广播消息转发的Socket编程(有图形用户界面)【含源码】_第6张图片

 

7.用户进行私聊转发(单播)

右下角的用户点击了在线好友列表中的一位用户,并在输入框内输入消息点击发送。服务器接收到该消息,并将其向对应客户端转发。

JAVA实现服务器和客户端的单播/广播消息转发的Socket编程(有图形用户界面)【含源码】_第7张图片

 

8.服务器向群体用户广播

    服务器点击所有人标签或不点击(默认为所有人),在输入框内输入消息并点击发送。每个客户端都收到服务器发送的内容。

JAVA实现服务器和客户端的单播/广播消息转发的Socket编程(有图形用户界面)【含源码】_第8张图片

 

9.服务器私聊转发(单播)

    服务器点击在线用户列表中的某一个人,并输入消息点击发送。对应客户端收到消息。

JAVA实现服务器和客户端的单播/广播消息转发的Socket编程(有图形用户界面)【含源码】_第9张图片

 

说明文档:

客户端:

1.    声明主类Client的成员变量

这些变量都是在多处调用的,如果不声明为成员变量,利用传参的方法传递很麻烦,故在外声明。尤其是注释内容1.2的socket相关变量,不在外部声明将会导致socket关闭。

1.1  UI组件:连接、断开、发送、昵称、IP、端口、输入内容、在线列表、聊天内容

1.2  Socket相关:客户端socket,输入和输出

1.3  用户昵称:声明DefaultListModel类型的列表,向其内部添加数据可以自动同步到在线列表中

1.4  客户端线程:为了防止死锁,将客户端新建一个与UI界面无关的线程

2.    构造函数

2.1  调用UI函数显示窗口

3.    与连接服务器相关

3.1  连接服务器

3.1.1      获取用户昵称、服务器ip、port

3.1.2      New与socket相关

3.1.3      在线列表添加所有人标签

3.1.4      向服务器发送本地账号登陆

3.1.5      为客户端新建一个与UI分离的线程用于等待服务器发送消息

4.    UI相关

4.1  显示界面

4.2  添加事件监听

4.2.1      连接

4.2.2      发送:将消息按照固定协议发送

4.2.3      选择消息发送对象

4.3输出错误:红色

4.4输出私聊内容:黑色

4.5输出上线下线内容:蓝色

4.6输出广播内容:绿色

5. 客户端线程的内部类

考虑到读取服务器消息时调用readline会将进程死锁,故新建一个线程,该线程继承了Runnable接口,在构造函数被调用后自动执行run方法

       5.1构造函数

       5.2run()

              5.2.1对服务器消息进行解析(分割)

              5.2.1按照制定协议处理消息:

登陆: LOGIN@USERNAME

普通消息: MESSAGE@TO@FROM@CONTENT

6. 发送消息

7.消息分割器

 

服务端:

1.    在主类Server中声明必要的成员变量

1.1  UI组件

1.2  Socket相关:服务器socket

1.3  线程相关:服务器线程、记录每一个客户端的线程地图

1.4  用户昵称

2.    构造函数

3.    启动服务器

3.1建立socket

3.2新建服务器线程

3.3新建存储客户端线程的地图

4. UI相关

       4.1生成界面窗口

       4.2添加监听器:启动、发送消息、检验目标用户

       4.3输出消息内容:黑色

       4.4输出错误提示:红色

       4.5输出成功提示:绿色

5. 服务器线程内部类

       5.1isRuning用于确定服务器是否在运行

       5.2构造函数:调用构造函数后自动调用对应线程

       5.3run()

              5.3.1循环等待下一个客户端连接,注意,这里的socket在内部声明,保证每一个客户端的socket不是同一个。

              5.3.2每接收到一个客户端socket就为其新建一个客户端线程,保证每个客户端之间互不干扰

              5.3.3将新的客户端线程添加到记录客户端线程的地图中

6.客户端线程内部类

       6.1新建线程时首先调用初始化函数,接收登陆消息

              6.1.1为每一个其他用户转发该用户的登陆消息

              6.1.2为该用户发送已经在线的用户列表

              6.1.3在线用户增加该用户的昵称

       6.2run()第二次和以后调用该方法

              6.1阻塞等待客户端传送消息

              6.2收到消息后按照对应要求解析消息

                     6.2.1MESSAGE@TO@FROM@CONTENT

              6.3按照To指向的用户对消息进行单播或广播


源码如下

Client.java

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

public class Client {
	//1.以下为必须在外部声明的变量
	
	//1.1 UI组件
	JButton ConnectServer;//连接
	JButton DisconnectServer;//断开
	JButton SendMessageButton;//发送
	JTextField NickNameText;//昵称
	JTextField ServerIPAddressText;//服务器ip
	JTextField ServerPortText;//服务器端口
	JTextField InputContentText;//输入内容
	JList OnlineClientList;//在线列表
	JLabel ChatContentLabel;//聊天内容

	//1.2 socket相关
	Socket socket;//input和output是通过socket定义的,如果socket关闭了,其他两个也失效
	BufferedReader input;//input为服务器传来的数据
	PrintStream output;//output为向服务器输出的数据
	
	//1.3  用户昵称
	DefaultListModel OnlineClientNickName;//在线用户昵称列表:向其中插入数据,自动将数据插入到JList中
	String ToTargetName = "ALL";//目标用户昵称:OnlineClientList的监听器对其修改
	
	//1.4客户端线程
	ClientThread cliendThread;
	
	//2.构造函数
	public Client() {
		//2.1 调用UI函数显示窗口
		CreateFrame();
	}
	
	//3.与连接服务器相关
	//3.1连接服务器
	public void ConnectServer() {
		//3.1.1 获取基本信息
		String ServerIPAddress = ServerIPAddressText.getText().trim();
		int ServerPort = Integer.parseInt(ServerPortText.getText().trim());
		String NickName = NickNameText.getText();
		
		try {
			//3.1.2 socket相关
			socket = new Socket(ServerIPAddress, ServerPort);
			input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			output = new PrintStream(socket.getOutputStream());
			//3.1.2 在线列表添加所有人标签
			OnlineClientNickName.addElement("所有人");
			//3.1.3 向服务器发送本帐号登陆消息
			SendMessage("LOGIN@"+NickName);
			
			//3.1.4 为客户端建立线程
			cliendThread = new ClientThread();
			
		} catch (UnknownHostException e) {
			Error("Client:主机地址异常"+e.getMessage());
			return;
		} catch (IOException e) {
			Error("Client:连接服务器异常"+e.getMessage());
			return;
		}	
	}
	//3.2 断开连接
	
	//3.3连接或断开时的按钮是否可点击设置
	

	//4. UI相关
	//4.1界面
	public void CreateFrame() {
		//4.1.1 总窗口
		JFrame ClientFrame = new JFrame("客户端");
		ClientFrame.setSize(800,600);//设置长宽,注意数值不需要引号
		ClientFrame.setLocationRelativeTo(null);//设置在屏幕中央显示
		ClientFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置默认关闭按钮,不设置也可以

		//4.1.2 客户端信息
		JPanel ClientIdPanel = new JPanel();
		ClientIdPanel.setLayout(new FlowLayout(FlowLayout.LEFT));//4.2.1 设置客户端id栏的布局为浮动布局
		ClientIdPanel.setSize(800, 100);
		//4.1.2.2 昵称栏
		JLabel NickNameLabel = new JLabel("昵称");
		NickNameText = new JTextField(10);
		NickNameText.setText("jiangbowen");
		ClientIdPanel.add(NickNameLabel);
		ClientIdPanel.add(NickNameText);
		//4.1.2.3 服务器IP地址
		JLabel ServerIPAddressLabel = new JLabel("IP地址");
		ServerIPAddressText = new JTextField(10);
		ServerIPAddressText.setText("127.0.0.1");
		ClientIdPanel.add(ServerIPAddressLabel);
		ClientIdPanel.add(ServerIPAddressText);
		//4.1.2.4 端口号
		JLabel ServerPortLabel = new JLabel("端口");
		ServerPortText = new JTextField(10);
		ServerPortText.setText("8288");
		ClientIdPanel.add(ServerPortLabel);
		ClientIdPanel.add(ServerPortText);
		//4.1.2.5 连接服务器/断开连接
		ConnectServer = new JButton("连接");
		DisconnectServer = new JButton("断开");
		ClientIdPanel.add(ConnectServer);
		ClientIdPanel.add(DisconnectServer);
		//4.1.2.6 设置标题
		ClientIdPanel.setBorder(new TitledBorder("用户信息栏"));
		
		//4.1.3 好友列表
		JPanel FriendListPanel = new JPanel();
		FriendListPanel.setPreferredSize(new Dimension(200,400));
		FriendListPanel.setBorder(new TitledBorder("好友列表"));
		//4.1.3.1 好友列表内容
		OnlineClientNickName = new DefaultListModel();
		OnlineClientList = new JList(OnlineClientNickName);
		FriendListPanel.add(OnlineClientList);
		
		//4.1.4 聊天内容面板
		JPanel ChatContentPanel = new JPanel();
		ChatContentPanel.setPreferredSize(new Dimension(490,400));
		ChatContentPanel.setBorder(new TitledBorder("聊天内容"));
		//4.1.4.1 声明聊天内容标签
		ChatContentLabel = new JLabel("");
		ChatContentLabel.setPreferredSize(new Dimension(490,400));
		ChatContentPanel.add(ChatContentLabel);

		//4.1.5 输入内容面板
		JPanel InputContentPanel = new JPanel();
		InputContentPanel.setPreferredSize(new Dimension(600,100));
		//4.1.5.1 聊天输入框
		InputContentText = new JTextField();
		InputContentText.setPreferredSize(new Dimension(600,60));
		//4.1.5.2 发送按钮
		SendMessageButton = new JButton("发送");
		InputContentPanel.add(InputContentText);
		InputContentPanel.add(SendMessageButton);
		InputContentPanel.setBorder(new TitledBorder("输入内容"));
		
		//4.1.6 客户端整体布局
		ClientFrame.add(ClientIdPanel, BorderLayout.NORTH);
		ClientFrame.add(FriendListPanel, BorderLayout.WEST);
		ClientFrame.add(ChatContentPanel,BorderLayout.CENTER);
		ClientFrame.add(InputContentPanel,BorderLayout.SOUTH);
		
		//4.1.7设置可见
		ClientFrame.setVisible(true);	//设置可见必须在所有内容都add进Frame之后
		
		//4.1.8 添加监听事件
		AddActionListener();
	}
	//4.2 添加事件监听
	private void AddActionListener() {
		//4.2.1 点击连接
		ConnectServer.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				ConnectServer();
			}
		});
		//4.2.2 点击断开
		DisconnectServer.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				
			}
		});
		//4.2.3 点击发送
		SendMessageButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String message = InputContentText.getText().trim();
				SendMessage("MESSAGE@"+ToTargetName+"@"+NickNameText.getText()+"@"+message); 
			}
		});
		
		//4.2.4 检验目标发送者是谁
		OnlineClientList.addListSelectionListener(new ListSelectionListener() { 
			@Override
			public void valueChanged(ListSelectionEvent e) {
				int index = OnlineClientList.getSelectedIndex();
				if(index<0) {
					Error("Client:检测到目标发送者下标为负数");
					return;
				}
				if(index == 0) {
					ToTargetName = "ALL";
				}else {
					String ToClientNickName = (String)OnlineClientNickName.getElementAt(index);
					ToTargetName = ToClientNickName;
				}
			}
		});
	}
	//4.3 输出错误(红色)
	private void Error(String message){
		//JLabel不支持\n换行,故添加html标签进行换行,没有结束标签不影响显示
		ChatContentLabel.setText(ChatContentLabel.getText()+""+message+""+"
"); } //4.4 输出上线下线内容 private void Log(String message){ //JLabel不支持\n换行,故添加html标签进行换行,没有结束标签不影响显示 ChatContentLabel.setText(ChatContentLabel.getText()+""+message+""+"
"); } //4.5 输出私聊内容 private void Message(String message){ //JLabel不支持\n换行,故添加html标签进行换行,没有结束标签不影响显示 ChatContentLabel.setText(ChatContentLabel.getText()+""+message+""+"
"); } //4.6 输出广播内容 private void MessageTotal(String message){ //JLabel不支持\n换行,故添加html标签进行换行,没有结束标签不影响显示 ChatContentLabel.setText(ChatContentLabel.getText()+""+message+""+"
"); } //5. 客户端线程 内部类 public class ClientThread implements Runnable{ //与服务器建立连接时,新建客户端线程,否则无法接收信息 //与服务器断开连接时,向服务器告知,杀掉客户端进程 //客户端调用readline时会产生死锁,故需要新建一个线程 boolean isRuning = true; //5.1 构造函数 public ClientThread () { //5.1.1 开始本线程 new Thread(this).start(); } @Override //5.2 run函数会在线程开始时自动调用 public void run() { while(isRuning) {//循环用于重复接收消息,客户端断开连接之前不停止 // TODO Auto-generated method stub String message; try { //5.2.1 在服务器传来的消息中读取下一行 //readline会产生死锁,如果没有下一条消息则继续等待 //正是因为死锁,才要新建一个客户端线程 message = input.readLine(); Tokenizer tokens = new Tokenizer(message, "@");//对原有消息进行分割 String MessageType = tokens.nextToken(); //5.2.2根据人为定义的传输协议对消息进行显示 switch(MessageType) { case "LOGIN":{//其他用户上线 String LoginClientNickName = tokens.nextToken(); Log("上线通知:用户"+LoginClientNickName+"已上线"); OnlineClientNickName.addElement(LoginClientNickName); break; } case "MESSAGE":{//聊天消息 String ToClientNickName = tokens.nextToken(); String FromClientNickName = tokens.nextToken(); String content = tokens.nextToken(); if("ALL".equals(ToClientNickName)) { MessageTotal("来自"+FromClientNickName+ "对全体的消息:"+content); }else { Message("来自"+FromClientNickName+ "对您的私聊消息:"+content); } break; } case "LOGOUT":{//其他用户下线的消息 break; } default :{ Error("客户端接收消息格式错误"); break; } } System.out.println("客户端接收到"+message); } catch (IOException e) { // TODO Auto-generated catch block Error("Client:客户端接收消息失败"+ e.getMessage()); } } } } //6. 消息相关 //6.1 发送消息 public void SendMessage(String message) { output.println(message); output.flush(); } //6.2 消息分割器(内部类) public class Tokenizer{ String Tokens[]; int TokenIndex = 0; //6.2.1 构造方法,把Message,按Delimiter进行分割 public Tokenizer(String Message, String Delimiter) { Tokens = Message.split(Delimiter); } //6.2.2 获取下一项 public String nextToken() { TokenIndex++; return Tokens[TokenIndex-1]; } } public static void main(String srgs[]) { Client client = new Client(); } }


Server.java

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.concurrent.*;

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

public class Server {
	//1.v以下为必须在外部声明的变量
	//1.1 UI组件
	JButton StartServer;//开始服务器
	JButton StopServer;//停止服务器
	JButton SendMessageButton;//发送消息
	JTextField MaxNumberText;//最大连接人数
	JTextField ServerPortText;//监听端口
	JTextField InputContentText;//消息输入框
	JList OnlineClientList;//在线列表
	JLabel LogsLabel;//日志栏
	
	//1.2 socket相关
	ServerSocket serverSocket;//服务器的socket类型为 ServerSocket
	//这里必须要把serverThread线程在外部声明,否则会出现找不到类的报错
	
	//1.3 线程相关
	ServerThread1 serverThread;//服务器线程:这里的命名增加了1,怀疑系统中有和他同名的类
	ConcurrentHashMap clientThreads;//所有client的Thread,key=clientNickName, value=clientThread
															//ConcurrentHashMap对于多线程可以不一次死锁掉全部线程
	//1.4 用户昵称
	DefaultListModel OnlineClientNickName;//在线用户昵称,向其中插入数据,自动将数据插入到JList中
	String ToTargetName  = "ALL";//信息发送的目标用户昵称
	
	//2.构造函数
	public Server() {
		CreateFrame();
	}

	//3. 与服务器建立相关
	//3.1 开始运行服务器
	public void StartServer() {
		int ServerPort = Integer.parseInt(ServerPortText.getText().trim());
		try {
			//3.1.1 对相应端口建立socket
			serverSocket = new ServerSocket(ServerPort);
			
			//3.1.2 为服务器建立新的线程,在线程里面对socket进行监听
			serverThread = new ServerThread1();//如果不重新建立线程会导致服务器界面阻塞,无法点击
			
			//3.1.3新建存取每个客户端socket线程的map
			clientThreads = new ConcurrentHashMap();
			
			//3.1.4 服务端在线用户列表显示ALL
			OnlineClientNickName.addElement("ALL");
		} catch (BindException e) {
			// TODO Auto-generated catch block
			Error("Server:端口异常"+e.getMessage());
		} catch(Exception e) {
			Error("Server:服务器启动失败");
		}
		Success("成功运行服务器");
	}
	//3.2 断开服务器
	

	//4. UI相关
	//4.1 生成界面窗口
	public void CreateFrame() {
		//4.1 生成一个JFrame窗体
		JFrame ServerFrame = new JFrame("服务器");
		//4.1.1设置长宽,注意数值不需要引号
		ServerFrame.setSize(800,600);
		//4.1.2设置在屏幕中央显示
		ServerFrame.setLocationRelativeTo(null);
		//4.1.3设置默认关闭按钮,不设置也可以
		ServerFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		//4.2 声明服务端信息栏
		JPanel ServerIdPanel = new JPanel();
		//4.2.1 设置服务端信息栏的布局为浮动布局
		ServerIdPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
		ServerIdPanel.setSize(800, 100);
		//4.2.2 最大连接人数
		JLabel MaxNumberLabel = new JLabel("    最大连接人数");
		MaxNumberText = new JTextField(10);
		MaxNumberText.setText("10");
		ServerIdPanel.add(MaxNumberLabel);
		ServerIdPanel.add(MaxNumberText);
		//4.2.3 端口号
		JLabel ServerPortLabel = new JLabel("    端口");
		ServerPortText = new JTextField(10);
		ServerPortText.setText("8288");
		ServerIdPanel.add(ServerPortLabel);
		ServerIdPanel.add(ServerPortText);
		//4.2.5 启动/停止服务器
		StartServer = new JButton("启动");
		StopServer = new JButton("停止");
		ServerIdPanel.add(StartServer);
		ServerIdPanel.add(StopServer);
		//4.2.6 设置标题
		ServerIdPanel.setBorder(new TitledBorder("服务器信息栏"));
		
		//4.3 在线用户列表栏
		JPanel FriendListPanel = new JPanel();
		FriendListPanel.setPreferredSize(new Dimension(200,400));
		FriendListPanel.setBorder(new TitledBorder("好友列表"));		
		//4.3.1 好友列表内容
		OnlineClientNickName = new DefaultListModel();
		OnlineClientList = new JList(OnlineClientNickName);
		FriendListPanel.add(OnlineClientList);
		
		//4.4 日志面板
		JPanel LogsPanel = new JPanel();
		LogsPanel.setPreferredSize(new Dimension(590,400));
		LogsPanel.setBorder(new TitledBorder("日志内容"));
		//4.4.1 日志内容标签
		LogsLabel = new JLabel("");
		LogsLabel.setPreferredSize(new Dimension(590,400));
		LogsPanel.add(LogsLabel);
		
		//4.5 声明输入内容面板
		JPanel InputContentPanel = new JPanel();
		InputContentPanel.setPreferredSize(new Dimension(600,100));
		//4.5.1 定义聊天输入框
		InputContentText = new JTextField();
		InputContentText.setPreferredSize(new Dimension(600,60));
		//4.5.2 发送按钮
		SendMessageButton = new JButton("发送");
		InputContentPanel.add(InputContentText);
		InputContentPanel.add(SendMessageButton);
		InputContentPanel.setBorder(new TitledBorder("输入内容"));
		
		//4.6 服务端整体布局
		ServerFrame.add(ServerIdPanel, BorderLayout.NORTH);
		ServerFrame.add(FriendListPanel, BorderLayout.WEST);
		ServerFrame.add(LogsPanel, BorderLayout.CENTER);
		ServerFrame.add(InputContentPanel,BorderLayout.SOUTH);

		//4.7 设置可见
		ServerFrame.setVisible(true);	
		
		//4.8 添加监听事件
		AddActionListener();
	}
	//4.2添加监听
	private void AddActionListener() {
		StartServer.addActionListener(new ActionListener() {//启动服务器
			public void actionPerformed(ActionEvent e) {	
				StartServer();
			}
		});
		StopServer.addActionListener(new ActionListener() {//停止服务器
			public void actionPerformed(ActionEvent e) {
				Log("Server:点击停止,暂没有作用");
			}
		});
		SendMessageButton.addActionListener(new ActionListener() {//发送消息
			public void actionPerformed(ActionEvent e) {
				String message = InputContentText.getText();
				if("ALL".equals(ToTargetName)) {
					for(ConcurrentHashMap.Entry entry: clientThreads.entrySet()) {
						entry.getValue().SendMessage("MESSAGE@"+ToTargetName+"@SERVER@"+message);
					}
				}else {
					clientThreads.get(ToTargetName).SendMessage("MESSAGE@"+ToTargetName+"@SERVER@"+message);
				}
			}
		});
		OnlineClientList.addListSelectionListener(new ListSelectionListener() { //检验目标发送者是谁
			@Override
			public void valueChanged(ListSelectionEvent e) {
				// TODO Auto-generated method stub
				int index = OnlineClientList.getSelectedIndex();
				if(index<0) {
					Error("Server:消息目标用户下标错误");
					return;
				}
				if(index == 0) {
					ToTargetName = "ALL";
				}else {
					String ToClientNickName = (String)OnlineClientNickName.getElementAt(index);
					ToTargetName = ToClientNickName;
				}
				Success("成功修改消息目标用户为:"+ToTargetName);
			}

		});
	}
	//4.3 输出消息内容
	private void Log(String message){
		//JLabel不支持\n换行,故添加html标签进行换行,没有结束标签不影响显示
		LogsLabel.setText(LogsLabel.getText()+message+"
"); } //4.4 输出错误信息 private void Error(String message){ //JLabel不支持\n换行,故添加html标签进行换行,没有结束标签不影响显示 LogsLabel.setText(LogsLabel.getText()+"Error:"+message+""+"
"); } //4.4 输出成功信息 private void Success(String message){ //JLabel不支持\n换行,故添加html标签进行换行,没有结束标签不影响显示 LogsLabel.setText(LogsLabel.getText()+"Success:"+message+""+"
"); } //5.服务器线程 private class ServerThread1 implements Runnable{//在serverScoket.accept()的时候防止界面死锁 //5.1 isRuning为true时,服务器不停的accept下一个连接 boolean isRuning = true; //5.1构造函数 public ServerThread1() { new Thread(this).start(); } @Override //5.2线程开始后自动运行 public void run() { //5.2.1 循环accept下一个socket连接 while(isRuning) { if(!serverSocket.isClosed()) { try { Socket socket = serverSocket.accept();//这里要为每个即将连接的客户端新建一个socket,注:必须在try内部声明socket ClientThread clientThread = new ClientThread(socket);//每接收到一个服务器请求,就为其新建一个客户线程 String ClientNickName = clientThread.getClientNickName(); clientThreads.put(ClientNickName, clientThread);//将每个客户端的线程都存在ConcurrentHashMap中 Success("为用户"+ClientNickName+"新建线程完毕"); } catch (IOException e) { Error("Server:建立客户线程失败"+e.getMessage()); } }else { Error("Server:服务器socket已关闭"); } } } } //6.客户端线程 public class ClientThread implements Runnable{//为每个客户端建立线程,并存在ConcurrentHashMap中,建立新的线程可防止等待用户输入时死锁 //6.1 与该用户的socket相关输入输出 private Socket socket; private BufferedReader input; private PrintStream output; //6.2 由于服务器主线程需要以客户端姓名为key,故需要调用getClientNickName()返回昵称 private String ClientNickName; //6.3用于区分第一次连接和之后接收的消息 boolean isRuning = false; //6.4 构造函数 public ClientThread(Socket clientSocket) { this.socket = clientSocket; isRuning = Initialize(); new Thread(this).start(); } //6.5 第一次生成线程时(用户刚登陆时)调用 public synchronized boolean Initialize() { try { input = new BufferedReader(new InputStreamReader(socket.getInputStream())); output = new PrintStream(socket.getOutputStream()); //6.5.1 接收用户的输入数据 String clientInputStr; clientInputStr = input.readLine();//readline运行时阻塞,故须建立客户端线程 Log("Client:"+clientInputStr); //6.5.2 检验信息头是否为LOGIN,是则向所有其他用户转发 Tokenizer tokens = new Tokenizer(clientInputStr,"@"); String MessageType = tokens.nextToken(); if("LOGIN".equals(MessageType)) { ClientNickName = tokens.nextToken(); OnlineClientNickName.addElement(ClientNickName);//服务端在线用户列表显示该用户昵称 Broadcast(clientInputStr);//向所有已登陆用户广播该用户登陆的信息 for(ConcurrentHashMap.Entry entry: clientThreads.entrySet()) { SendMessage("LOGIN@"+entry.getKey()); } }else { Error("Server:初次接收的消息不为LOGIN"); } } catch (IOException e) { // TODO Auto-generated catch block Error("Server:Initialize无法读取下一行 "+e.getMessage()); } Success("用户线程初始化完毕"); return true; } @Override //6.6 线程开始时自动调用该方法 public void run() { while(isRuning) { try { //6.6.1接收用户的输入数据 String clientInputStr; clientInputStr = input.readLine(); Log("Server:"+clientInputStr); //6.6.2按信息头部分类处理 Tokenizer tokens = new Tokenizer(clientInputStr,"@"); String MessageType = tokens.nextToken(); switch(MessageType) { case "MESSAGE":{//消息 String ToClientNickName = tokens.nextToken(); if(ToClientNickName.equals("ALL")) { //对消息进行广播转发 Broadcast(clientInputStr); tokens.nextToken(); Log("Server:已将消息广播转发,消息内容为"+tokens.nextToken()); }else { //对消息进行一对一的转发 clientThreads.get(ToClientNickName).SendMessage(clientInputStr); Log("Server: 已将来自"+tokens.nextToken()+"的消息"+tokens.nextToken()+"转发给"+ToClientNickName); } break; } case "LOGOUT":{//登出 Broadcast(clientInputStr); Log("Server:用户"+tokens.nextToken()+"退出"); break; } default : { Error("Server: 服务器收到的消息格式错误"); break; } } } catch (IOException e) { // TODO Auto-generated catch block Error("Server:run无法读取下一行 "+e.getMessage()); } } } //6.7 返回该用户昵称 public String getClientNickName() { return ClientNickName; } //6.8 发送消息 public void SendMessage(String Message) { output.println(Message); output.flush(); } //6.9 向全体在线账号发送消息 public void Broadcast(String Message) { for(ConcurrentHashMap.Entry entry: clientThreads.entrySet()) { entry.getValue().SendMessage(Message); } } } //7. 消息分割器 public class Tokenizer{ String Tokens[]; int TokenIndex = 0; //7.1 将消息Message按照Delimiter分割 public Tokenizer(String Message, String Delimiter) { Tokens = Message.split(Delimiter); } //7.2 返回下一个内容 public String nextToken() { TokenIndex++; return Tokens[TokenIndex-1]; } } public static void main(String arg[]) { Server server = new Server(); } }



你可能感兴趣的:(开发软件,SOCKE,JAVA)