之前学习完了网络和java跟网络的相关知识,想试着写点东西,可又无从下手....于是就跟着书上完成了这个聊天系统


该系统能够提供聊天和多人聊天,只要输入注册名和IP地址注册和选择聊天对象即可

信息服务器

信息服务器需要不断的检测新的客户端发来的请求,并且为已经连接的客户端提供服务,所以需要不断的执行1.接收请求 2.解析请求 3.发送响应这三个操作,解析请求又会根据不同类型的请求发送不同的响应

收到的请求,服务器完成的操作,发出的响应
   收到的请求                 服务器完成的操作           发出的响应
1.客服端注册 1.从服务器保存的客户端端信息中查阅是否有此人 2.已存在 1."该名字已被注册"
2.注册,并保存注册名,IP地址  1.注册成功
2.获取在线客户端 从服务器中生成在线客户端列表 2.返回列表
3..获得聊天对象的IP地址 通过注册名从服务器查找对应的IP地址 3.返回聊天对象的IP地址
4.客户端退出服务器 从服务器中删除该客户端的信息 4."已经退出"

服务器不断检测新的客户端发送的请求,每当有新的客服端注册,服务器就会生成一个子线程,只为该客户端服务。

客户端与服务器通信时,传递注册名和地址要保证传送的准确性和可靠,故选择TCP连接客户端和服务器,使用Socket对象,客户端与客户端之间进行通信时,要求实时性,不需要无比的准确,故选择UDP连接客户端与客户端,使用DatagramSocket对象进行通信,DatagramPacket为数据包

UDP为无连接传输,在DatagramSocket传送DatagramPacket时,只需要知道IP地址和端口号即可进行通信

服务器实现如下

服务器有两个类,MessageServer类和MessageHandler类。MessageServer类实现主程序,MessageHandler类是子线程的线程体类,定义了子线程所需完成的各种方法

本例的ServerSocket构造函数,只定义了端口号,没有定义IP地址,IP地址采用默认的地址,即0.0.0.0,表示所有的IP地址,就表示ServerSocket监听在本机的所有IP地址上,通过任何一个IP地址都可以访问到.如果只想访问特定的IP地址,可以进行设置

 MessageServer 类public class MessageServer {public static final int PORT=8000;//固定端口号public static final int MAX_QUEUE_LENGTH=100;public void start(){try{ServerSocket s=new ServerSocket(PORT, MAX_QUEUE_LENGTH);System.out.println("****服务器已经启动...****");while(true){Socket socket=s.accept();//监听是否有客户端的连接,如果有,返回socket对象System.out.println("已接收到客户来自: "+socket.getInetAddress());MessageHandler handler=new MessageHandler(socket);//为每个新连接的客户端创建一个子线程handler.start();//启动线程}}catch (Exception e){e.printStackTrace();}}public static void main(String[] args) {MessageServer ms=new MessageServer();ms.start();}
}
MessageHandler 类public class MessageHandler implements  Runnable {private Socket socket;//与聊天端的通信时的输入,输出端private ObjectInputStream datainput;private ObjectOutputStream dataoutput;private Thread listener;private  static Hashtable<String,InetSocketAddress>clientMessage= new Hashtable<>();//保存p2p注册名和地址private Request request;//请求变量private Response response;//响应变量private boolean keepListening=true;//创建客户端子线程的线程体public MessageHandler(Socket socket){this.socket=socket;}public synchronized  void start(){if(listener==null){try{//初始化datainput=new ObjectInputStream(socket.getInputStream());dataoutput=new ObjectOutputStream(socket.getOutputStream());listener=new Thread(this);listener.start();}catch (IOException e){e.printStackTrace();}}}public synchronized  void stop(){if(listener!=null){try{listener.interrupt();;listener=null;datainput.close();dataoutput.close();socket.close();}catch (IOException e){e.printStackTrace();}}}public void run() {try {while(keepListening){receiveRequest();//接收请求parseRequest();//解析请求sendResponse();//发送响应request=null;}stop();}catch (ClassNotFoundException e){e.printStackTrace();}catch (IOException e){stop();System.err.println("与客户端通信出现错误...");}}private void receiveRequest()throws IOException,ClassNotFoundException{request=(Request)datainput.readObject();//从客户端接收请求}private void parseRequest(){if(request==null)return;response=null;int requestType=request.getRequestTyper();String registerName=request.getRegisterName();if(requestType!=1&&!registerNameHasBeenUsed(registerName)){response=new Response(1,registerName+"你还未注册!" );return;}switch (requestType){//测试请求类型case 1:if(registerNameHasBeenUsed(registerName)){response=new Response(1,"|"+registerName+"|"+"已被其他人使用,请使用其他名字注册" );break;}clientMessage.put(registerName, new InetSocketAddress(socket.getInetAddress(), request.getUDPPort()));response=new Response(1,registerName+",你已经注册成功!" );System.out.println("|"+registerName+"| 注册成功...");break;case 2:Vector<String> allNameOfRegister= new Vector<>();for(Enumeration<String>e=clientMessage.keys();e.hasMoreElements(); ){//生成已注册的P2P端注册名列表allNameOfRegister.addElement(e.nextElement());}response=new Response(2,allNameOfRegister );break;case 3:String chatRegisterName=request.getChatRegisterName();InetSocketAddress chatP2PEndAddress=clientMessage.get(chatRegisterName);response=new Response(3, chatP2PEndAddress);break;case 4:clientMessage.remove(registerName);response=new Response(1,registerName+",你已经从服务器退出!" );keepListening=false;System.out.println("|"+registerName+"| 从服务器退出...");}}private boolean registerNameHasBeenUsed(String registerName){if(registerName!=null&&clientMessage.get(registerName)!=null)return true;return false;}private void sendResponse()throws IOException{if(response!=null){dataoutput.writeObject(response);//将响应写回聊天端}}
}

