写点什么

2021 年 Java 开发实战!仿微信的网络聊天室项目开发【完整源码讲解

发布于: 2021 年 08 月 04 日

前言

今年我也 33 了,离传说中不好找工作的 35 岁又更近了。说没有焦虑是对自己撒谎,于是我采访了一些人,自己思考了下,写下了这篇文章,希望能有些共鸣。


先看看大家的态度:


  • 色老力衰,不好忽悠,不能带头加班

  • 老油条,学不动,加班就是磨洋工

  • 关键还是贡献的价值。能不能给业务带来价值

  • 找背书

  • 基本逻辑就是,比你这个难得多的哥们都搞定了,你这点小事根本不再话下

  • 去 bat 镀镀金,捷径

  • 看看你的上司是咋过来的


不知从何时起,很多企业将入职门槛限定在 35 岁以下,“35 岁”已然成为职场老鸟的魔咒,而近年来有关**“程序员猝死”、“程序员跳楼”**等新闻层出不穷,这股百般苦恼的邪气,已入侵到了程序员群体,每每看到这类信息,我的心情都无比沉重,大多数人在这个年龄所面临的困惑、承担的压力、肩上的重担,这个年龄的遭遇我都感同深受。



  1. 编写通信断开函数,实现网络通信的可断开


好了,以上五个步骤是主要的开发过程,其中还有很多需要编写和注意的小细节,接下来分享一下网络聊天室项目的详细开发思路,同时附上对应的源码:


服务器端


========


服务器界面设计




服务器端的界面设计上,主要包括的元素是:连接、断开、发送按钮、消息输入框、消息接收框、端口号输入框等,根据 PC 端微信的界面原理,可以根据自己的想法简单设计,我设计的服务器端的界面如下:



之后根据设计依次要完成的是:


建立 TCP 服务器端通信




根据 TCP 协议通信原理,在服务器端需要基于端口号建立通信协议,之后在客户端以相同的方式建立客户端套接字来实现通信连接。


在服务器端,按照实际需要,网络通信应该是在用户输入通信端口号并且点击了连接按钮之后,再进行服务器端通信的,因此该段代码应该写在连接按钮的监听函数中去,在这里是建立了连接按钮内部监听类:



//设置连接按钮内部监听类
class ConnectJBClass implements ActionListener{


@Override
public void actionPerformed(ActionEvent e) {
//判断端口输入框是否为空
if (portText.getText().equals("")) {
JOptionPane.showMessageDialog(null, "请输入连接的端口号!", "提示", JOptionPane.ERROR_MESSAGE);
}
else {
//如果输入的端口不是整型,则异常抛出
try {
port = Integer.parseInt(portText.getText()); //获取到用户输入的端口号
isCorrectPort = true; //如果用户输入的端口号正确,就设置为true
} catch (Exception e2) {
// TODO: handle exception
JOptionPane.showMessageDialog(null, "请输入正确的端口号!", "提示", JOptionPane.ERROR_MESSAGE);
}

//如果输入了正确格式的端口号则继续,否则不执行
if (isCorrectPort) {

try {
server = new ServerSocket(port); //建立服务器,端口为用户输入port
stateJL.setText("正在等待连接...");
System.out.println("端口号是“" + Integer.toString(port) + "”");
client = server.accept(); //调用服务器函数对客户端进行连接
stateJL.setText("IP:" + client.getInetAddress());
isConnect = true; //建立通信
threadConnect.start();//连接成功后启动通信线程
} catch (IOException e1) {
JOptionPane.showMessageDialog(null, "客户端已断开!", "提示", JOptionPane.ERROR_MESSAGE);
}

}
}
}

}
复制代码


建立服务器消息发送输出流




在通信建立完成之后,就是实现双方的信息交互了,首先我们应该完成客户端到服务器或服务器到客户端的单向通信,之后再进行双向通信,在这里我们先完成服务器端向客户端发送,客户端向服务器通信使用的是同样的方法,该代码同样是在发送按钮的内部监听类中完成:



//设置发送按钮内部监听类
class ShendJBClass implements ActionListener{


@Override
public void actionPerformed(ActionEvent e) {
try {
String putText = sendWindow.getText(); //获取到服务器用户输入的文本
String putTime = getTime();
setInfoWindosFont(putTime, Color.blue, false, 15);
setInfoWindosFont(putText, Color.black, false, 20);
sendWindow.setText(""); //发送完毕后将发送框清空
OutputStream put = client.getOutputStream(); //定义发送给客户端的输出流
put.write(putText.getBytes()); //将文本转为字节发送
} catch (IOException e1) {
// TODO Auto-generated catch block

}
}
}
复制代码


建立服务器消息接收输入流




这一步要完成的是对客户端发送过来的消息进行接收,在这里根据 TCP/IP 协议,要建立消息输入流对象,从而实现对消息的接收:客户端接收是以同样的方法,具体代码如下:



