基于Java Swing与TCP/IP协议完成的简单聊天程序

一.功能介绍

   首先,要完成多人聊天的程序,我们要先明白其运行的基本机制是什么?
在这里插入图片描述
该图源自网上

   当两个用户进行通讯时,是采用C/S模式进行的。也就是说,当两个客户端进行通讯时,并不止是两个用户之间建立了tcp链接,而是当客户端登录服务器时,服务器获取了客户端的Socket,并存储下来。当两个客户端想要通讯时,服务器拿出这两个客户端的socket后,再进行通讯。服务器的功能有点类似于中转站

   因此,不难预见,该程序也要用到多线程的调度,以及数据库简单的增删改查操作。
功能图
   此外,该程序还简单实现了文件的传输功能。

二、项目结构与程序简单的展示

在这里插入图片描述

二.(1)登录界面

在这里插入图片描述
二.(2)用户好友列表界面
在这里插入图片描述
   该图展示了三个用户的界面,方便分析。

   从左到右依次展示的是:

好友列表:用户添加的所有好友。
对等方列表:展示的是已经上线的对等方。
在线的好友:用户当前在线的好友。
可以得知的是,对等方列表是根据只要有一个对等方上线即把它加入列表,并不会判断新上线的对等方是不是自己的好友。这也是对等方列表与在线的好友的主要区别。

二.(3)聊天界面
在这里插入图片描述
   一个用户可以同时与多个用户同时在线聊天

   当在聊天窗口点击选择文件发送的按钮时,此时你即为发送方,跟你聊天的那个人为接收方。接收方的窗口会弹出一个是否接收文件的请求,
在这里插入图片描述
在这里插入图片描述
   发送方弹出选择文件的窗口

   发送方选择完后,接收方选择保存文件的路径
在这里插入图片描述
 点击发送

在这里插入图片描述
在这里插入图片描述
 上述打印的相同的数据是用来缓冲文件的字节数组,一个数组大小1KB(该文件大小7KB),上传于下载完毕后,发送方和接收方都会在控制台打印出相关信息。

二.(4)用户个人界面(其余功能)

 该界面可以实现对好友的操作,群聊、聊天记录、画板等功能,本文主要讲述核心功能的实现,在此略过。
在这里插入图片描述

二、部分功能的代码实现


import java.io.*;
import java.net.*;

import pers.fjl.communication_system.Thread.ConnectThread;
import pers.fjl.communication_system.Thread.adminConnectThread;
import pers.fjl.communication_system.transfer_files.Download;
import pers.fjl.communication_system.transfer_files.Jdbc_downloadpath;
import pers.fjl.communication_system.utils.name;

public class server implements java.io.Serializable {
		private static int i=0;
		private static String DownloadPath=null;

	public void Startserver() {
		try {

			System.out.println("服务器启动,端口是5228");
			ServerSocket ss=new ServerSocket(5228);
			while(true) {
			//阻塞,等待连接			
			Socket s = ss.accept();
				ObjectInputStream ois=null;
				String downloadpath=Jdbc_downloadpath.getDownloadpath();
				if(!downloadpath.equals("0")){
					InputStream is=s.getInputStream();  //获取发送方的流
//					ObjectInputStream is=new ObjectInputStream(s.getInputStream());
					System.out.println(downloadpath);
					FileOutputStream fos=new FileOutputStream(downloadpath,true);
					System.out.println("Download2");
					byte[] b=new byte[1024];
					int len;
					while ((len=is.read(b))!=-1){
						fos.write(b,0,len);
					}
					System.out.println("数据下载完成");
					Jdbc_downloadpath.deleteDownloadpath(downloadpath);
				}else {
						ois=new ObjectInputStream(s.getInputStream());
						name n=(name)ois.readObject();	//强转从客户端获取对象o
//			System.out.println("服务器启2收到id"+n.getId()+n.getPassword());
						ConnectThread ct=new ConnectThread(s);	//启动与该客户说话的线程
						Download dl=new Download(s);
//				dl.start();
						adminConnectThread.addConnectThread(n.getId(),ct);//获取用户名和线程加入到哈希表

						ct.start();
						ct.tellalluser(n.getId());
				}
			}
		} catch (Exception  e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}		
	}

	}

服务器端


import java.io.IOException;
import java.io.*;
import java.net.*;

import pers.fjl.communication_system.Thread.CSThread;
import pers.fjl.communication_system.Thread.adminCSThread;
import pers.fjl.communication_system.udp.getHostIp;
import pers.fjl.communication_system.utils.name;