请求类和响应类

创建Request类和Respone类来封装请求信息和响应信息,Request类和Respone类的对象需要在网络中传输,需要进行序列化,实现Serializable接口

Request类public class Request implements Serializable {private int requestTyper;//请求类型private String registerName;//注册名private int UDPPort;//端口号private String chatRegisterName;//聊天对象的注册名public Request(int requestTyper,String registerName){this.requestTyper=requestTyper;this.registerName=registerName;}public Request(int requestTyper,String registerName, int UDPPort){this(requestTyper,registerName);this.UDPPort=UDPPort;}public Request(int requestTyper,String registerName, String chatRegisterName){this(requestTyper,registerName);this.chatRegisterName=chatRegisterName;}public int getRequestTyper() {return requestTyper;}public String getRegisterName() {return registerName;}public int getUDPPort() {return UDPPort;}public String getChatRegisterName() {return chatRegisterName;}
}
Response 类public class Response implements Serializable {private int responseType;private String message;//响应信息private Vector<String> allNameOfRegister;//存放所有客户端注册名的集合private InetSocketAddress chatP2PEndAddress;//聊天对象的地址public  Response(int responseType){this.responseType=responseType;}public Response(int responseType, String message) {this.responseType = responseType;this.message = message;}public Response(int responseType, Vector<String> allNameOfRegister) {this.responseType = responseType;this.allNameOfRegister = allNameOfRegister;}public Response(int responseType, InetSocketAddress chatP2PEndAddress) {this.responseType = responseType;this.chatP2PEndAddress = chatP2PEndAddress;}public int getResponseType() {return responseType;}public String getMessage() {return message;}public Vector<String> getAllNameOfRegister() {return allNameOfRegister;}public InetSocketAddress getChatP2PEndAddress() {return chatP2PEndAddress;}
}

聊天端的实现

1.P2PChatEnd类实现了主界面

public class P2PChatEnd extends JFrame {private Register register;private GetOnlineP2PEnds getOnlineP2PEnds;private Chat chat;private JLabel label;private JTabbedPane tabbedPane;private Exit exit;private CommWithServer commWithServer;//与服务器通信的线程public P2PChatEnd(){setTitle("P2P聊天端");label=new JLabel();label.setText("P2P聊天端");label.setForeground(Color.blue);label.setFont(new Font("隶书", Font.BOLD, 22));label.setHorizontalTextPosition(SwingConstants.RIGHT);label.setBackground(Color.green);commWithServer=new CommWithServer();register=new Register(commWithServer);getOnlineP2PEnds=new GetOnlineP2PEnds(commWithServer);chat=new Chat(this);register.setChat(chat);exit=new Exit(commWithServer,this);tabbedPane=new JTabbedPane(JTabbedPane.LEFT);tabbedPane.add("系统封面",label);tabbedPane.add("注册信息服务器",register);tabbedPane.add("选择聊天对象",getOnlineP2PEnds);tabbedPane.add("聊天",chat);tabbedPane.add("退出信息服务器",exit);add(tabbedPane,BorderLayout.CENTER);setBounds(120,60,400,147 );setVisible(true);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}public static void main(String[] args) {new P2PChatEnd();}
}

