计算机网络 课设——实时网络通讯工具

本文描述了一个基于Java的局域网即时通讯工具的设计,包括客户端和服务端的实现。客户端能连接到服务器,发送和接收信息,而服务器端负责接收和转发信息,管理连接的客户端。设计中涉及了Socket编程,多线程以及图形用户界面的构建。服务端具备记录和踢人功能,但不支持发言。程序未包含心跳包检测,建议后续完善。
摘要由CSDN通过智能技术生成

一、任务描述及设计要求

【设计目的】
1.熟悉开发工具 (Visual Studio、C/C++、Java等)的基本操作;
2.了解基于对话框的windows/Linux应用程序的编写过程;
3.对于Socket编程建立初步的概念。

【设计要求】
1.熟悉Socket API主要函数的使用;
2.掌握相应开发工具对Socket API的封装;
3.制作基于局域网的一对一网络即时通讯工具,实现基本数据的网络传输。

二、开发环境与工具

Eclipse IDE,jdk1.8.0_231

四、系统功能描述及软件模块划分

1、服务端多线程接收信息
主要是接收客户端发送过来的信息,通过创建多个线程并行地接收信息并显示在服务器端,可以开启服务和结束服务,可以中止对某一个客户端的服务,将从客户端接受到的信息发送到与服务器连接的其他客户端上。可以看到所有连接在此服务器的客户端。

2、客户端发送信息
连接服务器,可以断开与服务器的连接,可以看到连接到同一服务器的其他客户端,可以输出信息,可以看到自己和其他客户端输出的信息。

八、软件使用说明

本软件用于实时网络通讯,服务器端无法发言,仅有客户端可以发言,服务器端起记录、连接、踢人等功能,客户端仅能进行聊天,本软件的功能近乎于QQ群,但与QQ群并不相同,虽然有记录功能,但不会将所有的信息都同步到客户端,只会将客户端在线期间的信息同步到客户端上。对于用户端的连接,服务器不会设置限制,在同一局域网下的所有角色都可以进行连接,用户可以随意更换昵称,但不会同步到以前的信息上。

第一个client:

package Web;

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;
 
public class web_clientone{
	public static void main(String[] args) {
		Myclient one = new Myclient();
        one.web_client(8848,"127.0.0.1","client_one");
    }
}

@SuppressWarnings("serial")
class Myclient extends JFrame {//客户机窗体类
    //该图形界面拥有四块区域,分别位于上、左、中、下(up、Left、middle、down)。
    private JPanel Up = new JPanel();
    private JPanel Left = new JPanel();
    private JPanel Mid = new JPanel();
    private JPanel Down = new JPanel();
 
    //Up区域的子节点定义,3个标签、3个输入框、2个按钮
    private JLabel lblLocalPort1 = new JLabel("Server IP: ");
    private JLabel lblLocalPort2 = new JLabel("Port: ");
    private JLabel lblLocalPort3 = new JLabel("My nickname: ");
    protected JTextField tfLocalPort1 = new JTextField(15);
    protected JTextField tfLocalPort2 = new JTextField(5);
    protected JTextField tfLocalPort3 = new JTextField(5);
    protected JButton butStart = new JButton("Connect server");
    protected JButton butStop = new JButton("Disconnect Server");
 
    //Left区域的子节点定义,显示框、滚动条
    protected JTextArea Message = new JTextArea(28, 25);
    JScrollPane scroll = new JScrollPane(Message);
 
    //Mid区域的子节点定义,lstUsers在线用户界面
    JScrollPane jsp = new JScrollPane();
    @SuppressWarnings({ "rawtypes"})
	JList UserList = new JList();
 
    //Down区域的子节点定义,标签,输入框
    private JLabel lblLocalPort4 = new JLabel("Message (press Enter to send): ");
    protected JTextField tfLocalPort4 = new JTextField(20);
    //上面是图形界面变量,下面是存放数据的变量

    BufferedReader in;
    PrintStream out;
    public static int localPort;//默认端口
    public static String localIP;//默认服务器IP地址
    public static String nickname;//默认用户名
    public Socket socket;
    public static String msg;//存放本次发送的消息
    Vector<String> clientNames = new Vector<>();
    boolean JudgeLink = false;
    public void web_client(int Port,String IP,String niname) {
    	localPort = Port;
    	localIP = IP;
    	nickname = niname;
        init();
    }
 