public class client implements java.io.Serializable{	
	public Socket s;
	private String name;
	private String password;
	
	public client(Object o) {	//采用对象流,从user_login类获取	
		try {			
			s=new Socket("127.0.0.1",5228);
			new getHostIp();
			ObjectOutputStream oos=new ObjectOutputStream(s.getOutputStream());
			oos.writeObject(o);			
			CSThread cst=new CSThread(s);//创建该用户和服务器保持通讯的线程
			cst.start();
			adminCSThread.addCSThread(((name)o).getId(), cst);	//得到o的用户名
			
		} catch (UnknownHostException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}
	

}

客户端

 需要注意的是,当服务器和客户端启动时,都要启动管理socket的线程,以便管理服务器与客户端之间的连接,在此我使用哈希表管理线程。(用户名为键)


import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;

import javax.swing.*;

import pers.fjl.communication_system.Thread.adminCSThread;
import pers.fjl.communication_system.Thread.adminfriendlist;
import pers.fjl.communication_system.udp.peer;
import org.springframework.jdbc.core.JdbcTemplate;
import pers.fjl.communication_system.client.client;
import pers.fjl.communication_system.utils.*;


public class user_login extends JFrame implements java.io.Serializable{	//登录界面,先执行user_login()方法
	private static final long serialVersionUID=1L;		//实现序列化不同版本的兼容性
	protected  String  correctname;	
	protected  String correctpassword;		//声明两个变量,用于传给子类启动数据库
	protected boolean flag;
	public static boolean flag2;	//用来传递是否从数据库查询成功的真值
	public static boolean flag3;	//用来传递是否从数据库查询成功的真值
	
	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}

	public String getCorrectname() {
		return correctname;
	}

	public void setCorrectname(String correctname) {
		this.correctname = correctname;
	}

	public String getCorrectpassword() {
		return correctpassword;
	}

	public void setCorrectpassword(String correctpassword) {
		this.correctpassword = correctpassword;
	}