2.Register类实现了客户端的注册

public class Register extends JPanel implements ActionListener {private JLabel hintLabel;private JTextField registerNameField,serverIPField;//注册名和IP地址文本框private JButton submint;//提交按钮private CommWithServer commWithServer;private Chat chat;private Request request;private Response response;//使用对象流接收和发送响应和请求private ObjectOutputStream pipedOut;private ObjectInputStream pipedIn;private int clickNum=0;private boolean isRegister=false;//判读是否注册public Register(CommWithServer commWithServer){this.commWithServer=commWithServer;setLayout(new BorderLayout());hintLabel=new JLabel("注册",JLabel.CENTER);hintLabel.setFont(new Font("隶书", Font.BOLD, 18));registerNameField=new JTextField(10);serverIPField=new JTextField(10);submint=new JButton("提交");submint.addActionListener(this);Box box1=Box.createHorizontalBox();box1.add(new JLabel("注 册 名: ",JLabel.CENTER));box1.add(registerNameField);Box box2= Box.createHorizontalBox();box2.add(new JLabel("服 务 器IP: ",JLabel.CENTER));box2.add(serverIPField);Box boxH=Box.createVerticalBox();boxH.add(box1);boxH.add(box2);boxH.add(submint);JPanel panelC=new JPanel();panelC.setBackground(new Color(210,210,110 ));panelC.add(boxH);add(panelC,BorderLayout.CENTER);JPanel panelN=new JPanel();panelN.setBackground(Color.green);panelN.add(hintLabel);add(panelN,BorderLayout.NORTH);}public void setChat(Chat chat){this.chat=chat;}public void actionPerformed(ActionEvent e) {if(isRegister){String hint="不能重复注册";JOptionPane.showMessageDialog(this, hint,"警告",JOptionPane.WARNING_MESSAGE);clear();return;}clickNum++;String registerName=registerNameField.getText().trim();String serverIP=serverIPField.getText().trim();if (registerName.length()==0||serverIP.length()==0){String hint="必须输入注册名和服务器IP";JOptionPane.showMessageDialog(this, hint,"警告",JOptionPane.WARNING_MESSAGE);clear();return;}try {if(clickNum==1){//使用管道通信,让线程commWithServer可以和该类进行通信PipedInputStream pipedI=new PipedInputStream();PipedOutputStream pipedO=new PipedOutputStream(pipedI);//序列化和反序列化pipedOut=new ObjectOutputStream(pipedO);pipedIn=new ObjectInputStream(pipedI);}DatagramSocket socket=new DatagramSocket();Chat.setSocket(socket);int UDPPort=socket.getLocalPort();//获得一个UDP端口号request=new Request(1, registerName,UDPPort);//封装请求if(commWithServer!=null){if(commWithServer.isAlive()){//线程已经启动,已与信息服务器连接commWithServer.close();//断开与信息服务器的连接//连接信息服务器,pipedOut传递给commWithServer,commWithServer再将响应写到缓冲器commWithServer.connect(serverIP,request,pipedOut);commWithServer.notifyCommWithServer();//将线程唤醒}else{commWithServer.connect(serverIP,request,pipedOut);//连接信息服务器commWithServer.start();//启动线程,与信息服务器通信}}//pipedIn读取缓存区的响应response=(Response)pipedIn.readObject();}catch (Exception ex){JOptionPane.showMessageDialog(this, "无法连接或与服务器通信出错","警告",JOptionPane.WARNING_MESSAGE);clear();return;}String message=response.getMessage();boolean flag=true;if(message!=null&&message.equals(request.getRegisterName()+",你已经注册成功!")){message+="请单击左侧的\"获取在线P2P端\"";flag=false;}JOptionPane.showMessageDialog(null, message,"信息提示",JOptionPane.PLAIN_MESSAGE);if(flag){//注册没有成功,清除单行文本域,返回重新注册clear();return;}/*注册成功,将注册名传递给GetOnlineP2PEnds类对象,Chat类对象和Exit对象*/GetOnlineP2PEnds.setRegisterName(registerName);Chat.setRegisterName(registerName);Exit.setRegisterName(registerName);isRegister=true;//设置注册成功标志,控制不能重复注册//建立并启动"从其他P2P端接收信息"的子线程,等待接收信息new Thread(chat).start();clear();}private void clear(){registerNameField.setText(" ");serverIPField.setText(" ");}
}

3.GetOnlineP2PEnds类