    //初始化方法:初始化图形界面
    private void init() {
        //Up区域初始化:流式面板,3个标签、3个输入框,2个按钮
        Up.setLayout(new FlowLayout());
        Up.add(lblLocalPort1);
        Up.add(tfLocalPort1);
        Up.add(lblLocalPort2);
        Up.add(tfLocalPort2);
        Up.add(lblLocalPort3);
        Up.add(tfLocalPort3);
        tfLocalPort1.setText(localIP);
        tfLocalPort2.setText(String.valueOf(localPort));
        tfLocalPort3.setText(nickname);
        Up.add(butStart);
        Up.add(butStop);
        butStart.addActionListener(new linkServerHandlerStart());
        butStop.addActionListener(new linkServerHandlerStop());
        butStop.setEnabled(false);//断开服务器按钮的初始状态应该为不可点击,只有连接服务器之后才能点击
 
        //添加Left
        Message.setEditable(false);
        Left.add(scroll);
        Left.setBorder(new TitledBorder("Chat -- message area"));
        scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
 
        //添加Middle
        Mid.setBorder(new TitledBorder("Online users"));
        jsp.getViewport().setView(UserList);
        UserList.setVisibleRowCount(28);
        Mid.add(jsp);
 
        //添加Down,JTextField输入框的回车事件默认存在,无需添加
        Down.setLayout(new FlowLayout());
        Down.add(lblLocalPort4);
        Down.add(tfLocalPort4);
        tfLocalPort4.addActionListener(new Myclient.SendHandler());
 
        //图形界面的总体初始化 + 启动图形界面
        this.setTitle("Client");
        this.add(Up, BorderLayout.NORTH);
        this.add(Left, BorderLayout.WEST);
        this.add(Mid, BorderLayout.CENTER);
        this.add(Down, BorderLayout.SOUTH);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.addWindowListener(new WindowHandler());
        this.setPreferredSize(new Dimension(800, 600));
        this.pack();
        this.setVisible(true);
    }
 
    //“连接服务器”按钮的动作事件监听处理类:
    private class linkServerHandlerStart implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            //当点击"连接服务器"按钮之后,该按钮被禁用(不可重复点击)。同时"断开服务器按钮"被恢复使用 
            localIP = tfLocalPort1.getText();
            localPort = Integer.parseInt(tfLocalPort2.getText());
            nickname = tfLocalPort3.getText();
            linkServer();//连接服务器
            if(JudgeLink) {
            	butStart.setEnabled(false);
                butStop.setEnabled(true);
                Thread acceptThread = new Thread(new Myclient.ReceiveRunnable());
                acceptThread.start();
            }
        }
    }
 
    private class linkServerHandlerStop implements ActionListener {//“断开服务器”按钮的动作事件监听处理类
        @Override
        public void actionPerformed(ActionEvent e) {//当点击该按钮之后,断开服务器连接、清空图形界面所有数据    
            Message.append("==== You have exited ====\n");
            clientNames = new Vector<>();
            updateUsers();
            out.println("——Customer ["+nickname+"] leaves:bye\n");
            butStart.setEnabled(true);
            butStop.setEnabled(false);
        }
    }
    
    //连接服务器的方法
    public void linkServer() {
        try {
            socket = new Socket(localIP, localPort);
            JudgeLink = true;
        } catch (Exception ex) {
            Message.append("==== Failed to connect to the server ====\n");
        }
    }
 
    //接收服务器消息的线程关联类
    private class ReceiveRunnable implements Runnable {
        public void run() {
            try {
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintStream(socket.getOutputStream());
                out.println(nickname);//当用户首次连接服务器时,应该向服务器发送自己的用户名、方便服务器区分
                Message.append("——["+nickname+"] successfully connected to the server......\n");
                out.println("USERS");//向服务器发送请求,请求当前在线用户列表
                while (true) {
                    if ((msg = in.readLine())!= null) {//读取服务器端的发送的数据
                    	if (msg.matches(".*\\[.*\\].*")) {//此if语句的作用是:过滤服务器发送过来的 更新当前在线用户列表请求
                            clientNames.removeAllElements();
                            msg = msg.substring(msg.indexOf("[")+1,msg.indexOf("]"));
                            if(msg.contains(",")) { 	
                            	String[] split = msg.split(",");
                            	for(String ss : split) {
                            		ss = ss.replace(" ","");
                            		clientNames.add(ss);
                            	}
                            }
                            else {
                            	clientNames.add(msg);
                            }
                            updateUsers();
                            continue;
                        }
                    	Message.append(msg + "\n");//更新"聊天——消息区" 信息
                        //此 if 语句作用:与服务器进行握手确认消息。
                        //当接收到服务器端发送的确认离开请求bye 的时候,用户真正离线
                        msg = msg.substring(msg.lastIndexOf(":") + 1);        
                        if (msg.equals(nickname)) {
                        	socket.close();
                            clientNames.remove(nickname);
                            clientout();
                            break;//终止线程
                        }
                        else if( msg.equals("——Server Shutdown.")|msg.equals("——You've been kicked out.")) {
                        	socket.close();
                        	clientNames.removeAllElements();
                        	clientout();
                            break;
                        }
                    }  
                }
            } catch (Exception e) {
            	e.printStackTrace();
            }
        }
        
        public void clientout() {
        	updateUsers();
        	butStart.setEnabled(true);
            butStop.setEnabled(false);
            JudgeLink = false;
        }
    }
 
    //"发送消息文本框"的动作事件监听处理类
    private class SendHandler implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            out.println("【" + nickname + "】:" + tfLocalPort4.getText());
            tfLocalPort4.setText("");//当按下回车发送消息之后,输入框应该被清空
        }
    }
 
    private class WindowHandler extends WindowAdapter {//窗口关闭的动作事件监听处理类
        @Override// 当用户点击 "x" 离开窗口时,也会向服务器发送 bye 请求,目的是为了同步更新数据。
        public void windowClosing(WindowEvent e) {
        	if(JudgeLink) {
        		cutServer();
        	}
        }
    }
 
    private void cutServer() {
        out.println("——Client ["+nickname+"] leaves:bye");
        JudgeLink = false;
    }
 
    @SuppressWarnings("unchecked")
	public void updateUsers() {//更新 "在线用户列表" 的方法
        Mid.setBorder(new TitledBorder("Online users(" + clientNames.size() + ")"));
        UserList.setListData(clientNames);
    }
}

