基于Java Socket的局域网聊天系统

实验目标:

1、Java实现一个基于TCP/UDP的网络聊天系统-添加UI和使用持久化技术

2、本系统的持久化技术是使用文件存储,存放到一个excel表格中

3、本系统实现了一个服务器端-多线程监听;客户端可与服务器端实现通信--通信图如下图。

4、其中:本系统实现了局域网通信,如果需要使用本系统,请到通信配置处修改通信IP

项目已开源:

        gitee: https://gitee.com/TangGarlic/socket-chat-system.git

        github: https://github.com/TonyTang-dev/javaSocketChatSystem.git

如有改进需求和改进想法,可与作者联系。

实现效果:

1、socket:socket叫套接字,由IP地址和端口号共同组成。每一条TCP 连接唯一地被通信两端的两个端点即两个套接字所确定,形如:

2、端口号:即协议端口号,通常简称为端口(port),通信的终点是应用 进程,可以通过把端口想象成为通信的终点,因为只需要把传送的报文交到目的主机的某一个合适的目的端口,剩下的工作即最后交付目的进程就由TCP完成。在协议栈层间的抽象的协议端口是软件端口,而硬件端口是不同硬件设备进行交互的端口,而软件端口是应用层的各种协议进程与传输实体进行层间交互的一种地址。端口用一个16位端口号进行标志,只具有本地意义,即端口号只是为了标志本计算机应用层中的各进程;在互联网中,不同计算机的相同端口号没有联系。因此两台计算机中的进程要相互通信,不仅需要知道对方的IP地址以找到对方计算机,而且还要知道对方的端口号以找到对方计算机中的应用进程。 3、TCP:即传输控制协议,传送的数据单位协议是TCP报文段,是一 种面向连接的协议,提供面向连接的服务,传送的数据单位协议是TCP报文段,不提供广播或多播服务;由于TCP要提供可靠的、面向连接的传输服务,因此不可避免地增加了许多的开销,这不仅使协议数据单元的首部增大很多,还要占用许多的处理及资源。TCP报文段是在传输层抽象的端到端逻辑信道中传送,是可靠的全双工信道。但这样的信道却不知道究竟经过了那些路由器,而这些路由器也根本不知道上面的传输层是否建立了TCP连接。

4、UDP:即用户数据报协议,传输的数据单位协议是用户数据报,是 一种无连接协议,在传输数据之前不需要先建立连接,传送的数据单位协议是UDP报文或用户数据报;对方的传输层在收到UDP报文后不需要给出任何确认,虽然UDP不提供可靠交付,但在某些情况下UDP是一种最有效的工作方式。UDP用户数据包与网络层的IP数据报有很大差别主要是IP数据报要经过互联网中许多路由器的存储转发,UDP用户数据包是在传输层的端到端抽象的逻辑信道中传送的。 5、注意:UDP传输是基于报文的,而TCP传输是面向字节流的,其中 TCP的字节流中的“流”是指流入或流出进程的字节序列,面向字节流的含义是虽然应用程序和TCP的交互是一次一个数据块,但TCP把应用程序交下来的数据看成仅仅是一连串无结构的字节流。

实现: 1、因为目前还没有直接连接公网IP的功能,我们采用的是基于局域 网的聊天系统。其中通过连接同一个路由器(本次使用一个手机移动热点作为无线路由器),客户端和服务端都连接至此局域网以实现物理组网。 2、我们对系统的设计为:客户端各个端既是服务器又是客户机,即 客户端之间可以直接收发消息和传输文件,实现的一个端到端的系统;与此同时,为了解决客户之间需要寻找陌生用户和与陌生用户建立通信的需求,我们搭建了一个服务器,供客户端访问服务端获得陌生人信息,服务器下发信息到客户端。客户端之间的通信我们使用的是面向无连接的UDP通信,而客户机与服务器之间的通信我们使用的是面向连接的TCP通信,建立的逻辑框图如下:

3、我们采用的开发语言是Java程序设计语言,其中用于实现UDP通 信的算法,如下所示:

如图所示,实现的是UDP的通信连接请求发送过程,标号1是我们实现的一个序列化类,用于存储需要发送的信息如文本,文件等等;标号2是通过InetAddress来设置需要连接的用户的IP地址,通过获得port来设置对方的软件端口,并通过socket发送出去,用户接受的程序实现如下:

其中通过获取输入流并解析就可以得到传输的内容。同理,基于TCP通信的发送请求和接受内容实现如下所示(以访问服务器为例):

我们采用的序列化发送类如下所示,其中Cmd是指自定义的一些识别代码,例如以1024代表的是发送文本,对方接受到内容并解析得到Cmd是1024后就知道要接受文本;还分别存储了己方账号和对方账号,便于双方知道通信双方的信息;其余变量类似,即存储文件或文件名以及存储发送的字节等等,最后一个向量存储的是访问服务器后得到的用户信息。