	public user_login() {
		setTitle("登录窗体");			//窗体标题
		setBounds(800,400,300,150);		
		Container c=getContentPane();		//容器
		c.setLayout(null);
		JLabel jl1=new JLabel("用户名:");		//标签
		jl1.setBounds(10, 10, 200, 18);
		
		final JTextField name = new JTextField();	
        name.setBounds(80, 10, 150, 18);
		
        JLabel jl2=new JLabel("密码:");
		jl2.setBounds(10, 50, 200, 18);
		
		final JPasswordField password=new JPasswordField();		//创建密码框对象
		password.setBounds(80,50 , 150, 18);	//设置位置大小
		c.add(jl1);
		c.add(name);
		c.add(jl2);
		c.add(password);			//全部加到容器中
		
		JButton jb=new JButton("确定");
		jb.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				user_login a=new user_login();			//创建对象
				a.dispose();			//关掉因实例化a而多出来的窗口
                a.setCorrectname(name.getText().trim());	
                System.out.println(a.getCorrectname());
                char data[]=password.getPassword();
                a.setCorrectpassword(new String(data));	//用户名和密码
                
                new  user_login(a.getCorrectname(),a.getCorrectpassword());	
                System.out.println(flag2);      
                
                
				if(name.getText().trim().length()==0
						|| new String(password.getPassword()).trim().length()==0) {		//除去空格后,长度为0的话
					JOptionPane.showMessageDialog(null, "用户名或者密码不准为空");
					return;
				}
								
				if (flag2 && !flag3) {
						
                    JOptionPane.showMessageDialog(null, "登录成功");       
                    
                    JdbcTemplate template=new JdbcTemplate(druid_utils.getDataSource());
            		String sql="update user set onlinestatus=1 where username=?";
            		template.update(sql,a.getCorrectname());//下线时状态为0,用户上线则把状态改变为1
                    
                    correctname=name.getText().trim();
                    correctpassword=new String(password.getPassword());
                   
        			
                    close_login cl=new close_login();  //创建线程类的对象
                                           
                    try {
						Thread.sleep(800);			//	登录成功后,过0.8s关掉登录页面
						cl.start();	
											  
					} catch (InterruptedException e1) {
						// TODO 自动生成的 catch 块
						e1.printStackTrace();
					}
               	          
                } else if(flag2==false){
                    JOptionPane.showMessageDialog(null, "用户名或密码错误");	
                }else if(flag2 && flag3) {
                	JOptionPane.showMessageDialog(null, "该用户已在线,请勿重新登录");	
                }
			}
			
		});
		 jb.setBounds(80, 80, 60, 18);
	        c.add(jb);
	        
	        final JButton button = new JButton();
	        button.setText("重置");
	        button.addActionListener(new ActionListener() {
	            public void actionPerformed(ActionEvent arg0) {
	                // TODO 自动生成方法存根
	                name.setText("");			
	                password.setText("");		//清空文本框实现重置操作
	            }
	        });
	        button.setBounds(150, 80, 60, 18);
	        getContentPane().add(button);

	        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
	        setVisible(true);
	}
	
	public user_login(String username,String password) {
		// TODO 自动生成的方法存根	
		
		Connection conn=null;		
		ResultSet rs=null;
		PreparedStatement pstmt=null;
		
		Map<String, Object>map=null;
try {
	conn=jdbc_utils.getConnection();		//调用工具类,加载驱动获取连接
	String sql="select * from user where username= ? and password = ?";		//?来拼接
	pstmt=conn.prepareStatement(sql);

	pstmt.setString(1,username);
	pstmt.setString(2, password);				//给问号赋值
	rs =pstmt.executeQuery();			//执行查询表中的用户名和密码
	flag2=rs.next();			//如果有下一行,则返回true。表示如果能从数据库找到账号密码则,返回true,否则返回false
	
	try{
        JdbcTemplate template=new JdbcTemplate(druid_utils.getDataSource());
		String sql2="select * from user where username=? and onlinestatus =1";
		map=template.queryForMap(sql2,username);
		if(map!=null) {
			flag3=true;
			}
		}catch(Exception e) {
			flag3=false;		//搜索不到在线状态为1的该用户名,则会发生异常
		}

	
}catch (SQLException e) {
	// TODO 自动生成的 catch 块
	e.printStackTrace();
}finally {				//释放资源

	jdbc_utils.close(rs, pstmt, conn);			//	调用工具类,关闭资源

}	
		}
		
	public static void main(String[] args) {
		new user_login();		
	}
	
	class close_login extends Thread{	
		public void run(){
			user_login.this.dispose();		//登录成功后,用来自动关掉登录的页面
			name n=new name();
			n.setId(correctname);
			n.setPassword(correctpassword);

			new client(n);		//开启该客户端
			try {				
				friendlist f=new friendlist(correctname);	//	打开好友界面	
				adminfriendlist.addfriendlist(correctname, f);
				ObjectOutputStream oos=new ObjectOutputStream
						(adminCSThread.getCSThread(n.getId()).getS().getOutputStream());
			
				Message m=new Message();
				m.setMesType(MesType.MesType_onlinenum);	//发好友的状态
				m.setSender(correctname);//要的是刚登陆的用户的好友列表				
				oos.writeObject(m);
				
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}finally{
				new peer(correctname);		//会阻塞程序,放在最后
			}
			
		}	
}}

登录验证的窗体以及方法
 该方法代码有点冗余,看看就好。


import javax.swing.*;