try {
InputStream iStream = client.getInputStream(); //获取到客户端的输入流
byte [] b = new byte[1024];
int len = iStream.read(b); //以二进制的形式对数据进行读取
String data = new String(b,0,len); //接收到的内容
String infoTime = getTime(); //消息发送的时间
setInfoWindosFont(infoTime, Color.red, false, 15);
setInfoWindosFont(data, Color.black, false, 20);

/*使滚动条置于文本框最下端*/ infoWindow.setSelectionStart(infoWindow.getText().length());
JScrollBar jSBInfo = jScrollPaneInfo.getVerticalScrollBar();
jSBInfo.setValue(jSBInfo.getMaximum());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
复制代码


建立服务器实时消息通信线程




以上我们基本就完成了客户端和服务器的双向通信,也就是说我们的客户端可以接收到来自服务器的消息,服务器同时也可以接收到来自客户端的消息,但是需要注意的是,以上我们建立的通信是单次的,也就是说只能实现一次发送和接收,那么很显然这样是不可以的,实际开发的聊天室应该是可以在通信畅通的前提下一直通信的才对,所以我们接下来就是要解决这样的问题。


实现客户端和服务器实时通信的方法其实很简单,我们只需要对客户端或者服务器发送的消息实时的监听,只要一旦监听到有消息的发送,那么我们就将接收到的消息在对应的消息框显示出来,所以这里要使用线程的方法,具体代码如下:



//通信线程
threadConnect = new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
//如果当前已经建立连接
if (isConnect) {
try {
InputStream iStream = client.getInputStream(); //获取到客户端的输入流
byte [] b = new byte[1024];
int len = iStream.read(b); //以二进制的形式对数据进行读取
String data = new String(b,0,len); //接收到的内容
String infoTime = getTime(); //消息发送的时间
setInfoWindosFont(infoTime, Color.red, false, 15);
setInfoWindosFont(data, Color.black, false, 20);

/*使滚动条置于文本框最下端*/ infoWindow.setSelectionStart(infoWindow.getText().length());
JScrollBar jSBInfo = jScrollPaneInfo.getVerticalScrollBar();
jSBInfo.setValue(jSBInfo.getMaximum());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
stateJL.setText("服务器未连接!");
break;
}
}
}
});
复制代码


设置服务器通信自由断开




在以上完成之后,我们的聊天室就可以实现双向的实时通信了,但是这也仅仅是通信,就像我们在使用微信的时候,还有对方下线的情况出现对吧,所以在这里我们同样是建立了一个客户端和服务器的断开设置。在点击了断开按钮之后,我们的客户端和服务器就无法通信了,其实很简单只需要将客户端和服务器的套接字 close 掉就可以了,具体代码如下:



//设置断开连接按钮内部监听类
class CloseConnectJBClass implements ActionListener{


@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
isConnect = false; //服务器断开
try {
server.close(); //中止服务器端运行
} catch (IOException e1) {
// TODO Auto-generated catch block
JOptionPane.showMessageDialog(null, "服务器端已断开!", "提示", JOptionPane.ERROR_MESSAGE);
}
//JOptionPane.showMessageDialog(null, "服务器端已断开!", "提示", JOptionPane.ERROR_MESSAGE);

}

}
复制代码


客户端


=======


在客户端我们同样需要按照和服务器端一样的思路进行编写,


客户端界面设计




首先是界面的设计,效果如下:



建立 TCP 客户端通信




与服务器端稍微有所不同的是,客户端的通信是基于 IP 地址和端口号的,也就是说在建立客户端通信时,我们需要输入通信的 IP 地址还有和服务器端一样的端口号,这样才能建立双方的通信。建立客户端通信是在客户端的连接按钮中实现的,这里建立客户端连接按钮内部监听类:



//为连接按钮添加内部事件监听类
class ConnectJBClass implements ActionListener{

@Override
public void actionPerformed(ActionEvent e) {

//判断端口输入框和IP输入框是否为空
if (portText.getText().equals("端口号")||ipTextArea.getText().equals("IP地址")) {
JOptionPane.showMessageDialog(null, "请输入完整的IP和端口号!", "提示", JOptionPane.ERROR_MESSAGE);
}
else {
//对用户输入的格式进行判断
try {
ipClient = ipTextArea.getText(); //获取到用户输入的IP
//如果IP正确
if (isCorrectIp2(ipClient)) {
isCorrectIp = true;
}
else {
JOptionPane.showMessageDialog(null, "请输入正确格式的IP!", "提示", JOptionPane.ERROR_MESSAGE);
}
port = Integer.parseInt( portText.getText()); //获取到用户输入的端口号
isCorrectPort = true;
//System.out.println("端口正确!");
} catch (Exception e2) {
// TODO: handle exception
JOptionPane.showMessageDialog(null, "请输入正确格式的端口号!", "提示", JOptionPane.ERROR_MESSAGE);
}
//如果用户输入的IP和端口格式都正确
if (isCorrectIp&&isCorrectPort) {
JOptionPane.showMessageDialog(null, "输入完成,正在连接......\nIP:" + ipClient, "提示", JOptionPane.ERROR_MESSAGE);
/*******判断正确后将判断变量赋初值,以便下次输入判断********/
isCorrectIp = false;
isCorrectPort = false;
try {
client = new Socket(ipClient,port); //建立客户端
stateJL.setText("客户端连接成功!");
isConnect = true; //已经建立连接
threadConnect.start();//启动通信线程
} catch (IOException e1) {
// TODO Auto-generated catch block
JOptionPane.showMessageDialog(null, "客户端连接失败!", "提示", JOptionPane.ERROR_MESSAGE);

}
}
}
}

}
复制代码