服务端因为要访问来自用户的访问,所以必须要实现多线程通信,所以我们加入了多线程编程(客户端也实现了),每个用户访问服务器都会启动一个线程来响应,线程体实现如下:

完成基本的框架搭建之后,为了增加系统的可视化性,增加了UI设计,提高用户使用体验感,便于用户直接使用;同时,由于系统需要存储用户信息避免再次返回系统时好友信息丢失,我们采用文件存储的方式来是实现数据持久化,即将用户(好友)数据保存于文件中,通过读写文件实现数据保存,实现效果如下含文件表:(以好友列表为例)

报文发送和文件处理部分示例代码,详情查看代码仓库:

package ht.ui;
//主窗口
import ht.bean.Account;
import ht.cmd.Cmd;
import ht.db.DBOper;
import ht_.Send;
import ht_.SendMsg;
 
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.Vector;
 
import javax.swing.AbstractListModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
//启动线程 进入循环等到接收消息
public class MainUI extends JFrame implements MouseListener,ActionListener{
 
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	
	private Account account,friendAccount;
	private JTabbedPane tab;
	private JLabel lblhead;//登陆后显示的个人资料
	private JList<?> lstFriend;
	private JList<?> lstFamily;
	private JList<?> lstclassmate;
	private JList<?> lsthmd;
	private JButton btnFind;//查找好友按钮
	private Vector<Account> vFriend,vFamily,vClassmate,vHmd,vAllDetail;
	private JPopupMenu pop;//弹出菜单
	private JMenu menu;
	private JMenuItem miChat,miLookInfo,miFriend,miFamily,miMate,miHmd,miDel;//聊天 查看资料 删除好友 移动好友
	//创建哈希表保存所有在线用户的窗口
	private LinkedHashMap<Integer,ChatUI> ht_ChatUsers;
	
	
	public MainUI()
	{
		
		
	}
	public MainUI(Account acc) throws IOException
	{
		this.account=acc;
		
		setTitle(acc.getNickName());//设置窗口标签栏标题和头像
		setIconImage(new ImageIcon(acc.getFace()).getImage());
		//获取昵称 备注 QQ号 个性签名
		String str="";
		//备注不为空时,显示备注不显示昵称
		if(!acc.getRemark().equals(""))
		{
			str=acc.getRemark()+"(";
		}
		//备注为空时,显示昵称
		else {
			str=acc.getNickName()+"(";
		}
		str+=acc.getQqCode()+")"+acc.getSelfsign();	
		lblhead=new JLabel(str,new ImageIcon(acc.getFace()),JLabel.LEFT);
		add(lblhead,BorderLayout.NORTH);
		
		vFriend=new Vector<Account>();
		vFamily=new Vector<Account>();
		vClassmate=new Vector<Account>();
		vHmd=new Vector<Account>();
		vAllDetail=new Vector<Account>();
		
		
		lstFamily=new JList();
		lstFriend=new JList();
		lstclassmate=new JList();
		lsthmd=new JList();
		
		lstFriend.addMouseListener(this);
		lstFamily.addMouseListener(this);
		lstclassmate.addMouseListener(this);
		lsthmd.addMouseListener(this);
		
		
		
		refresh();//读好友列表
		
		tab=new JTabbedPane();
		tab.add("好友",new JScrollPane(lstFriend));
		tab.add("家人",new JScrollPane(lstFamily));
		tab.add("同学",new JScrollPane(lstclassmate));
		tab.add("其他",new JScrollPane(lsthmd));
		add(tab);
		createMenu();
		btnFind=new JButton("查找/添加好友");
		add(btnFind,BorderLayout.SOUTH);
 
		btnFind.addActionListener(this);
		setSize(300,680);
		setVisible(true);
		setResizable(true);
		//得到屏幕宽度,设置窗口位置
		int width=Toolkit.getDefaultToolkit().getScreenSize().width-300;
		setLocation(width,50);
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		
		//监听叉关闭
		this.addWindowListener(new WindowAdapter(){
            @Override
            public void windowClosing(WindowEvent e) {
               //super.windowClosing(e);    //To change body of overridden methods use File | Settings | File Templates.
               //这里写你想实现的代码,我只写了一行println
                System.exit(0);
            }
        });
		
		//启动消息接收线程
		new ReceThread().start();
	}
	
	
	class listmodel extends AbstractListModel
	{
		Vector dats;
		public listmodel(Vector dats)
		{
			this.dats=dats;
		}
		//获取行数
		public Object getElementAt(int index)
		{
			Account user=(Account) dats.get(index);
			return user.getNickName().trim()+"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值