资源描述
重庆交通大学信息科学及工程学院
课程设计实验报告
专 业:
学 号:
姓 名:
实验室(中心): 信息技术软件实验室
指 导 教 师 :
实验完成时间: 2015 年 1 月 12 日
21 / 24
目录
一、 实验设计题目---------------------------------------------------------------------2
二、 实验目---------------------------------------------------------------------------2
三、 实验设计要求---------------------------------------------------------------------2
四、 课程设计条件---------------------------------------------------------------------2
五、实验设计分析---------------------------------------------------------------------4
六、实验设计流程图------------------------------------------------------------------9
七、结果分析---------------------------------------------------------------------------13
八、实验心得体会---------------------------------------------------------------------14
九、实验主要代码---------------------------------------------------------------------15
一、 实验设计题目
基于TCP服务器/客户端程序设计
二、 实验目
1、 理解客户端及服务器模型工作原理。
2、 掌握套接字概念。
3、 掌握TCP协议,基于TCP协议来设计此客户端/服务器程序。
4、 通过设计面向连接数据流传输服务程序,加深对面向连接服务程序工作流程和基本框架理解。
三、 实验设计要求
1)任选一种编程语言,编程实现面向连接客户/服务器程序,客户端、服务器端分别编程;
2) 编程要充分体现服务器端及客户端连接建立、数据传输、连接释放过程;
四、课程设计条件
本次课程设计我采用是JAVA语言,实现客户端和服务器之间联系。
Java 编程语言风格十分接近C、C++语言。Java是一个纯面向对象程序设计语言,它继承了 C++语言面向对象技术核心。Java舍弃了C ++语言中容易引起错误指针(以引用取代)、运算符重载(operator overloading)、多重继承(以接口取代)等特性,增加了垃圾回收器功能用于回收不再被引用对象所占据内存空间,使得程序员不用再为内存管理而担忧。在 Java 1.5 版本中,Java 又引入了泛型编程(Generic Programming)、类型安全枚举、不定长参数和自动装/拆箱等语言特性。
Java 不同于一般编译执行计算机语言和解释执行计算机语言。它首先将源代码编译成二进制字节码(bytecode),然后依赖各种不同平台上虚拟机来解释执行字节码。从而实现了“一次编译、到处执行”跨平台特性。不过,每次执行编译后字节码需要消耗一定时间,这同时也在一定程度上降低了 Java 程序运行效率。
Java语言变量声明,操作符形式,参数传递,流程控制等方面和C语言,C++语言完全相同.尽管如此,Java和C语言,C++语言又有许多差别,主要表现在如下几个方面:
Java中对内存分配是动态,它采用面向对象机制,采用运算符new为每个对象分配内存空间,而且,实际内存还会随程序运行情况而改变。程序运行中 Java系统自动对内存进行扫描,对长期不用空间作为”垃圾”进行收集,使得系统资源得到更充分地利用.按照这种机制,程序员不必关注内存管理问题,这使Java程序编写变得简单明了,并且避免了由于内存管理方面差错而导致系统出问题。而C语言通过malloc()和free()这两个库函数来分别实现分配内存和释放内存空间,C++语言中则通过运算符new和delete来分配和释放内存。在C和C++这种机制中,程序员必须非常仔细地处理内存使用问题。一方面,如果对己释放内存再作释放或者对未曾分配内存作释放,都会造成死机;而另一方面,如果对长期不用或不再使用内存不释放,则会浪费系统资源,甚至因此造成资源枯竭。
Java不在所有类之外定义全局变量,而是在某个类中定义一种公用静态变量来完成全局变量功能。
Java不用goto语句,而是用try-catch-finally异常处理语句来代替goto语句处理出错功能。
Java不支持头文件,而C和C++语言中都用头文件来定义类原型,全局变量,库函数等,这种采用头文件结构使得系统运行维护相当繁杂。
Java不支持宏定义,而是使用关键字final来定义常量,在C++中则采用宏定义来实现常量定义,这不利于程序可读性。
Java对每种数据类型都分配固定长度。比如,在Java中,int类型总是32位,而在C和C++中,对于不同平台,同一个数据类型分配不同字节数,同样是int类型,在PC机中为二字节即16位,而在VAX-11中,则为32位.这使得C语言造成不可移植性,而Java则具有跨平台性(平台无关性)。
Java语言编写类库可以在其它平台Java应用程序中使用,而不像C++语言必须依赖于Windows平台。
五、实验设计分析
本实验设计是基于TCP/IP协议程序时,传输层使用TCP协议,它最大特点是在通信之前要在客户和服务器之间先建立连接,在数据传输完成后要关闭连接,释放网络资源。
对于TCP协议,主要有以下特点:
(1) TCP是面向连接运输层协议。应用程序在使用TCP协议之前,必须先建立TCP连接。在传送数据完毕后,必须释放已经建立TCP连接。也就是说,应用进程之间通信好像在“打电话”:通话前要先拨号建立连接,通话结束后要挂机释放连接。
(2) 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点,即一对一连接。
(3) TCP提供可靠交付服务。通过TCP连接传送数据,无差错、不丢失、不重复,并且按序到达。
(4) TCP提供全双工通信。TCP允许通信双方应用进程在任何时候都能发送数据。TCP连接两端都设有发送缓存和接受缓存,用来临时存放双向通信数据。在发送时,应用程序在把数据传送到TCP缓存后,就可以做自己事,而TCP在合适时候把数据发送出去。在接收时,TCP把收到数据放入缓存,上层应用进程在合适时候读取缓存中数据。
(5) 面向字节流。TCP中“流”指是流入到进程或从进程流出字节序列。虽然应用程序和TCP交互是一次一个数据块,但TCP把应用程序交下来数据看成仅仅是一连串无结构字节流。TCP不保证接收方应用程序所收到数据块和发送方应用程序所发出数据块具有对应大小关系。但接收方应用程序收到字节流必须和发送方应用程序发出字节流完全一样。TCP连接是一条虚连接而不是一条真正物理连接。TCP报文段先要传送到IP层,加上IP首部后,再传送到数据链路层。再加上数据链路层首部和尾部后,才离开主机发送到物理链路。
每一条TCP连接有两个端点,这个端点就是套接字(socket),端口号拼接到IP地址即构成了套接字,每一条TCP连接唯一地被通信两端两个端点,即两个套接字所确定。同一个IP地址可以有多个不同TCP连接,而同一个端口号也可以出现在不同TCP连接中。
TCP是面向连接协议。运输连接是用来传送TCP报文。TCP运输连接建立和释放是每一次面向连接通信中必不可少进程。因此,运输连接就有三个阶段,即:连接建立、数据传送和连接释放。运输连接管理就是使运输连接建立和释放都能正常进行。
在TCP连接建立过程中要解决一下三个问题:
(1) 要使每一方能够确知对方存在。
(2) 要允许双方协商一些参数(如最大窗口值、是否使用窗口扩大选项和时间戳选项及服务质量等)。
(3) 能够对运输实体资源(如缓存大小、连接表中项目等)进行分配。
TCP连接建立采用客户服务器方式。主动发起连接建立应用进程叫做客户,而被动等待连接建立应用进程叫做服务器。
CLOSED
LISTEN
SYN-
RCVD
ESTAB-
LISHED
CLOSED
SYN-
SENT
ESTAB-
LISHED
服务器B
客户A
下图是TCP连接建立过程。
数据传送
主机A运行是TCP客户程序,而B运行TCP服务器程序。最初两端TCP进程都处于CLOSED状态。A是主动打开连接,而B是被动打开连接。
BTCP服务器进程先创建传输控制块TCB,准备接受客户进程连接请求。然后服务器进程就处于LISTEN状态,等待客户连接请求。如有,即做出响应。
ATCP客户进程也是首先创建传输控制模块TCB,然后向B发出连接请求报文段,这时首部中同步位SYN=1,同时选择一个初始序号seq=x。SYN报文段不能携带数据,但要消耗掉一个序号。这时,TCP客户进程进入SYN-SENT状态。
B收到连接请求报文段后,如同意建立连接,则向A发送确认。在确认报文段中应把SYN位和ACK位都置1,确认号是ack=x+1,同时也为自己选择一个初始序号seq=y。这个报文段也不能携带数据,但同样要消耗掉一个序号。这时TCP服务器进程进入SYN-RCVD状态。
TCP客户进程收到B确认后,还要向B给出确认。确认报文段ACK置1,确认号ack=y+1,而自己序号seq=x+1,ACK报文段可以携带数据,但如果不携带数据则不消耗序号,在这种情况下,下一个数据报文段序号仍是seq=x+1。这时,TCP连接已经建立,A进入ESTABLISHED状态。当B收到A确认后,也进入ESTABLISHED状态,这个过程就是三次握手。
数据传输结束后,通信双方都可释放连接。现在A和B都处ESTABLISHED状态。A应用进程先向其TCP发出连接释放报文段,并停止再发送数据,主动关闭TCP连接。A把连接释放报文段首部终止控制位FIN置1,其序号seq=u,它等于前面已传送过数据最后一个字节序号加1。这时A进入FIN-WAIT-1状态,等待B确认。FIN报文段即使不携带数据,它也消耗掉一个序号。
B收到连接释放报文段后即发出确认,确认号是ack=u+1,而这个报文段自己序号是v,等于B前面已传送过数据最后一个字节序号加1。然后B就进入CLOSE-WAIT状态。TCP服务器进程这时应停止高层应用进程,因而从A到B这个方向连接就释放了,这时TCP连接处于半关闭状态,即A已经没有数据要发送了,但B若发送数据,A仍要接收。也就是说,从B到A这个方向连接并为关闭,这个状态可能会持续一些时间。
A收到来自B确认后,就进入FIN-WAIT-2状态,等待B发出连接释放报文段。
若B已经没有要向A发送数据,其应用进程就通知TCP释放连接。这时B发出连接释放报文段必须使FIN=1。假定B序号为w,B还必须重复上次已发送过确认号ack=u+1。这是B就进入LAST-ACK状态,等待A确认。
A在收到B连接释放报文段后,必须对此发出确认。在确认报文段中把ACK置1,确认号ack=w+1,而自己序号是seq=u+1。然后进入到TIME-WAIT状态。要经过4分钟才能进入到CLOSED状态,才能开始建立下一个新连接,当A撤销相应传输控制块TCB后,就结束了这次TCP连接。这个过程就是TCP连接释放过程四次握手。
TCP连接释放采用四次握手机制。任何一方都可以在数据传送结束后发出连接释放通知,待对方确认后就进入半关闭状态。当另一方也没有数据再发送时,则发送连接通知,对方确认后就完全关闭了TCP连接。如下图所示。
服务器B
客户 A
ESTAB-LISHED
CLOSE-
WAIT
LAST-
ACK
CLOSED
ESTAB-LISHED
FIN-
WAIT-1
FIN-
WAIT-2
TIME-
WAIT
CLOSED
主动
数据传送
被动
TCP虽然是面向字节流,但TCP传送数据单元却是报文段。一个TCP报文段分为首部和数据两部分,而TCP全部功能都体现在它首部中各字段作用。因此,只有弄清TCP首部各字段作用才能掌握TCP工作原理。
TCP报文段首部前20个字节是固定,后面有4n字节是根据需要而增加选项。因此TCP首部最小长度是20字节。如下图所示。
对于TCP报文段中序号部分,它是占了4 字节,TCP是面向字节流,在下一个TCP连接中传送字节流中每一个字节都按顺序编号,整个要传送字节流起始序号必须在连接建立时设置。首部中序号字段值则指是本报文段所发送数据第一个字节序号。例如,一报文段序号字段值是301,而携带数据共有100字节。这就表明:本报文段数据第一个字节序号是301,最后一个字节序号是400。显然,下一个报文段数据序号应该从401开始。
对于确认号,是占4个字节,是期望收到对方下一个报文段第一个数据字节序号。例如,B正确收到了A发送过来一个报文段,其序号字段值是501,而数据长度是200字节,这表明B正确收到了A发送到序号700为止数据。因此,B期望收到A下一个数据序号是701,于是B在发送给A确认报文段中把确认号置为701。
还有一个比较重要部分就是校验和。是占2个字节,校验和字段检验范围包括首部和数据这两部分。在计算校验和时,要在TCP报文段前面加上12字节伪首部。伪首部格式及UDP用户数据报伪首部一样。但应把伪首部第4个字段中17改为6,把第5字段中UDP长度改为TCP长度,接收方收到此报文段后,仍要加上这个伪首部来计算校验和。
TCP可靠传输实现是已字节为单位滑动窗口。发送窗口是根据接收窗口设置,但在同一时刻,发送窗口并不总是和接受窗口一样大。这是因为通过网络传送窗口值需要经历一定时间滞后。发送方还可以根据网络当时拥塞情况适当减小自己发送窗口值。
对不按序到达数据是先临时存放在接受窗口中,等到字节流中所缺少字节收到后,再按序交付上层应用进程。
TCP要求接收方必须有累积确认功能,这样可以减少传输开销。接收方可以在合适时候发送确认,也可以在自己有数据要发送时把确认信息顺便捎带上。但是,接收方也不应该过分推迟发送确认,否则会导致发送方不必要重传,这回浪费网络资源。其次,捎带确认实际上并不经常发生,因为大多数应用程序不同时在两个方向上发送数据。
六、实验设计流程图
本课程设计采用是JAVA语言编制,在JAVA中,客户端和服务器之间通信编程一般是基于socket实现。Socket是两个实体之间进行通信有效端点,通过socket可以获得源IP地址和源端口、终点IP地址和终点端口,并创建一个能被多人使用分布式应用程序,实现及服务器双向自由通信。
本设计是一对多socket通信,即一个服务器对应多个客户端,采用方法是将socket对象放置在线程中,这样当每一个socket对象执行完任务后,只有包含该socket对象线程会终止,对其他线程没有任何影响。
服务器:服务器是用来接收客户端各种信息,并把信息传送回给客户端。网络应用程序一般是以客户机/服务器模型方式工作。因特网是客户机/服务器模型一个典型应用。在这种工作方式中,一个服务器程序通常事先启动,并在一个熟知端口侦听对服务器请求。当客户机应用程序需要某种服务时,需向提供这种服务服务器发出请求,服务器在接收到请求后,向客户机发出相应请求信息。这样客户机应用程序和服务器程序之间便建立了通信连接此后可以进行数据通信。通信任务完成后需要关闭它们之间通信连接。
开始
客户端:
设置一个新的socket,定义端口号为5500,连接到本机
将输入数据流连接到socket上
将数据输出流连接到socket上
打印输入待求平方值,输入bye结束
将输入数据流读入到缓存中
进行一个while死循环
读入用户的写入
写到socket中
写到socket中,清空缓存区,立即发送,从socket中读数据
输入为bye?
N
Y
返回结果
结束
服务器线程:相当于服务器里面socket一个集合,执行一次,就运行一个socket对象,当每一个socket对象执行完任务后,只有包含该socket对象线程会终止,对其他线程没有任何影响。
结束
输出结果
进行平方运算
将接收到的值打印出来
清空缓存区,立即发送
将bye写入到socket中
Y
数据为bye?
读缓存中的数据
执行while死循环
在run()方法中与客户端通信
启动run()方法
开始
在构造方法中为每个套接字连接输入和输出流
七、结果分析
首先,运行是服务器主程序,由显示可知,服务器已处于等待连接状态。
然后运行是客户端程序代码。可知,服务器及客户端已建立了连接。此连接建立是通过服务器和客户端都有一个端口号一样socket,这样才可以建立连接。
连接建立以后,就要开始进行数据传输了,通过客户端输入要传送数据,此数据通过客户端和服务器之间建立连接进入到服务器。在服务器中又调用线程程序,来进行数据出来,本设计是对数据进行求平方,并把结果返回给客户端。
在客户端输入bye之后,便删除了所建立连接。多次运行客户端程序,即可实现一对多通信过程,一个服务器对应多个客户端,每次执行完一次,便终止此socket对象,下次执行时候,再进行另外一个线程。
八、实验心得体会
经过不断修改及查找资料,终于完成了此次课程设计实验,虽说这次设计实验不是很大型,但是还是学到了很多东西,收获颇丰,不仅学习到了一些新知识,回顾了一些以前快要遗忘知识点,而且是自己学习目标更加明确,学习方法更加完善,也体会到了软件开发趣味,更加清楚认识到了自己在软件开发及学习上不足之处。
此次设计是服务器及客户端之间进行通信,老师在我们设计之前讲解很清晰,使得我们大家操作起来比较娴熟。让我感觉到了软件编程趣味性和实用性,虽说一些技术我们在课堂上也曾学习过,但是大都停留在理论基础上,实际开发很少,而这次实验给了我们一个很好边学习边实践机会,对我们深入学习这些技术有很大帮助,深刻体会到了这些技术实用性。这次课程设计编程我采用是JAVA语言,虽说以前没有学习过这门语言,可是经过两个多星期查找资料及询问同学,大致上对于JAVA编程方法还是有了一定理解和认识。JAVA功能很强大,有很多要实现功能,只要通过调用一些函数就可以实现了,确实是很好用一门语言。每当自己成功调试一段代码或者通过自己努力克服一个技术困难,都颇有收获感。这次实验让我们体验了软件实用性,发现自己不足,增加了一定编程经验。结束了此次实验,让我发现我对我们专业有了新认识,通过这次实验,我了解到,要真真正正掌握到计算机程序不是一件简单事情,但真正掌握后,它带给我们将是无穷便捷及科技,我会努力学习计算机!
九、实验主要代码
【服务器线程程序】
package cn.edu.cqjtu.socket2;
import java.io.*;
import .*;
public class ServerThread extends Thread{
private Socket s;
private DataInputStream is;//输入数据流
private DataOutputStream os;//输出数据流
//在构造方法中为每个套接字连接输入和输出流
public ServerThread(Socket socket) throws IOException{
super();
s=socket;
is=new DataInputStream(s.getInputStream());
os=new DataOutputStream(s.getOutputStream());
start(); //启动run()方法
}
//在run()方法中及客户端通信
public void run(){
try{
String str;
double result,zhi;
boolean NotEnd=true;
while(NotEnd){
str=is.readUTF();//读数据
if(!str.equals("bye")){
zhi=Double.parseDouble(str);
System.out.println("接收到值为:"+zhi);
result=zhi*zhi;
str=Double.toString(result);
os.writeUTF(str);//写入数据
os.flush();//清空缓存
System.out.println("平方值"+str+"已经发送");
}else{
NotEnd=false;
os.writeUTF("bye");
os.flush();
}
}
is.close();
os.close();
s.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
【服务器主程序】
package cn.edu.cqjtu.socket2;
import java.io.*;
import .*;
public class MultiServer {
public static void main(String[]args){
try{
System.out.println("等待连接");
ServerSocket serverSocket=new ServerSocket(5500);
Socket s=null;
while(true){
//等待客户端请求
s=serverSocket.accept();
//每次请求都等待一个线程来处理
new ServerThread(s);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
【客户端程序】
package cn.edu.cqjtu.socket2;
import java.io.*;
import .*;
public class Client {
public static void main(String[]args){
try{
//连接到本机,端口号为5500
Socket s=new Socket("localhost",5500);
//将数据输入流连接到socket上
DataInputStream is=new DataInputStream(s.getInputStream());
//将数据输出流连接到socket上
DataOutputStream os=new DataOutputStream(s.getOutputStream());
System.out.println("输入待求平方值,输入bye结束。");
String outStr,inStr;
boolean NotEnd=true;
BufferedReader buf=new BufferedReader(new InputStreamReader(System.in));
//反复读用户数据并计算
while(NotEnd){
outStr=buf.readLine();//读入用户数据
os.writeUTF(outStr);//写入socket中
os.flush();//清空缓存区,立即发送
inStr=is.readUTF();//从socket中读数据
if(!inStr.equals("bye"))
System.out.println("返回结果:"+inStr);
else
NotEnd=false;
}
is.close();
os.close();
s.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
展开阅读全文