资源描述
基于UDP协议的聊天程序设计报告(完整资料)
(可以直接使用,可编辑 优秀版资料,欢迎下载)
编号:
计算机网络课程设计
题 目:基于UDP的聊天程序
系 别: 计算机科学与工程学院
摘要
随着网络技术的发展及人们生活的需求,网络聊天已越来越受到人们的青睐 。网络聊天已经成为人们工作生活中传递信息、交流感情的重要工具,给人们带来了很大的方便。 本课题是开发一个基于UDP的局域网聊天系统,运用软件工程的设计流程,综合运用数据库编程技术、Windows程序设计技术、网络通讯技术,此网络聊天工具采用客户端/服务器(C/S)模式,客户端采用UDP与服务器连接,客户端与客户端之间通过UDP互相通讯。服务器端具有服务器端口设置,此聊天工具能实现多人聊天功能,适用于局域网使用的网络聊天工具,其操作简单,灵活性好,运行也比较稳定。
关键词:网络通讯;客户端/服务器模型;用户数据报协议;套接字
一、需求分析
1.1 课程设计目的 开发一个专用于实现两台计算机之间即时通讯的软件以方便两台计算机之间信息的交流。在连接并通信时尤其是近程的即时通讯彻底的脱离了远程的服务器避免了和远程服务器连接时过多的浪费网络资源。并且避免了服务器忙或与服务器无法连接时浪费过多时间用于和服务器建立连接因此这个软件是极具适应性和实用性的即时通讯软件本次课程设计的目的是学习基于UDP协议实现网络聊天程序已达到学会面向无连接方式的程序设计方法并理解网络编程中面向无连接的概念.
1.2 课程设计的内容 用户数据报UDP是一个无连接协议使用这种协议时并不需要在两台计算机之间建立固定的连接也就是说通信双方没有服务器和客户机之分它们之间进行的是对等通信所以它的优势很明显是现代通信不可或缺的一部分。所以利用它的优势设计一个可以聊天的软件实现两台计算机间的即时通讯。
1.3 课程设计要求 基于UDP协议实现的聊天和一对多的聊天提供友好的用户界面便于用户进行操作。
二 . UDP协议的理解:
UDP协议是英文UserDatagramProtocol的缩写,即用户数据报协议,主要用来支持那些需要在计算机之间传输数据的网络应用.包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议.UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但是即使是在今天,UDP仍然不失为一项非常实用和可行的网络传输层协议。UDP协议直接位于IP(网际协议)协议的顶层。UDP协议的主要作用是将网络数据流量压缩成数据报的形式。一个典型的数据报就是一个二进制数据的传输单位。每一个数据报的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。UDP协议使用端口号为不同的应用保留其各自的数据传输通道。正是采用这一机制实现对同一时刻内多项应用同时发送和接收数据的支持。数据发送一方(可以是客户端或服务器端)将UDP数据报通过源端口发送出去,而数据接收一方则通过目标端口接收数据。有的网络应用只能使用预先为其预留或注册的静态端口;而另外一些网络应用则可以使用未被注册的动态端口。因为UDP报头使用两个字节存放端口号,所以端口号的有效范围是从0到65535。一般来说,大于49151的端口号都代表动态端口。数据报的长度是指包括报头和数据部分在内的总的字节数.因为报头的长度是固定的,所以该域主要被用来计算可变长度的数据部分(又称为数据负载).数据报的最大长度根据操作环境的不同而各异。从理论上说,包含报头在内的数据报的最大长度为65535字节.不过,一些实际应用往往会限制数据报的大小,有时会降低到8192字节。UDP协议使用报头中的校验值来保证数据的安全。校验值首先在数据发送方通过特殊的算法计算得出,在传递到接收方之后,还需要再重新计算.如果某个数据报在传输过程中被第三方篡改或者由于线路噪音等原因受到损坏,发送和接收方的校验计算值将不会相符,由此UDP协议可以检测是否出错。UDP协议并不提供数据传送的保证机制.如果在从发送方到接收方的传递过程中出现数据报的丢失,协议本身并不能做出任何检测或提示,由于排除了信息可靠传递机制,将安全和排序等功能移交给上层应用来完成,极大降低了执行时间,使速度得到了保证.
三. UDP协议特点
1、UDP传送数据前并不与对方建立连接,即UDP是无连接的,在传输数据前,发送方和接收方相互交换信息使双方同步.
2、UDP不对收到的数据进行排序,在UDP报文的首部中并没有关于数据顺序的信息(如TCP所采用的序号),而且报文不一定按顺序到达的,所以接收端无从排起.
3、UDP对接收到的数据报不发送确认信号,发送端不知道数据是否被正确接收,也不会重发数据.
4、UDP传送数据较TCP快速,系统开销也少。
5、由于缺乏拥塞控制(congestion control),需要基于网络的机制来减小因失控和高速UDP流量负荷而导致的拥塞崩溃效应。换句话说,因为UDP发送者不能够检测拥塞,所以像使用包队列和丢弃技术的路由器这样的网络基本设备往往就成为降低UDP过大通信量的有效工具。数据报拥塞控制协议(DCCP)设计成通过在诸如流媒体类型的高速率UDP流中增加主机拥塞控制来减小这个潜在的问题。 从以上特点可知,UDP提供的是无连接的、不可靠的数据传送方式,是一种尽力而为的数据交付服务。
四。 基于C/S的多客服端相互通信原理分析:
在C/S模式中,它是在分散式 ,集中式,以及分布式基础上发展起来的一种新模型,目前大多数网络通信以及应用都属于这种模型,C/S模式将一个网络事务分为两部分,一部分是客户端(Client),他为用户提供网络请求服务的接口,另一部分是服务端(Server),它负责接受用户对服务的请求,并将这些服务透明的提供给用户,既适用于实际应用的程序,又实用于真正的计算装置,举例来说,我们到饭店吃饭时,要首先提出请求吃什么,属于客户端,饭店服务员根据请求提供相应的服务,属于服务端,至于相应的饭菜是由哪一个厨师来做,则由饭店的服务员去联系,而客户端只需要和服务器打交道就行了,从程序实现上来说,客户端和服务器打交道实际上是两个进程打交道,服务端启动server进程,并等待客户端与其联系,而客户端则启动客户进程和服务器打交道.当服务器进程处理完一个客服进程请求的信息之后,由接着等待其他客户的请求。
五. 套接字编程原理分析:
图1.1 套接字编程原理图
注释:socket(),使用前创建一个新的套接字;bind(),将套接字地址与所创建的套接字号联系起来;send()与recv(),数据的发送与接收;closesocket(),关闭套接字。
六、概要设计
服务器
客户机甲
客户机乙
客户机丙
图2。1 整体框架设计图
服务器端主要实现的功能是启动一个监听的进程,开放自己的端口号为7777,不断的监听是否有新的客服端进程向自己发送连接请求,为每一个主动连接自己的客户端设置一个ID号设置一个threads的容器用来管理客户端的线程.与客户端建立连接,实现socket通信,对于服务器端是先接受数据流然后再发送数据流,客服端发送过来的信息经服务器端然后转发到其他所有的客户端,服务器端相当于中间的桥梁。
客户端要求主要实现的功能是建立一个图形的界面,用于显示聊天信息等,并且建立与服务器端的通信,主动的向服务器端发送连接请求,然后对输入文本框注册事件监听并且发送给客服端,不断的监听服务器端发来的信息,然后显示出来。
七、详细设计
服务器端:
创建服务端接口
建立一个seversocket的类svsocket
创建一个容器用来管理客户端进程
开始监听,监听是否有客户端连接,有的话与其建立连接
分配ID
监听线程
监听端口是否有消息传入
如果有的话接收信息
再将信息发送到其他的所有的客服端
当某客户离开,结束他与其他人的通信
为客户端连接创建线程
从容器vector中删除该线程
表示该线程已经离开聊天室,结束两者之间连接
图7.1 服务器端整体设计流程图
客户端建立一个seversocket的类,并且创建一个vector用来管理客户端的线程,然后就开始检测,如果有客户端请求与服务器连接就与其建立socket连接,创建进程设置ID,告诉其他的客户端有新的客户端接入,然后开始监听所有的客户端线程如果有信息通过端口进入就接受然后再发送给其他客户端,如果有客户端退出,就会告诉其他的客户端并且关闭与该客户端的socket连接,然后在vector里面删除相应的线程。
主界面
服务器界面,当运行服务器端则出现
运行客服端登录
心得体会:
经过两个星期的学习和实践,我也算是顺利的完成了计算机网络课程设计,同时对计算机网络有一个大致的了解,在实践过程中遇到了很多的困难,感觉自己很难将理论与实践相结合,觉得我们学习的那些知识也派不上用场,通过这次课设,我深刻的认识到实践与理论必须 要想结合才能使所学的知识变成可用的,通过自己的努力和老师同学们的帮助,多了一种看待问题的角度;我也发现了我所学知识不扎实,只是走马观花的应付考试,在以后的学习中需要时刻的告诫自己,踏踏实实做好每一步。
本次课程设计是利用java语言编写的,虽说自己以前接触过这种语言,但是经过这两周的学习,我受益匪浅。首先,我进一步弄懂了JAVA语言的编程方法和原则,并学会了编写java程序。其次,使我更深层次的理解到JAVA语言是一种面向对象的语言,具有可视化编程的特点且代码具有可移植等特点。本次课程设计是实现一个UDP即时通讯程序。经过编写程序和运行,调试程序,我对java语言有了更深的了解。另外,使我对UDP协议的特点和工作过程有了更深的了解。UDP协议提供无连接的、不可靠的服务。它工作于传输层,是传输层的重要协议之一。最后,UDP协议是利用客户端和服务器端模型来实现传输的. 本次课程设计使我明白了知识的重要性,同时也更加懂得实践更不可少。我们要经常把所学的知识运用到实践,这样,才能充分的融会贯通。同时,也使我认识到自己动手能力太差,为我以后的学习和发展提供了一个警钟!本次课程设计的过程中,虽然遇到了不少问题,但最后还是成功的完成了.
这次课设对于我而言是有很大的收获,懂得独立寻找资料学习,这是最大的收获,在以后的工作中肯定会面临不懂得问题,有了这次课设的经验,就能够去独立学习并解决困难。
主要代码
package server;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt。event.ActionListener;
import java.awt.event.WindowAdapter;
import java。awt。event.WindowEvent;
import java.io。DataInputStream;
import java。io。DataOutputStream;
import java.io。IOException;
importjava.io。ObjectInputStream;
importjava。io。ObjectOutputStream;
import .ServerSocket;
import java.net。Socket;
import java.util。ArrayList;
import java。util。Date;
import javax.swing。JButton;
import javax.swing.JFrame;
importjavax.swing。JLabel;
import javax。swing.JOptionPane;
importjavax.swing.JScrollBar;
import javax。swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
publicclassserverextends JFrame{
private JTextArea allmsg;
private JTextField currnum,totalnum,copyright,chatmsg;
private JButton send;
private JScrollPane js;
intnum1,num2,port;/* -— num1:当前在线人数 num2:总上线人数 port:服务端口号 --*/
private ServerSocket ss;
ArrayListlists;//存放所有在线用户
public server()
{
super(”聊天室服务器端");
this。setSize(310,660);
this。setLocation(200,50);
lists = newArrayList();
num1 = num2 =0;
port = 7777;
currnum = new JTextField(" 当前在线人数: "+num1);
currnum.setEnabled(false);
totalnum = new JTextField(” 上线总人数: ”+num2);
totalnum。setEnabled(false);
allmsg = new JTextArea();
allmsg.append(” —-—------—--—-— 系统消息 ——--—--—--———-\n”);
allmsg.setEditable(false);
allmsg.setLineWrap(true); //允许自动换行
js = new JScrollPane(allmsg);//为JTextArea添加滚动条
chatmsg = new JTextField(”在此输入系统信息”);
chatmsg。addActionListener(new ActionListener(){
publicvoid actionPerformed(ActionEvent arg0) {
String str = chatmsg。getText()。trim();
if(!""。equals(str))
{sendmsg((new Date())。toLocaleString()+" —- 系统消息: "+str);chatmsg。setText(””);}
else
JOptionPane。showMessageDialog(null, "消息不能为空","错误",JOptionPane。OK_OPTION);
chatmsg.setText("");/* —— 发送信息后,将输入栏中的信息清空 —- */
}
});
send = new JButton("发送");
send。addActionListener(new ActionListener(){
publicvoid actionPerformed(ActionEvent arg0) {
String str = chatmsg.getText().trim();
if(!"".equals(str))
{sendmsg((new Date())。toLocaleString()+” —- 系统消息: "+str);chatmsg.setText("");}
else
JOptionPane.showMessageDialog(null, "消息不能为空","错误”,JOptionPane。OK_OPTION);
chatmsg.setText(””);/* -— 发送信息后,将输入栏中的信息清空 -— */
}
});
addcomponettocontainer();
this.addWindowListener(new WindowAdapter(){
publicvoid windowClosing(WindowEvent we)
{
sendmsg(”SYSTEM_CLOSED”);/* -- 向客户端发送服务器关闭信息 -- */
destory();
}
});
start(); /* —- 启动连接服务 -- */
}
publicvoid addcomponettocontainer()
{
Container c = this。getContentPane();
c.setLayout(null);
currnum.setBounds(20,15,130,20);
totalnum.setBounds(155,15,125,20);
js.setBounds(10,50,280,500);
chatmsg.setBounds(10,560,180,30);
send.setBounds(220,560,70,30);
copyright。setBounds(10,600,280,20);
c。add(currnum);
c。add(totalnum);
c.add(js);
c。add(chatmsg);
c。add(send);
c.add(copyright);
this。setVisible(true);
this.setResizable(false);
}
publicvoid start()
{
boolean isStarted = false;/* —- 用于标记服务器是否已经正常启动 -- */
try {
this.ss = new ServerSocket(port);
isStarted = true;
this.allmsg。append((new Date())。toLocaleString()+” 服务器启动 @ 端口: ”+port+”\n”);
while(isStarted)
{
Socket client = this.ss.accept(); /* —— 监听客户端的连接 -- */
DataInputStream in = new DataInputStream(client.getInputStream());
String name = in。readUTF();
user u = new user();
u。name = name;
u.socket = client;
lists。add(u); //将该用户加到列表中去
num1++;
num2++;
currnum.setText(" 当前在线人数: "+num1);
totalnum.setText(” 上线总人数: "+num2);
this.allmsg.append((new Date()).toLocaleString()+" : "+u.name+" 登录 \n");
new Thread(new ClientThread(u))。start();/* -- 为该用户启动一个通信线程 -- */
}
} catch (IOException e) {
System.out。println("服务器已经启动.。。...");
System。exit(0);
}
}
class ClientThread implements Runnable
{
user user = null;
booleanisConnected = true;
DataInputStream dis = null;
DataOutputStream dos = null;
public ClientThread(user u)
{
this。user = u;
try {
this。dis = new DataInputStream(this。user。socket。getInputStream());
this。dos = new DataOutputStream(this.user.socket.getOutputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
publicvoid run()
{
readmsg();
}
/* —— 读取客户的聊天信息 -— */
publicvoid readmsg()
{
while(isConnected)
{
try {
String msg = dis。readUTF();
if(”quit&logout"。equals(msg))//当用户关闭客户端窗口时,发送quit字符串 表示用户已经退出
{
num1—-;
try{
this.dis.close();
this.dos.close();
this.user。socket.close();
this.isConnected = false;
}catch(IOException ioe)
{
ioe。printStackTrace();
}finally{
this.isConnected = false;
if(dis!=null) this.dis.close();
if(dos!=null) this.dos。close();
if(this.user.socket!=null) this。user.socket.close();
}
lists。remove(this.user);//从列表中删除该用户
currnum。setText(" 当前在线人数: ”+num1);
allmsg.append((new Date()).toLocaleString()+” : "+this.user.name+" 退出\n");
}
else
sendmsg(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/* —— 将信息进行转发 -— */
publicvoid sendmsg(String msg)
{
user us = new user();
DataOutputStream os = null;
if(lists。size()>0)
{
for(int i=0;i<lists。size();i++)
{
us = (user)lists.get(i);
try {
os = new DataOutputStream(us.socket。getOutputStream());
os.writeUTF(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
else
JOptionPane.showMessageDialog(null, ”当前无用户在线.发送消息失败”,"失败”,JOptionPane。OK_OPTION);
}
publicvoid destory()
{
try {
this。ss.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this。dispose();
}
publicstaticvoid main(String[] args) {
new server();
}
}
package client;
import java.awt.*;
import javax。swing。*;
import java.awt。event.*;
import java。io。*;
import java.net。*;
publicclassclientextends JFrame{
private JTextField name;
private JTextField ip;
private JButton ok,cancle;
public Socket socket;
public client()
{
super("登录框");
this.setSize(400,80);
this.setLocation(100,100);
name = new JTextField(”昵称");
ip = new JTextField(”127.0.0.1”);
ok = new JButton(”登录”);
cancle = new JButton(”取消");
ok.addActionListener(new listenEvent());
cancle.addActionListener(new listenEvent());
addcomponettocontainer();
this。setDefaultCloseOperation(EXIT_ON_CLOSE);
}
publicvoid addcomponettocontainer()
{
Container c = this.getContentPane();
c。setLayout(null);
name。setBounds(10,10,100,30);
ip.setBounds(120,10,100,30);
ok.setBounds(230,10,70,30);
cancle。setBounds(310,10,70,30);
c.add(name);
c.add(ip);
c。add(ok);
c.add(cancle);
this。setVisible(true);
this。setResizable(false);
}
publicclass listenEvent implements ActionListener
{
publicvoid actionPerformed(ActionEvent event) {
// TODO Auto—generated method stub
if(event。getSource()==ok)
{
String n = name。getText().trim();
String i = ip.getText()。trim();
if(""。equals(n)||”".equals(i))
{
JOptionPane.showMessageDialog(null,"昵称、IP不能够为空!”,”错误",JOptionPane。OK_OPTION);
}
else{login(n,i);}
}
if(event。getSource()==cancle)
{
name.setText("”);
ip.setText("”);
}
}
}
publicvoid login(String name,String ip)
{
try {
socket = new Socket(ip,7777);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeUTF(name);
out.flush();//强制输出缓存中的内容
//out.close();
new ClientFrame(name,socket);
destroywindow();
} catch (UnknownHostException e) {
JOptionPane.showMessageDialog(null,”找不到主机地址(IP错误/网络故障)!”,"错误",JOptionPane.OK_OPTION);
} catch (IOException e) {
}
}
publicvoid destroywindow()
{
this.dispose();
}
publicstaticvoid main(String[] args)
{
new client();
}
}
package server;
import java.net.*;
publicclass user {
String name;
Socket socket;
}
package client;
import java.awt.Color;
import java。awt.Container;
import java。awt.event.ActionEvent;
import java.awt。event.ActionListener;
import java.awt。event。WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java。io。DataOutputStream;
import java.io。IOException;
import java.net.Socket;
import java.util.Date;
import javax.swing。JButton;
import javax。swing.JFrame;
import javax。swing.JOptionPane;
import javax。swing。JScrollPane;
import javax。swing.JTextArea;
import javax。swing。JTextField;
publicclassClientFrameextends JFrame{
private JTextArea allmsg;
private JTextField welcome,copyright,chatmsg;
private JButton send;
private JScrollPane js;
privatebooleanisConnected = true;
public DataOutputStream out;
public DataInputStream in;
public Socket s = null;
String nic; /* —- 保存用户昵称 --*/
/**
* 初始化客户端资源
* 1.获取从LoginFrame传递过来的参数
* 2。初始化界面元素
* 3.初始化通信所需要的资源 EG:输入/输出流(DataInputStream/DataOutputStream)
* */
public ClientFrame(String name,Socket socket)
{
this.setSize(310,660);
this.setLocation(290,50);
this。setTitle("聊天室客户端<"+name+">”);/* -— 指定窗口的标题 -—*/
thi
展开阅读全文