目 录
一、需求分析.......................................................................... - 1 -
二、概要设计.......................................................................... - 3 -
三、详细设计.......................................................................... - 4 -
四、设计和调试分析.............................................................. - 8 -
五、测试结果........................................................................ - 11 -
六、参考文献........................................................................ - 12 -
一、需求分析
理解基于 UDP 的网络编程技术,分析类似于 QQ 群聊程序设计原理和程
序流程,选择合适的开发环境,参考已有的群聊程序功能,设计模拟实现基
于 UDP 的群聊应用程序。
1.UDP 协议的理解:
UDP 协议是英文 UserDatagramProtocol 的缩写,即用户数据报协议,主
要用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议
系统在内的众多的客户/服务器模式的网络应用都需要使用 UDP 协议。UDP
协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协
议所掩盖,但是即使是在今天,UDP 仍然不失为一项非常实用和可行的网络
传输层协议。UDP 协议直接位于 IP(网际协议)协议的顶层。UDP 协议的主
要作用是将网络数据流量压缩成数据报的形式。一个典型的数据报就是一个
二进制数据的传输单位。每一个数据报的前 8 个字节用来包含报头信息,剩
余字节则用来包含具体的传输数据。UDP 协议使用端口号为不同的应用保留
其各自的数据传输通道。正是采用这一机制实现对同一时刻内多项应用同时
发送和接收数据的支持。数据发送一方(可以是客户端或服务器端)将 UDP
数据报通过源端口发送出去,而数据接收一方则通过目标端口接收数据。有
的网络应用只能使用预先为其预留或注册的静态端口;而另外一些网络应用
则可以使用未被注册的动态端口。因为 UDP 报头使用两个字节存放端口号,
所以端口号的有效范围是从 0 到 65535。一般来说,大于 49151 的端口号都
代表动态端口。数据报的长度是指包括报头和数据部分在内的总的字节数。
因为报头的长度是固定的,所以该域主要被用来计算可变长度的数据部分
(又称为数据负载)。数据报的最大长度根据操作环境的不同而各异。从理
论上说,包含报头在内的数据报的最大长度为 65535 字节。不过,一些实际
应用往往会限制数据报的大小,有时会降低到 8192 字节。UDP 协议使用报头
中的校验值来保证数据的安全。校验值首先在数据发送方通过特殊的算法计
算得出,在传递到接收方之后,还需要再重新计算。如果某个数据报在传输
过程中被第三方篡改或者由于线路噪音等原因受到损坏,发送和接收方的校
验计算值将不会相符,由此 UDP 协议可以检测是否出错。UDP 协议并不提供
数据传送的保证机制。如果在从发送方到接收方的传递过程中出现数据报的
- 1 -
丢失,协议本身并不能做出任何检测或提示,由于排除了信息可靠传递机制,
将安全和排序等功能移交给上层应用来完成,极大降低了执行时间,使速度
得到了保证。
2.基于 C/S 的多客服端相互通信原理分析:
在 C/S 模式中,数据的分发采用专门的服务器,多个客户端都从此服务
器获取数据。这种模式的优点是:数据的一致性容易控制,系统也容易管理。
3.套接字编程原理分析:
图 1.1 套 接 字 编 程 原 理 图
注 释 :socket(),使 用 前 创 建 一 个 新 的 套 接 字 ;bind(),将 套 接
字 地 址 与 所 创 建 的 套 接 字 号 联 系 起 来 ; send()与 recv() , 数 据 的 发
送 与 接 收 ;closesocket(), 关 闭 套 接 字 。
- 2 -
二、概要设计
服务器
客户机甲
客户机乙
客户机丙
图 2.1 整体框架设计图
服务器端主要实现的功能是启动一个监听的进程,开放自己的端口号为
5555,不断的监听是否有新的客服端进程向自己发送连接请求,为每一个主
动连接自己的客户端设置一个 ID 号设置一个 threads 的容器用来管理客户端
的线程。与客户端建立连接,实现 socket 通信,对于服务器端是先接受数
据流然后再发送数据流,客服端发送过来的信息经服务器端然后转发到其他
所有的客户端,服务器端相当于中间的桥梁。
客户端要求主要实现的功能是建立一个图形的界面,用于显示聊天信息
等,并且建立与服务器端的通信,主动的向服务器端发送连接请求,然后对
输入文本框注册事件监听并且发送给客服端,不断的监听服务器端发来的信
息,然后显示出来。
- 3 -
三、详细设计
服务器端:
建立一个 seversocket 的类 svsocket
创建一个容器用来管理客户端进程
创建服务端接口
开始监听,监听是否有客户端连
接,有的话与其建立连接
为客户端连接创建线程
分配 ID
监听线程
监听端口是否有消息传入
如果有的话接收信息
再将信息发送到其他的所有的客服端
当某客户离开,给其他客户发送提示消息
从容器 vector 中删除该线程
表示该线程已经离开聊天室,
结束两者之间连接
图 3.1 服务器端整体设计流程图
- 4 -
客户端建立一个 seversocket 的类,并且创建一个 vector 用来管理客
户端的线程,然后就开始检测,如果有客户端请求与服务器连接就与其建立
socket 连接,创建进程设置 ID,告诉其他的客户端有新的客户端接入,然
后开始监听所有的客户端线程如果有信息通过端口进入就接受然后再发送
给其他客户端,如果有客户端退出,就会告诉其他的客户端并且关闭与该客
户端的 socket 连接,然后在 vector 里面删除相应的线程
服务器端主要功能程序代码如下:
import java.net.*;
import java.io.*;
import java.util.*;
public class ChatServer{
public static void main(String[] args)throws Exception{
ServerSocket svSocket =null;
//Vector threads 为ServerThread集合
Vector threads = new Vector();
//开始监听
System.out.println ("listening...");
try {
//创建服务端套接口
svSocket = new ServerSocket(5555);
}catch (Exception ex) {
System.out.println ("Server create ServerSocket failed!");
return;
}
try{
int nid = 0;
//监听是否有客户端连接
while(true){
Socket socket = svSocket.accept();
System.out.println ("accept a client");
//创建一个新的ServerThread
ServerThread st = new ServerThread(socket,threads);
- 5 -
//为客户端分配一个ID号
st.setID(nid++);
threads.add(st);
new Thread(st).start();
//向所有人广播有新客户端连接
for(int i=0;i < threads.size();i++){
ServerThread temp = (ServerThread)threads.elementAt(i);
st.write("<#>Welcome "+ temp.getID()+" to enter the chatroom");
}
System.out.println ("Listen again...");
}
}catch(Exception ex){
System.out.println ("server is down");
}
}
}
/**
* 监听线程,监听是否有客户端发消息过来
*/
class ServerThread implements Runnable{
private Vector threads;
private Socket socket = null;
private DataInputStream in = null;
private DataOutputStream out = null;
private int nid;
//构造器
public ServerThread(Socket socket,Vector threads){
this.socket = socket;
this.threads = threads;
try {
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
}
catch (Exception ex) {
}
}
- 6 -
//实现run方法
public void run(){
System.out.println ("Thread is running");
try{
//监听客户端是否发消息过来
while(true){
String receive = in.readUTF();
if(receive == null)
return;
//当某客户离开,给其它客户端发消息
if(receive.equals("leave")){
for(int i=0;i < threads.size();i++){
ServerThread st = (ServerThread)threads.elementAt(i);
st.write("***"+getID()+"leaving...***");
}
}else{
//把某客户端发过来的发送到所有客户端
for(int i=0;i < threads.size();i++){
ServerThread st = (ServerThread)threads.elementAt(i);
st.write("<"+getID()+">: "+receive);
}
}
}
}catch(Exception ex){
//从Vector中删除该线程,表示该线程已经离开聊天室
threads.removeElement(this);
//ex.printStackTrace();
}
try{
socket.close();
}catch(Exception ex){
ex.printStackTrace();
}
}
/**
* 服务端向客户端发送信息
*/
- 7 -