public class GetOnlineP2PEnds extends JPanel implements ActionListener {private JButton getOnlineP2PEnds,submit;private JList list;private CommWithServer commWithServer;private Request request;private Response response;private ObjectOutputStream pipedOut;private ObjectInputStream pipedIn;private static String registerName;private int clickNum=0;public GetOnlineP2PEnds(CommWithServer commWithServer){this.commWithServer=commWithServer;setLayout(new BorderLayout());getOnlineP2PEnds=new JButton("获取在线P2P端");getOnlineP2PEnds.setBackground(Color.green);submit=new JButton("提 交");submit.setBackground(Color.green);getOnlineP2PEnds.addActionListener(this);submit.addActionListener(this);list=new JList();list.setFont(new Font("楷体", Font.BOLD, 15));JScrollPane scroll=new JScrollPane();scroll.getViewport().setView(list);Box box=Box.createHorizontalBox();box.add(new JLabel("单击 '获取' :",JLabel.CENTER));box.add(getOnlineP2PEnds);JPanel panelR=new JPanel(new BorderLayout());panelR.setBackground(new Color(201,210,110 ));panelR.add(submit,BorderLayout.SOUTH);JPanel panel=new JPanel(new BorderLayout());panel.setBackground(new Color(210,210,110 ));panel.add(box,BorderLayout.NORTH);panel.add(new JLabel("选择聊天P2P端:"),BorderLayout.WEST);panel.add(scroll,BorderLayout.CENTER);panel.add(panelR,BorderLayout.EAST);add(panel,BorderLayout.CENTER);submit.setEnabled(false);validate();}public static void setRegisterName(String name){registerName=name;}public void actionPerformed(ActionEvent e) {if(registerName==null||commWithServer==null||!commWithServer.isAlive()){JOptionPane.showMessageDialog(null, "你还没有注册!","信息提示",JOptionPane.PLAIN_MESSAGE);return;}try{if(e.getSource()==getOnlineP2PEnds){clickNum++;if(clickNum==1){PipedInputStream pipedI=new PipedInputStream();PipedOutputStream pipedO=new PipedOutputStream(pipedI);pipedOut=new ObjectOutputStream(pipedO);pipedIn=new ObjectInputStream(pipedI);}request=new Request(2, registerName);commWithServer.setRequest(request);commWithServer.setPipedOut(pipedOut);commWithServer.notifyCommWithServer();;response=(Response)pipedIn.readObject();//从响应中得到在线的P2P端注册名列表Vector<String> onLineP2PEnds=response.getAllNameOfRegister();//尝试将null值传递给此方法会导致未定义的行为,并且最有可能发生异常。 创建的模型直接引用给定的// Vector 。调用此方法后尝试修改Vector会导致未定义的行为。list.setListData(onLineP2PEnds);submit.setEnabled(true);}if(e.getSource()==submit){List<Object> list2=list.getSelectedValuesList();int len=list2.size();if(len==0){JOptionPane.showMessageDialog(this, "你还未选择聊天P2P端!","信息提示",JOptionPane.PLAIN_MESSAGE);return;}String register[]=new String[list2.size()];for(int i=0;i<list2.size();i++)register[i]=(String)list2.get(i);Vector<InetSocketAddress> P2PEndAddress=new Vector<>();int chatP2PEnds=0;for(int i=0;i<len;i++){if(register[i].equals(registerName))//如果聊天对象名与当前相同,则跳过continue;request=new Request(3, registerName, register[i]);commWithServer.setRequest(request);commWithServer.setPipedOut(pipedOut);commWithServer.notifyCommWithServer();response=(Response)pipedIn.readObject();//以下代码将从响应中得到的聊天对象地址加入到列表中P2PEndAddress.add(response.getChatP2PEndAddress());chatP2PEnds++;}String message=null;if(chatP2PEnds==0){message="你只选择了与自己聊天,请重新选择聊天端!";}else{Chat.setChatP2PEndAddress(P2PEndAddress);message="已获取到你选择P2P端的地址,请单击左侧的|聊天|按钮";}JOptionPane.showMessageDialog(this, message,"信息提示",JOptionPane.PLAIN_MESSAGE);P2PEndAddress.clear();//清空地址列表list.setListData(P2PEndAddress);}}catch (Exception e1){JOptionPane.showMessageDialog(this, "与服务器通信出错","警告",JOptionPane.WARNING_MESSAGE);}}
}

写到一半发现把代码都放在博客上不现实,太长了,还是放在GitHub上吧

java于网络:P2P聊天系统相关推荐