其余的client:

package Web;

public class web_clientone2{
	public static void main(String[] args) {
		Myclient one = new Myclient();
        one.web_client(8848,"127.0.0.1","client_two");
    }
}

server端:

package Web;

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

public class web_server{
	public static void main(String[] args) {
		Myserver one = new Myserver();
        one.web_server();
    }
}

@SuppressWarnings("serial")
class Myserver extends JFrame {
    private JPanel frameup = new JPanel();
    private JPanel framemidwest = new JPanel();
    private JPanel framecenter = new JPanel();
 
    private JLabel lblLocalPort = new JLabel("Local server listening port:");
    protected JButton butStart = new JButton("Start Server");
    protected JButton kickstart = new JButton("Kick out");
    protected JTextField tfLocalPort = new JTextField(30);
 
    protected JTextArea Message = new JTextArea(30, 45);
    JScrollPane scroll = new JScrollPane(Message);
 
    JScrollPane jsp = new JScrollPane();
	@SuppressWarnings({ "rawtypes" })
	JList UserList = new JList();
 
    public static int localPort = 8848;// 默认端口 
    static int SerialNum = 0;// 用户连接数量
    ServerSocket serverSocket;// 服务器端 Socket
    ArrayList<AcceptRunnable.Client> clients = new ArrayList<>();// 用户连接对象数组
    Vector<String> clientNames = new Vector<>();// UserList 中存放的数据

    public void web_server() {
        init();
    }
 
    private void init() {//初始化方法:初始化图形界面布局
    	frameup.setLayout(new FlowLayout());
    	frameup.add(lblLocalPort);
    	frameup.add(tfLocalPort);
    	frameup.add(butStart);
    	frameup.add(kickstart);
        tfLocalPort.setText(String.valueOf(localPort));
        butStart.addActionListener(new startServerHandler());
        kickstart.addActionListener(new kickoutHandler());
        kickstart.setEnabled(false);
 
        framemidwest.setBorder(new TitledBorder("Listen for messages:"));
        Message.setEditable(false);
        framemidwest.add(scroll);
 
        framecenter.setBorder(new TitledBorder("Online users:"));
        jsp.getViewport().setView(UserList);
        UserList.setVisibleRowCount(30);
        framecenter.add(jsp);    
 
        this.setTitle("Server side");
        this.add(frameup, BorderLayout.NORTH);
        this.add(framemidwest, BorderLayout.WEST);
        this.add(framecenter, BorderLayout.CENTER);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.addWindowListener(new WindowHandler());
        this.setPreferredSize(new Dimension(800, 600));
        this.pack();
        this.setVisible(true);
    }
 