import pers.fjl.communication_system.Thread.adminCSThread;
import pers.fjl.communication_system.Thread.adminchat;
import pers.fjl.communication_system.transfer_files.Transfer_file;
import pers.fjl.communication_system.transfer_files.Upload;
import pers.fjl.communication_system.chat_record.record;
import pers.fjl.communication_system.utils.Message;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class chat extends JFrame implements ActionListener,java.io.Serializable {	//与好友聊天的界面

	JTextArea jta;
	JTextField jtf;
	JButton jb1,jb2;	//发送按钮和选择文件夹的按钮
	JPanel jp;
	String myname;
	String friendname;
	//	public static void main(String[] args) {
//		// TODO 自动生成的方法存根
//		chat ct=new chat("s");
//	}
	public chat(String myname,String friend) {
		this.myname=myname;
		this.friendname=friend;
		jta=new JTextArea();
		jtf=new JTextField(30);
		jb1=new JButton("发送");
		jb1.addActionListener(this);
		jb2=new JButton("选择文件发送");
		jb2.addActionListener(this);
		jp=new JPanel();
		jp.add(jtf);
		jp.add(jb1);
		jp.add(jb2);

		this.add(jta,"Center");
		this.add(jp,"South");
		this.setTitle("你("+myname+")正在和"+friend+" 聊天");
		this.setBounds(800, 400, 550, 460);
		this.setVisible(true);
	}

	public void download_or_not(String friendname){//接收方弹出是否下载文件的确认框

		int isDelete = JOptionPane.showConfirmDialog(null, "请问是否接收?", friendname+"给你("+myname+")发来了文件", JOptionPane.YES_NO_CANCEL_OPTION);
		if(isDelete == JOptionPane.YES_OPTION){
			System.out.println("download or not我的名字"+myname+friendname);
			Transfer_file t=new Transfer_file();
			chat c=	adminchat.getchat(myname+" "+friendname);	//在friendlist的时候加入的哈希表的键值取出
			Transfer_file a=new Transfer_file();
			a.Sender_filepath(friendname,myname);
//			c.upload_or_not();		//弹出是否发送的确认框
//			t.Downloader_filepath(friendname,myname);		//下载人而言,他的朋友就是最初的发送者
		}
	}

	public void upload_or_not(String UploadPath,String Uploader){//发送方弹出是否发送文件的确认框

		int isDelete = JOptionPane.showConfirmDialog(null, "是", "开始发送?", JOptionPane.YES_NO_CANCEL_OPTION);
		if(isDelete == JOptionPane.YES_OPTION){
			System.out.println("upload_or_not"+Uploader);
			Transfer_file a=new Transfer_file();
			new Upload(UploadPath,Uploader);
//			a.Sender_filepath(String );
//			new Upload();
		}
	}

	public void showMessage(Message m) {
		String info="        "+m.getSender()+"对你("+m.getReceiver()+")说:"+m.getInfo()+"\r\n";
		String time=m.getSendtime();
		this.jta.append(time+"\n");	//加到自己的文本域上显示
		this.jta.append(info);	//加到自己的文本域上显示
		new record(m.getReceiver(),m.getSender(),info);//存储聊天记录
	}

	public void actionPerformed(ActionEvent e) {
		// TODO 自动生成的方法存根
		if(e.getSource()==jb1) {//如果点了发送
			Date d = new Date();	//用来获取时间
			SimpleDateFormat sdf = new SimpleDateFormat("yy年MM月dd日 E HH:mm:ss");
			String msg="        【你对"+friendname+"说】:"+jtf.getText();
			new record(myname,friendname,msg);//存储聊天记录
			this.jta.append(sdf.format(d)+"\n");
			this.jta.append(msg+"\n");
			Message m=new Message();
			m.setSender(this.myname);
			m.setReceiver(this.friendname);
			m.setInfo(jtf.getText());
			m.setMesType("3");//这是信息包
			jtf.setText("");

			m.setSendtime(sdf.format(d));
//			m.setSendtime(new java.util.Date().toString());
			System.out.println("这里是chat类"+m.getSender());

			try {	//通过类取得线程,通过线程取得Socket
				ObjectOutputStream oos=new ObjectOutputStream
						(adminCSThread.getCSThread(myname).getS().getOutputStream());	//这是客户端的Socket s产生的输出流,只有服务器端的输入流才能获取

				oos.writeObject(m);
			} catch (IOException e1) {
				// TODO 自动生成的 catch 块
				e1.printStackTrace();
			}
		}else if(e.getSource()==jb2){//选择文件发送

			try {
				Transfer_file a=new Transfer_file();
				ObjectOutputStream oos=new ObjectOutputStream
						(adminCSThread.getCSThread(myname).getS().getOutputStream());     //这里要修改的
				Message m=new Message();
				m.setSender(this.myname);
				m.setReceiver(this.friendname);
				m.setMesType("6");  //这是传输文件的包
				oos.writeObject(m);         //发送给服务器
//				a.Sender_filepath();	//选择要发送的文件
			} catch (IOException ioException) {
				ioException.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
//		new chat("张三","李四");

	}
}

chat聊天窗体以及里面的方法

下面是两个管理通讯的线程:


import java.net.*;

import pers.fjl.communication_system.Thread.adminchat;
import pers.fjl.communication_system.Thread.adminfriendlist;
import pers.fjl.communication_system.settings.chat;
import pers.fjl.communication_system.settings.friendlist;
import pers.fjl.communication_system.utils.MesType;
import pers.fjl.communication_system.utils.Message;

import java.io.*;

public class CSThread extends Thread {//客户端连接服务端的线程
	private Socket s;

	public CSThread(Socket s) {
		this.s=s;
	}

	public Socket getS() {
		return s;
	}
	public void setS(Socket s) {
		this.s = s;
	}

	public void run() {
		System.out.println("CSThread启动了");
		while(true) {
			//不停读取从服务端发来的消息
			try {

//				String downloadpath= Jdbc_downloadpath.getDownloadpath();
					//如果不是对象流则不作任何处理
					ObjectInputStream ois=new ObjectInputStream(s.getInputStream());
					Message m=(Message)ois.readObject();

					if(m.getMesType().equals(MesType.MesType_returnnum)) {
						System.out.println("CST读取从服务器发来的消息"+m.getInfo());
						String info=m.getInfo();	//返回在线人的列表
						String friends[]=info.split(" ");//拆分
						String receiver=m.getReceiver();//刚才的发送者要接收传回去的好友列表
						friendlist fr= adminfriendlist.getfriendlist(receiver);//返回一个好友列表
						if(fr!=null) {
							//代表这是返回好友列表的,里面的包并不是用来传递聊天信息
						}
					}else if(m.getMesType().equals(MesType.MesType_transfer_file)){
						System.out.println("接收方收到了发送方发送文件的请求");
						chat c=	adminchat.getchat(m.getReceiver()+" "+m.getSender());	//在friendlist的时候加入的哈希表的键值取出
						c.download_or_not(m.getSender());		//弹出是否下载的确认框
					}

					else {//好友列表是空的,代表里面的包是用来传递客户端间的聊天讯息
						chat c=	adminchat.getchat(m.getReceiver()+" "+m.getSender());	//在friendlist的时候加入的哈希表的键值取出
						c.showMessage(m);
					}


			} catch (Exception e) {
				// TODO 自动生成的 catch 块
//				e.printStackTrace();
//				System.out.println("CSThread出现异常");
			}

		}
	}

}

管理客户端连接服务器的线程


import java.net.*;
import java.util.*;

import pers.fjl.communication_system.utils.MesType;
import pers.fjl.communication_system.utils.Message;

import java.io.*;

public class ConnectThread extends Thread implements java.io.Serializable{//服务器和某个客户端的通讯线程
	Socket s;

	public ConnectThread(Socket s) {//给线程连接通道,获取客户端的s
		this.s=s;
	}

	public void tellalluser(String own) {//另外一个用户登录后,刷新上线的表,告诉以前的人上线请求
		HashMap hm=adminConnectThread.hm;
		Iterator it=hm.keySet().iterator();

		while(it.hasNext()) {//取出在线人的列表
			Message m=new Message();
			m.setInfo(own);
			m.setMesType(MesType.MesType_returnnum);

			String onlineuser=it.next().toString();
			try {
				ObjectOutputStream oos=new ObjectOutputStream(adminConnectThread.getConnectThread(onlineuser).s.getOutputStream());
				m.setReceiver(onlineuser);//把包给已经上线的人
				oos.writeObject(m);
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}

		}
	}

	public void run() {
		System.out.println("线程运行了");
		while(true) {
			try {

				ObjectInputStream ois=new ObjectInputStream(s.getInputStream());
				Message m=(Message)ois.readObject();	//chat那里,服务器拿到的

				if(m.getMesType().equals(MesType.MesType_onlinenum)){//把服务器上线的人给客户返回

					String res=adminConnectThread.getonlineuser();
					Message m2=new Message();
					m2.setMesType(MesType.MesType_returnnum);
					m2.setInfo(res);
					m2.setReceiver(m.getSender());//最初的发送者此时变为接受者接回服务器发回给其的好友信息
					ObjectOutputStream oos=new ObjectOutputStream(s.getOutputStream());//s是接收信息的人与服务器的连接
					oos.writeObject(m2);
				}else if (m.getMesType().equals(MesType.MesType_transfer_file)){//传输文件的
					System.out.println("ConnectThread 收到发送方"+m.getSender()+"给"+m.getReceiver()+"的消息");
					Message m3=new Message();
					m3.setMesType(MesType.MesType_transfer_file);
					m3.setSender(m.getSender());
					m3.setReceiver(m.getReceiver());	//
					//应该输出把流输出给接收方
//					ObjectOutputStream oos=new ObjectOutputStream
//							(adminCSThread.getCSThread(m.getReceiver()).getS().getOutputStream());
					ConnectThread ct=adminConnectThread.getConnectThread(m.getReceiver());//找到接收人的通讯线程,并给她
					ObjectOutputStream oos=new ObjectOutputStream(ct.s.getOutputStream());//s是接收信息的人与服务器的连接
//						ObjectOutputStream oos=new ObjectOutputStream(s.getOutputStream());//s是接收信息的人与服务器的连接
					oos.writeObject(m3);
				}
				else{
					ConnectThread ct=adminConnectThread.getConnectThread(m.getReceiver());//找到接收人的通讯线程,并给她
					ObjectOutputStream oos=new ObjectOutputStream(ct.s.getOutputStream());//s是接收信息的人与服务器的连接
					oos.writeObject(m);//给接收人的包
				}

//
			} catch (Exception e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}

		}
	}
}

服务器连接客户端的线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值