建立客户端消息发出输出流





//设置发送按钮内部监听类
class ShendJBClass implements ActionListener{


@Override
public void actionPerformed(ActionEvent e) {
try {
String putText = sendWindow.getText(); //获取到客户端用户输入的文本
String putTime = getTime();
setInfoWindosFont(putTime, Color.blue, false, 15);
setInfoWindosFont(putText, Color.black, false, 20);
sendWindow.setText(""); //发送完毕后将发送框清空
OutputStream put = client.getOutputStream(); //定义发送给服务器的输出流
put.write(putText.getBytes()); //将文本转为字节发送
} catch (IOException e1) {

}
}
}
复制代码


建立客户端消息接收输入流





try {
InputStream input = client.getInputStream();
byte [] infoByte = new byte[1024];
int len = input.read(infoByte);
String infoTime = getTime(); //获取当前时间
String data = new String(infoByte,0,len); //获取接收的消息
String oldText = infoWindow.getText(); //获取到之前文本框的内容
String atText = oldText + "\n" + infoTime + "\n" + data; //将要在文本框显示的内容
System.out.println(atText);
setInfoWindosFont(infoTime, Color.RED, false, 15);
setInfoWindosFont(data, Color.black, false, 20);
//infoWindow.setText(atText);

/*使滚动条置于文本框最下端*/
infoWindow.setSelectionStart(infoWindow.getText().length());
JScrollBar jSBInfo = jScrollPaneInfo.getVerticalScrollBar();
jSBInfo.setValue(jSBInfo.getMaximum());

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
复制代码


建立客户端实时通信线程





//通信线程
threadConnect = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
if (isConnect) {
stateJL.setText("正在通信!");
try {
InputStream input = client.getInputStream();
byte [] infoByte = new byte[1024];
int len = input.read(infoByte);
String infoTime = getTime(); //获取当前时间## 最后
分享一套我整理的面试干货,这份文档结合了我多年的面试官经验,站在面试官的角度来告诉你,面试官提的那些问题他最想听到你给他的回答是什么,分享出来帮助那些对前途感到迷茫的朋友。
#### 面试经验技巧篇
* 经验技巧1 如何巧妙地回答面试官的问题* 经验技巧2 如何回答技术性的问题* 经验技巧3 如何回答非技术性问题* 经验技巧4 如何回答快速估算类问题* 经验技巧5 如何回答算法设计问题* 经验技巧6 如何回答系统设计题* 经验技巧7 如何解决求职中的时间冲突问题* 经验技巧8 如果面试问题曾经遇见过,是否要告知面试官* 经验技巧9 在被企业拒绝后是否可以再申请* 经验技巧10 如何应对自己不会回答的问题* 经验技巧11 如何应对面试官的“激将法”语言* 经验技巧12 如何处理与面试官持不同观点这个问题* 经验技巧13 什么是职场暗语
![](https://static001.geekbang.org/infoq/72/720e935bb7c77647c903b604d928a7b3.png)
#### 面试真题篇
* 真题详解1 某知名互联网下载服务提供商软件工程师笔试题* 真题详解2 某知名社交平台软件工程师笔试题* 真题详解3 某知名安全软件服务提供商软件工程师笔试题* 真题详解4 某知名互联网金融企业软件工程师笔试题* 真题详解5 某知名搜索引擎提供商软件工程师笔试题* 真题详解6 某初创公司软件工程师笔试题* 真题详解7 某知名游戏软件开发公司软件工程师笔试题* 真题详解8 某知名电子商务公司软件工程师笔试题* 真题详解9 某顶级生活消费类网站软件工程师笔试题* 真题详解10 某知名门户网站软件工程师笔试题* 真题详解11 某知名互联网金融企业软件工程师笔试题* 真题详解12 国内某知名网络设备提供商软件工程师笔试题* 真题详解13 国内某顶级手机制造商软件工程师笔试题* 真题详解14 某顶级大数据综合服务提供商软件工程师笔试题* 真题详解15 某著名社交类上市公司软件工程师笔试题* 真题详解16 某知名互联网公司软件工程师笔试题* 真题详解17 某知名网络安全公司校园招聘技术类笔试题* 真题详解18 某知名互联网游戏公司校园招聘运维开发岗笔试题
![](https://static001.geekbang.org/infoq/02/02f2be7c08b1886dc705ed18dd0e3852.png)
> 需要这份文档的朋友可以[点击蓝色传送门](https://gitee.com/vip204888/java-p7)即可免费获取!
资料整理不易,点个关注再走吧

复制代码


用户头像

还未添加个人签名 2021.07.29 加入

还未添加个人简介

评论

发布
暂无评论
2021年Java开发实战!仿微信的网络聊天室项目开发【完整源码讲解