    private class startServerHandler implements ActionListener {
        @Override//“启动服务器”按钮的动作事件监听处理类
        public void actionPerformed(ActionEvent e) {
            try {// 当点击按钮时,获取端口设置并启动新进程、监听端口      
                localPort = Integer.parseInt(tfLocalPort.getText());
                serverSocket = new ServerSocket(localPort);
                Thread acptThrd = new Thread(new AcceptRunnable());
                acptThrd.start();
                Message.append("**** Server (port "+localPort+") started ****\n");
                butStart.setEnabled(false);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    
    private class WindowHandler extends WindowAdapter{
    	public void windowClosing(WindowEvent e) {
    		if(UserList != null) {
    			for(Web.Myserver.AcceptRunnable.Client c : clients) {
    				c.out.println("——Server Shutdown."); 	
    			}//UserList.removeAll();
            }
        }
    }
    
    private class kickoutHandler implements ActionListener{
		@Override
		public void actionPerformed(ActionEvent e) {
			//Auto-generated method stub
			int select = UserList.getSelectedIndex();
			Object Nickname = UserList.getModel().getElementAt(select);
			for(Web.Myserver.AcceptRunnable.Client c : clients) {
				if(Nickname == c.nickname) {
					c.out.println("——You've been kicked out.");
					c.bekickout();
            		break;
				}
			}
		}	
    } 
    
    private class AcceptRunnable implements Runnable {//接受用户连接请求的线程关联类
        public void run() {//持续监听端口,当有新用户连接时 再开启新进程
            while (true) {
                try {
                    Socket socket = serverSocket.accept();//新的用户已连接,创建 Client 对象
                    Client client = new Client(socket);
                    Message.append("——Client【" + client.nickname + "】join\n");
                    Thread clientThread = new Thread(client);
                    clientThread.start();
                    clients.add(client);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
     
        //服务器存放用户对象的客户类(主要编程)。每当有新的用户连接时,该类都会被调用
        //该类继承自 Runnable,内部含有 run()方法
        private class Client implements Runnable {
            private Socket socket;//用来保存用户的连接对象
            private BufferedReader in;//IO流
            private PrintStream out;
            private String nickname;//保存用户昵称
 
            public Client(Socket socket) throws Exception {//Client类的构建方法。当有新用户连接时会被调用
                this.socket = socket;
                InputStream is = socket.getInputStream();
                in = new BufferedReader(new InputStreamReader(is));
                OutputStream os = socket.getOutputStream();
                out = new PrintStream(os);
                nickname = in.readLine();//获取用户昵称
                for (Client c : clients) {//将新用户的登录消息发给所有用户
                    c.out.println("——Client【" + nickname + "】join");
                }
            }
            
            public void bekickout() {
            	String usermsg = "——Client【" + this.nickname + "】have been kicked out.";
            	clients.remove(this);
            	for (Client c : clients) {
                    c.out.println(usermsg);
                }
            	Message.append("——Client【" + this.nickname + "】have been kicked out.\n");
            	updateUsers();
            }
            
            public void run() {//客户类线程运行方法
                try {
                    while (true) {
                        String usermsg = in.readLine();//读用户发来消息
                        if(usermsg != null) {
                        	String secondMsg = usermsg.substring(usermsg.lastIndexOf(":") + 1);// 字符串辅助对象 	
                            if (usermsg.length() > 0) {// 如果用户发过来的消息不为空
                                // 如果消息是 bye,则断开与此用户的连接并告知所有用户当前信息,跳出循环终止当前进程
                                if (secondMsg.equals("bye")) {
                                    clients.remove(this);
                                    Message.append("——Client leaves:" + nickname + "\n"); 
                                    for (Client c : clients) {
                                        c.out.println(usermsg);
                                        c.out.println("——Client leaves:" + nickname);
                                    }                   
                                    updateUsers();//更新在线用户数量UserList的界面信息
                                    break; 
                                }
     
                                if (usermsg.equals("USERS")) {//每当有新用户连接时,服务器就会接收到USERS请求
                                    updateUsers();//当服务器接收到此请求时,就会要求现在所有用户更新在线用户数量的列表
                                    continue;
                                }
                                
                                for (Client c : clients) {//当用户发出的消息都不是以上两者时,消息才会被正常发送
                                    c.out.println(usermsg);  
                                }
                                Message.append(usermsg+'\n');
                            }
                        }
                    }
                    socket.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
  
            @SuppressWarnings("unchecked")
			public void updateUsers() {//更新在线用户数量信息,并要求所有的用户端同步更新
                clientNames.removeAllElements();// clientNames是Vector<String>对象,用来存放所有用户的名字
                StringBuffer allname = new StringBuffer();
                for (AcceptRunnable.Client client : clients) {
                    clientNames.add(0, client.nickname);
                    allname.insert(0, "|" + client.nickname);
                }
                framecenter.setBorder(new TitledBorder("Online users number(" +clientNames.size() + ")"));
                
                for (Client c : clients) {//要求所有的用户端同步更新
                    c.out.println(clientNames);
                }
                UserList.setListData(clientNames);
                if(UserList.getModel().getSize()!=0) {
                	kickstart.setEnabled(true);
                }else {
                	kickstart.setEnabled(false);
                }
            }
        }
    }
}

改进方向:
课设没有做心跳包等类似的检测client与server是否连接的部分,最好在后续添加上。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值