UDP局域网聊天软件的设计与开发报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
一、设计思想
本课程设计主要是基于UDP的并用Java实现的简单的聊天程序。
UDP 用户数据报是在运输层的端到端抽象的逻辑信道中传送的。
UDP 在传送数据之前不需要先建立连接。
对方的运输层在收到 UDP 报文后,不需要给出任何确认。
虽然 UDP 不提供可靠交付,但在某些情况下 UDP 是一种最有效的工作方式。
虽然UDP 用户数据报只能提供不可靠的交付,但 UDP 在某些方面有其特殊的优点,它有即时通信的功能。
比如说,使用聊天程序聊天的时候,省去的了跟对方通信的时候的链接的麻烦,直接进行通信。
UDP 只在 IP 的数据报服务之上增加了很少一点的功能,即端口的功能和差错检测的功能。
UDP 使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制。
UDP 是面向报文的。
UDP 没有拥塞控制,很适合多媒体通信的要求。
UDP 支持一对一、一对多、多对一和多对多的交互通信。
发送方 UDP 对应用程序交下来的报文,在添加首部后就向下交付 IP 层。
UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。
应用层交给 UDP 多长的报文,UDP 就照样发送,即一次发送一个报文。
接收方 UDP 对 IP 层交上来的 UDP 用户数据报,在去除首部后就原封不动地交付上层的应用进程,一次交付一个完整的报文。
Socket又称作套接字,它是使用标准Unix 文件描述符(file descriptor) 和其它程序通讯的方式。
使用send()和recv()让你更好的控制数据传输。
在Java 网络编程中,socket相当于应用程序的港口码头;在计算机网络中,套接字由ip地址和端口号组成,为进程之间通信提供地址。
两个主机进行通信实际上就是两个主机中的应用进程互相通信,应用进程之间的通信又称为端到端的通信。
为应用进程之间的通信提供运输服务的是运输层的运输协议,运输层需要有两种不同的运输协议,即面向连接的TCP和无连接的UDP。
在Java中,用于实现基于UDP的聊天程序,使用两个重要的类(DatagramSocket类和DatagramPacket类)。
DatagramSocket类表示用来发送和接收数据报包的套接字。
数据报套接字是包投递服务的发送或接收点。
每个在数据报套接字上发送或接收的包都是单独编址和路由的。
从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。
在DatagramSocket上总是启用UDP广播发送。
为了接收广播包,应该将DatagramSocket 绑定到通配符地址。
在某些实现中,将DatagramSocket绑定到一个更加具体的地址时广播包也可以被接收。
DatagramPacket类表示数据报包。
数据报包用来实现无连接包投递服务。
每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。
从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。
不对包投递做出保证。
服务程序工作在服务器的某个端口上,一旦启动服务,它将在这个端口上监听,等待客户程序发来请求,当有客户连接到该端口,accept()方法就返回一个Socket对象,可以用该对象获得一个输入输出流。
服务器的套接字用服务器套接字类(ServerSocket)来建立。
具体实现如下:
(1)建立一个服务者,端口为5000。
(2)服务者永远等待,一旦客户送来正确的请求,连接至该端口,accept()方法就返回
一个Socket对象。
(3)用返回的Socket对象创建数据输入流类的实例in。
用返回的Socket对象创建数据输
出流的实例out。
以上实例in和out是服务者用于从客户接受输入信息和向客户程序发送信息所用。
此外,还需要用方法readline()和println()读取或输出一行数据。
(4)soc.close()关闭Socket。
二、算法流程图
本程序的核心算法流程图如下图所示:
图1 程序算法流程图
三、源代码
import java.awt.AWTEvent; import java.awt.Color;
import java.awt.Dimension; import java.awt.Rectangle; import java.awt.TextArea; import java.awt.Toolkit;
import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowEvent; import java.io.IOException; import .DatagramPacket; import .DatagramSocket; import .InetAddress; import .SocketException; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel;
import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField;
用户1 封装数据报
解析数据报
目的IP 与主机IP 比较
接受消息
用户2
是
否
传输数据报
丢弃
发送数据报
@SuppressWarnings("serial")
public class UDP extends JFrame {
// 创建主界面
static JPanel mainPane;
// 创建“发送信息”标签
JLabel sendMSGLabel = new JLabel("发送信息:");
// 创建“输入对方ip地址”标签
JLabel ipAddressLabel = new JLabel();
// 创建“群聊”标签
JLabel qunLabel = new JLabel("群聊");
// 创建发送文本框
JTextField sendMSGText = new JTextField();
// 创建ip文本框
JTextField ipAddressText = new JTextField();
// 创建“发送”按钮
JButton sendMSGButton = new JButton("发送");
// 创建“群聊”选择框
JCheckBox qunCB = new JCheckBox();
// 创建聊天信息文本框
static TextArea MSGText = new TextArea();
// 输入端口
int In = 3001;
// 输出端口
int Out = 3000;
// 声明发送和接收数据包引用
static DatagramPacket sendDP, getDP;
// 声明发送和接收套接字引用
static DatagramSocket sendDS, getDS;
// 界面开关
static boolean packFrame = false;
// 群聊开关
static boolean isQun = false;
// 程序主方法
public static void main(String[] args) {
// 创建UDP类实例
UDP frame = new UDP();
// 判定主界面是否以创建
if (packFrame) {
frame.pack();
} else {
frame.validate();
}
// 放置窗体在屏幕中央
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = frame.getSize();
if (frameSize.height > screenSize.height) {
frameSize.height = screenSize.height;
}
if (frameSize.width > screenSize.width) {
frameSize.width = screenSize.width;
}
frame.setLocation((screenSize.width - frameSize.width) / 2,
(screenSize.height - frameSize.height) / 2);
frame.setVisible(true);
// 接受局域网消息
while (true) {
try {
// 创建接受字符串
String received = null;
// 创建接受数组
byte[] array = new byte[100];
// 创建接受数据包
getDP = new DatagramPacket(array, array.length);
// 创建接受套接字
getDS.receive(getDP);
// 判定消息是否为群发
if (!getDP
.getAddress()
.toString()
.equals("/"+InetAddress.getLocalHost().getHostAddress()
.toString())) {
// 如果为私聊,则显示以下消息
MSGText.append("\n" + getDP.getAddress().toString().substring(1)
+ "说:");
received = new String(getDP.getData(), 0,
getDP.getLength(), "UTF-8");
MSGText.append(received);
}
// 捕获异常
} catch (IOException se) {
se.printStackTrace();
}
}
}
// 重构构造方法
public UDP() {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
// 构建主窗体
mainPane = (JPanel) this.getContentPane();
// 设置主窗体的布局
mainPane.setLayout(null);
// 设置主窗体的大小
this.setSize(new Dimension(363, 500));
// 设置标题
this.setTitle("基于UDP的局域网聊天室");
// 设置ip输入框的文字为“输入对方IP地址”
ipAddressLabel.setText("输入对方IP地址:");
// 设置ip地址标签的位置
ipAddressLabel.setBounds(new Rectangle(12, 20, 109, 23));
// 设置ip地址输入框的位置
ipAddressText.setBounds(new Rectangle(123, 20, 120, 19));
// 设置群聊标签的位置
qunLabel.setBounds(270, 20, 40, 20);
// 设置群聊选择框的位置
qunCB.setBounds(295, 20, 20, 20);
// 为群聊选择框添加监听器
qunCB.addActionListener(new ActionListener() {
// 重写处理方法
@Override
public void actionPerformed(ActionEvent e) {
// 将群聊选择框反选
isQun = !isQun;
// 设置ip地址输入框为不可用
ipAddressText.setEditable(!isQun);
}
});
// 设置聊天记录框的位置
MSGText.setBounds(new Rectangle(11, 60, 333, 355));
// 设置聊天记录框为可用
MSGText.setEditable(false);
// 设置聊天记录框背景色为白色
MSGText.setBackground(Color.white);
// 设置发送消息标签的位置
sendMSGLabel.setBounds(new Rectangle(13, 430, 65, 20));
// 设置发送文本框的位置
sendMSGText.setBounds(new Rectangle(80, 430, 190, 20));
// 设置发送按钮的位置
sendMSGButton.setBounds(new Rectangle(280, 430, 65, 20));
// 添加发送按钮的监听器
sendMSGButton
.addActionListener(new java.awt.event.ActionListener() {
// 重写处理方法
public void actionPerformed(ActionEvent e) {
try {
// 判定是否为群聊消息
if (isQun) {
// 如果是群聊,则显示以下消息
MSGText.append("\n您对大家说:"
+ sendMSGText.getText());
// 创建比特数组
byte data[] = sendMSGText.getText()
.getBytes("UTF-8");
// 创建数据包
sendDP = new DatagramPacket(
// 数据
data,
// 数据长度
data.length,
// ip地址
InetAddress
.getByName("255.255.255.255"),
// 输出端口
In);
// 发送数据包
sendDS.send(sendDP);
} else {
// 如果是私聊,则显示以下消息
MSGText.append("\n您对"
+ ipAddressText.getText() + "说:"
+ sendMSGText.getText());
// 创建比特数组
byte data[] = sendMSGText.getText()
.getBytes("UTF-8");
// 创建数据包
sendDP = new DatagramPacket(
// 数据
data,
// 数据长度
data.length,
// ip地址
InetAddress.getByName(ipAddressText
.getText()),
// 输出端口
In);
// 发送数据包
sendDS.send(sendDP);
}
// 捕获异常
} catch (IOException exc) {
System.out.println("您不在局域网!");
}
// 清空发送消息文本框内容
sendMSGText.setText("");
}
});
// 把ip地址标签添加进主界面
mainPane.add(ipAddressLabel, null);
// 把发送消息标签添加进主界面
mainPane.add(sendMSGLabel, null);
// 把发送消息文本框添加进主界面
mainPane.add(sendMSGText, null);
// 把ip地址输入框添加进主界面
mainPane.add(ipAddressText, null);
// 把消息记录框添加进主界面
mainPane.add(MSGText, null);
// 把发送按钮添加进主界面
mainPane.add(sendMSGButton, null);
// 把群聊标签添加进主界面
mainPane.add(qunLabel, null);
// 把群聊选择框添加进主界面
mainPane.add(qunCB);
// 设置各组件为可见
this.setVisible(true);
try {
// 创建发送端口套接字
sendDS = new DatagramSocket(Out);
// 创建接受端口套接字
getDS = new DatagramSocket(In);
// 异常捕获
} catch (SocketException se) {
se.printStackTrace();
// 捕获异常后退出系统
System.exit(0);
}
// 捕获异常
} catch (Exception e) {
e.printStackTrace();
}
}
// 重载关闭按钮函数
protected void processWindowEvent(WindowEvent e) { super.processWindowEvent(e);
if (e.getID() == WindowEvent.WINDOW_CLOSING) { System.exit(0);
}
}
}
四、运行结果
软件运行出事界面如下:
图2 软件初始界面
之后输入对方ip地址,在下面的输入框内输入想要说的文字:
图3 输入ip地址
点击“发送”按钮,发送消息:
图4 点击“发送”按钮
接收到对方的信息:
图5 收到对方私聊信息勾选群聊选项,输入想说的话:
图6 发送群聊信息
收到其他人的群聊信息:
图7 收到群聊信息
五、遇到的问题及解决
这部分我主要遇到了如下两个问题,其内容与解决方法如下所列:
1.程序无法发送和接收数据
解决方法:
没有弄明白DatagramPacket和DatagramSocket的用法和区别。
DatagramPacket和DatagramSocket都存在中。
DatagramPacket表示存放数据的数据报,DatagramSocket 表示接受或发送数据报的套接字。
DatagramPacket实例,并将其作为参数传递给DatagramSocket类的send()方法。
接收信息时,Java程序首先创建一个DatagramPacket实例,该实例中预先分配了一些空间(一个字节数组byte[]),并将接收到的信息存放在该空间中。
然后把该实例作为参数传递给DatagramSocket类的receive()方法。
除传输的信息本身外,每个DatagramPacket实例中还附加了地址和端口信息,其具体含义取决于该数据报文是被发送还是被接收。
若是要发送的数据报文,DatagramPacket实例中的地址则指明了目的地址和端口号,若是接收到的数据报文,DatagramPacket实例中的地址则指明了所收信息的源地址。
因此,服务器端可以修改接收到的DatagramPacket实例的缓存区内容,再将这个实例连同修改后的信息一起,发回给它的源地址。
Java使用DatagramSocket代表UDP协议的Socket,DatagramSocket本身只是码头,不维护状态,不能产生IO流,它的唯一作用就是接收和发送数据报,Java使用DatagramPacket 来代表数据报,DatagramSocket接收和发送的数据都是通过DatagramPacket对象完成的。
2.在程序中加入图片,用代码读取图片路径设置为绝对路径,导致程序移动之后,图片文件无法被读取。
解决办法:
从网上搜索资料,参考大神博客,真正理解什么是绝对路径和相对路径的区别以及在项目开发中用法的不同。
所谓的绝对路径,就是文件在整个文件系统的全路径,如在Windows 下,D:\JA V A\MyEclipse\MyEclipse 10 这个文件夹或者D:\JA V A\MyEclipse\MyEclipse 10\myeclipse.exe这个文件,都是以全路径名的形式出现的。
只是在Java代码中,\ 是必须转义的,所以我们写绝对路径时,总会D:\\JA V A\MyEclipse\\MyEclipse 10\\myeclipse.exe。
如代码中:File file04 = new File("C:\\test1\\test101\\SeProjectFileDemo04.txt");File file05 = new File(dirAbsPath05,"SeProjectFileDemo05.txt");这两种都是绝对路径的写法,其中前者中C:\\test1\\test101\\的目录必须事先已经存在者两个目录,后者dirAbsPath05也必须是已经创建好的目录,如代码中就File dir05 = new File("C:\\test2\\test201");if(!dir05.exists()) dir05.mkdirs();所谓的相对路径,就是相对某个目录的情况下,某个资源文件的短名称,这其中就涉及到参考谁的问题了。
如:如果我说,以后你们写相对路径,都是以D:\\JA V A\MyEclipse\\MyEclipse 10 这个目录为参考点,那么File file = new File("myeclipse.exe"); 中的"myeclipse.exe" 就是一个相对路径的写法。
六、心得体会
这次的课程设计,我觉得还是很成功的。
先是成功实现了此次设计的要求,另外我还添加了群聊功能。
一开始并不打算选这个题目的时候,后来只是抱着试试看的心理,这部分的知识应该说是没有学过,老师上课也没有讲这个SOCKET套接字的部分,是要我们课后自己看的。
我只好把书上的一段基于客户-服务器端的计算三角形面积的java代码先调试一下,熟悉一下代码运行的效果,给我一点思路。
再根据这个程序的框架,来编写代码,但是后来发现自己编代码并不简单,折腾了好久也没成功,觉得自己的设计思路有点偏差,所以才导致这个程序的失败,看来把理论变成实践可真不是一件简简单单就能完成的事。
接下来的几天,我只好到网上的一些专门学习JAVA的论坛里找些资料来看看,也试着调试调试别人编的程序,好给自己的课程设计定一个大体的框架。
在坛子里逛了许久,我的程序的雏形就也差不多完成了,只是简单的聊天功能,运行服务器,在给定端口侦听,转发客户端的消息,最后显示在客户端聊天窗口的文本域中。
虽然这才是只是一点点的成功,但也使这次课程设计有了好的开端,我相信后面的问题也就不算问题了。
我又陆陆续续地添加些功能,让程序更加丰满,最重要的是设计出来的程序更加的合理,更人性化。
先是聊天室服务器端启动服务器时,将创建侦听套接字,创建用户列表,创建并启动侦听线程。
用户登录时,将创建套接字,与服务器直接连接,并创建客户端接收线程。
服务器端侦听到有用户上线后,将创建新的用户节点,并在主界面上显示用户上线,发送新的用户列表。
客户端发送信息时,将要发送的内容通过output.writeObject();进行发送。
服务器端发送信息时,如果是发送给所有人,就遍历用户链表,如果是发送给某个用户,先在链表中找到该节点,通过output.writeObject()发送信息。
服务器端和客户端接收信息时,先读取聊天信息标识,做出判断后,依次读取信息,处理信息,并在主界面上显示。
服务器端还要将准备好的信息转发给指定的用户。
当服务器端侦听到有用户下线后,将删除该用户节点,并在主界面上显示用户下线,发送新的用户列表。
服务器端停止服务后,也会向客户端发送服务器即将关闭的信息,客户端将不再可以聊天。
这也就是程序的核心,然后添加了文件传输,记事本,背景切换的功能,还美化了界面,给按钮添加图片。
设置背景还真的不太好弄,原来JFrame还有分层的,要把图片放在最底层,还要使内容窗格透明,这样才能实现设置背景的效果。
本来还尝试添加语音聊天的功能,但由于自己的知识比较有限,所以只好作罢。
但这也会让我以后更加的努力去学好JAVA,在以后的编程中,JAVA真的个很好的编程语言。
这次程序设计也是一个毅力的考验过程。
有时候往往只是一个小小的错误,却要花出几小时甚至是一天的时间才可能发现它,所以在这个过程不能过于急躁,要把那部分的代码挑出来一一调试,来缩小范围,找出错误。
总的来说,这次课程设计给我的收获还是很多的,不仅巩固了以前所学过的知识,而且学到了很多在书本上所没有学到过的知识。
最重要的是,我知道了理论与实际相结合是很重要的,只有理论知识是远远不够的,只有把所学的理论知识与实践相结合起来,从理论中得出结论,才能真正掌握这门技术,在以后运用的时候才能更得心应手。
PS:因为百度不能上传附件,所以想要具体实现的详细代码的同学请到以下网址下载/detail/ldj0409/6674495。