  1. JAVA的网络编程【转】

    转自 http://www.cnblogs.com/springcsc/archive/2009/12/03/1616413.html 网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能 ...

  2. Netty:Java 领域网络编程的王者

    一.简介 1. 课程背景 分布式系统的根基在于网络编程,而 Netty 是 Java 领域网络编程的王者. 2. 课程内容 第一部分 NIO 编程,三大组件 第二部分 Netty 入门学习,Event ...

  3. java下载网络中的文件,java下载网络文件解决思路

    java下载网络文件 下面这段代码是下载一个http网络文件的代码,但有时候下载下来的图片是完整的,有时候下载下来的不完整,还有下载的ppt,pdf之类,也是打不开的.请大件们给指导一下,小弟感激不尽 ...

  4. Java IO: 网络

    原文链接 作者: Jakob Jenkov 译者: 李璟(jlee381344197@gmail.com) 校对:方腾飞 Java中网络的内容或多或少的超出了Java IO的范畴.关于Java网络更多 ...

  5. 你对Java网络编程了解的如何?Java BIO 网络编程 | Netty 前期知识

    一步一步走来,之前去学习了JUC并发编程知识,现在终于到Java IO网络编程啦,难啊. 一.BIO介绍 引入: 随着技术的发展,两个或以上的程序必然需要进行交互,于是提供了一种端到端的通信,相当于对 ...

  6. 四十六、深入Java的网络编程(下篇)

    @Author:Runsen @Date:2020/6/9 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰 ...

  7. 四十五、深入Java的网络编程(上篇)

    @Author:Runsen @Date:2020/6/8 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰 ...

  8. java的网络工具netty简介

    2019独角兽企业重金招聘Python工程师标准>>> java的网络工具netty简介 Netty是一个NIO的客服端服务器框架,它可以简单.快速的搭建器一个协议包客服端服务器的应 ...

  9. 取消java相关网络协议_java相关网络协议可以关掉吗

    Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言.Java 技术具有卓越的通用性.高效性.平台移植性和安全性,广泛应用于PC.数据中心.游戏控制台.科学超级计算机.移动电话和互联网,同时拥 ...

  10. java获取主机信息大全,网络编程:Java获取网络主机信息

    java.net.InetAddress类表示互联网协议 (IP) 地址. 有两个子类:Inet4Address, Inet6Address 通过 InetAddress可以方便获取一个域名下的IP, ...

最新文章

  1. PHP删除数组(array一维)中指定的某个值 (转)
  2. ce修改器传奇刷元宝_真原始传奇刷元宝方法 不封号刷元宝技巧
  3. html中js添加或删除activex,JS:操作样式表2 :用JS实现添加和删除一个类名的功能(addClass()和removeClass())...
  4. html图片上传选择文件后的事件,bootstrap-fileinput插件,上传成功后事件
  5. 5 table滑动固定_内滑动钢套钢蒸汽直埋保温钢管
  6. mvc EF 从数据库更新实体,添加视图实体时添加不上的问题
  7. 17 操作系统第四章 磁盘管理 磁盘的结构 磁盘调度算法 减少磁盘延迟时间的方法
  8. python pip安装第三方库老是报错_#python pip 安装dlib一直失败?#python安装dlib错误...
  9. RISC 和 CISC 区别
  10. NYOJ813 - 对决
  11. 查找表_leetcode454
  12. 空间查询时出错误 An expected Field was not found or could not be retrieved properly. [SHAPE.AREA] 问题的解决...
  13. python约瑟夫环单向循环链表_用单向循环链表解决约瑟夫环(Joseph)问题
  14. 20160205 - Windows 10 家庭版没有组策略
  15. 学生a3制图标题栏尺寸手绘_机械制图基本知识点
  16. pandas_计算夏普比率
  17. 微信小程序实战之 goods(订餐页)
  18. MIDI通信协议-数据字节:找到中央C(音名:C4)
  19. 代码生成工具jdbc+freemarker+swing
  20. 400,405,404 ,500是什么错

热门文章

  1. 知识图谱学习(一) py2neo
  2. 如何为NFT熊市做准备
  3. WIN10系统安装金蝶K3 WISE14.0以下客户端版本
  4. UVa OJ 10105
  5. 如何让网站很快被百度收录?
  6. 财务管理系统-数据库模块
  7. CPN:Cascaded Pyramid Network for Multi-Person Pose Estimation_及CPN实现
  8. ceph 运维操作 - POOL
  9. 定时任务调度框架Quartz
  10. weex_iOS集成