具体代码已上传csdn下载http://download.csdn.net/detail/u014277388/9569056
计算机网络
实验报告
实验名称: |
TCP/IP协议的应用 |
|||
姓名: |
中年英雄王叔叔 |
学号: |
|
|
专业: |
信息管理与信息系统 |
班级: |
|
|
指导教师: |
陈君 |
|||
|
||||
|
一、实验目的
了解和掌握基于socket的网络编程技术,开发基于socket的网络应用。
二、实验内容与实验步骤
【实验内容】
了解应用编程接口API,掌握基于socket的网络编程的原理,开发利用socket的TCP文件传输应用程序。该应用需要具备的功能以及实现的要点描述如下:
1.该程序应该包括服务器应用程序以及客户应用程序。
2.用户需要身份验证。即对于客户端来说,无论是上传文件,还是下载文件,首先需要做的事情是登陆服务器,得到服务器的验证。 若验证成功,即可与服务器之间开始传输文件。若验证失败,服务器则返回错误信息。错误信息包括,用户名错误,密码错误等。
3.理解文件传输的原理。所谓文件传输,对于发送端来说,实质是将数据读入发送缓存再将其发送。对于接收端来说,实质是从接收缓存里读取数据并将其写入到指定的位置。
4.请注意理解文件传输的含义。文件,指的是一切可以传输的信息,包括文本文件、图片文件、视频文件等。传输,指的是服务器和客户端都可以成为文件的发送者。从客户端的角度来说,客户端向服务器传送文件称为上传,服务器向客户端传送文件称为下载。
【实验步骤】
1. 阅读老师提供的示例程序,了解和掌握socket网络编程的原理
2. 根据实验要求,开发基于TCP的文件传输服务器端程序
3. 开发基于TCP的文件传输客户端程序
4. 编译程序、调试程序、运行程序
5. 写实验报告,>800字,提交设计程序
三、实验环境
硬件环境:
处理器:Intel(R) Core(TM) i5-3230M CPU @ 2.60GHz
安装内存:(RAM):2GB
软件环境:
操作系统:Windows7
开发工具:Eclipse(JRE1.7版本)
四、实验过程与分析
1.知识准备
通过阅读老师提供的实例程序以及实验要求,我大概对要做的程序心里有个预期,因为对Java的理解相较其他比较深,所以决定要选择用Java进行编程。然后Java开发工具中在Java.net包中是含有Socket和ServerSocket分别表示双向连接的客户端和服务器端。然后参照一个Socket简单通信实例(http://blog.csdn.net/gxy3509394/article/details/7899923)了解了大概怎么进行使用这两个类,(该程序并不含文件传输功能)并试着加入GUI界面,运行结果如下图:
接着在网上查找资料(http://www.cnblogs.com/mengdd/archive/2013/03/10/2952616.html、http://www.cnblogs.com/rond/p/3565113.html、基于Java的通信软件设计)等文献或者博客理解这两个类。
该图显示的为在Java封装socket情况下的通信,没有体现bind()方法,当然利用Java也可以进行显式的使用,比如可以像下面这样,当然依然设为127.0.0.1,因为服务器为本地服务器,此外对于该ip地址系统不会将数据放入数据链路层,而是直接在客户端和服务器直接传递。
ss =new ServerSocket();
Stringip="127.0.0.1";
intport=7888;
InetSocketAddressaddr=new InetSocketAddress(ip,port);
ss.bind(addr);
ss.accept();
此外,关于GUI图形化界面方面的知识,也是看些网上的教程慢慢上手的。
2.程序概述
在本次实验过程中,我在Java项目中共新建了四个包,分别是:Chat包(聊天功能,主要用来学习Java中的Socket类)、Server包(包含服务器的程序)、Client包(包含客户端各种功能的程序以及GUI程序)、FileTransfer包(实现客户端文件传输GUI界面以及功能)。由于Chat包与本次实验关联不大,故不再进行介绍。
1.服务器端 (Server包)
类名(class) |
方法(method) |
作用 |
Server |
Server() |
构造函数,建立套接字 |
log(BufferedReader br, PrintWriter pw) |
登录函数,验证客户端传来的帐号密码是否正确 |
|
register(BufferedReader br, PrintWriter pw) |
注册函数,将客户端传来的帐号密码写入"用户资料"文件 |
|
changepassword(BufferedReader br, PrintWriter pw) |
修改密码函数,允许用户修改密码,并将其保存至用户资料 |
|
upload(BufferedReader br, PrintWriter pw) |
允许用户上传各种文件类型到文件数据库中 |
|
showdatabase(BufferedReader br, PrintWriter pw) |
让用户可以选择文件数据库中的文件进行下载 |
|
download(BufferedReader br, PrintWriter pw) |
将用户在文件数据库选择的文件传到客户端 |
|
main() |
主函数,建立一个服务器对象 |
2.客户端(Client包和FileTransfer包)
Client包
类名(class) |
方法(method) |
作用 |
Client |
createConnection(int port) |
向服务器发出请求,建立套接字连接 |
Client(int port) |
构造函数调用createConnection方法 |
|
main() |
主函数,建立一个客户端对象 |
|
MainLog |
MainLog(Client client) |
实例化MainLog界面以及Client对象 |
actionPerformed(ActionEvent e) |
监听鼠标的动作,并调用相关方法 |
|
main() |
主函数,建立一个客户端对象 |
|
Log |
Log(Client client) |
构造函数,实例化Log界面以及Client对象 |
actionPerformed(ActionEvent e) |
监听鼠标的动作,并向服务器传递登录信号,完成登录功能 |
|
Register |
Register (Client client) |
构造函数,实例化Register界面以及Client对象 |
actionPerformed(ActionEvent e) |
监听鼠标的动作并向服务器传递注册信号,将账户信息发送给服务器 |
|
ChangePassword |
ChangePassword (Client client) |
构造函数,实例化ChangePassword界面以及Client对象 |
actionPerformed(ActionEvent e) |
监听鼠标的动作并向服务器传递修改密码信号,将新的账户信息发送给服务器 |
FileTransfer包
类名(class) |
方法(method) |
作用 |
FileTransfer |
FileTransfer(BufferedReader br, PrintWriter pw) |
构造函数,实例化FileTransfer界面以及Client对象 |
actionPerformed(ActionEvent e) |
监听鼠标的动作,并调用相关方法 |
|
Upload |
Upload(BufferedReader br, PrintWriter pw) |
构造函数实例化Upload界面 |
UploadListener |
UploadListener(JLabel filenamelabel, JProgressBar progressbar,BufferedReader br, PrintWriter pw) |
构造函数,实例化上传相关参数 |
actionPerformed(ActionEvent e) |
监听鼠标的动作,并实现客户端请求进行文件上传功能 |
|
Download |
Download(BufferedReader br, PrintWriter pw) |
构造函数,实例化Download界面 |
DownloadListener |
DownloadListener (JLabel filenamelabel, JProgressBar progressbar,BufferedReader br, PrintWriter pw) |
构造函数,实例化下载相关参数 |
actionPerformed(ActionEvent e) |
监听鼠标的动作,并实现客户端请求进行文件下载功能 |
3.程序运行情况
1.客户端与服务器建立连接
Server类的构建:创建ServerSocket对象服务器端口号(本次实验登记端口号为:8888,文件传输端口号为:6666);创建Socket利用accept()方法来监听接收客户发来的连接请求;创建进行信息传输的IO流;
Client类的构建:利用主机IP以及端口号创建客户端的Socket,并发出连接请求;
Server与Client建立连接:先启动服务器,然后打开客户端,可以在服务器端看到客户的运行情况
2.客户端运行展示
客户端首页:
包含登陆、注册、修改密码三个按钮,同时为三个按钮添加监听器,触发之后分别弹出相应界面。
登录界面:
在账号输入框中输入账号,在密码输入框中输入密码,点击登陆,即将信息传输至服务器端进行验证。根据服务器中用户资料文件中的信息进行比较,若密码和账号均一致,即登录成功,可能出现密码错误或账号不存在的情况。
注册界面:
在账号输入框中输入账号,在密码输入框中输入密码,在确认密码框中再次输入密码,点击确认,在客户端首先会进行密码与确认密码的匹配,若两者一致,且用户资料中无该用户,则传入服务器进行写入用户资料中,可能出现提示两次密码不一致或者该用户名已注册等情况。
修改密码界面:
在账号输入框中输入账号,在原密码输入框中输入旧密码,在新密码框中输入想要设定的新密码,在确认密码框里再次输入新密码,点击确认,在客户端首先会进行新密码与确认密码的匹配,若两者一致,且用户资料中已有该用户,则传入服务器改写用户资料,可能出现提示两次密码不一致、该用户名未注册等情况以及原始密码不对的情况。
文件上传与下载界面:
包含上传与下载两个按钮,同时为每个按钮添加监听器,触发之后分别弹出相应上传或下载的界面。
文件上传界面:
选择对应的文件上传按钮,弹出文件选择功能;选中所要上传的文件,文件即被上传至服务器的文件数据库中,进度条达100%,即上传成功,并弹出"文件上传成功"的提示。
文件下载界面:
文件下载界面展示了服务器端所有可供下载的文件;选择对应的文件点击后方的下载按钮,弹出文件下载位置选择框;选中所要下载文件的位置,当进度条到达100%时,文件即被下载至客户端下载完成,同时弹出"文件下载成功"的提示。
3.服务器运行展示
对于刚才用户的所有操作,服务器都会对相应操作进行监控显示
用户帐号信息被储存在一个txt文件中,上传文件的目的地址和下载文件的数据库均在文件夹"文件数据库中"。
五、实验结果总结与思考
本次实验刚刚接触时没有头绪,但是实验时间确实很充足,所以很多问题都慢慢理解明白了。总体感觉实验还是有一定难度,但是由于操作系统原理那门课也有一些相关方面的知识,所以对于这次的文件运输程序,自己在编的过程越来越懂、也越来越有成就感。
1.对于端口号的理解。开始并不是很懂TCP连接,于是对于出现Socket类和ServerSocket类全部使用toString()方法,观察并总结规律,再结合书上的讲解,我理解了对于服务器端,其用ServerSocket类绑定的端口即为本地端口,而远程端口打印的每次都不一样是因为客户端使用的端口号,是客户进程暂时使用的端口号,此次通信结束即释放该端口;而对于客户端,Socket(ip,port)即产生一个与之对应的本地端口,而其中的port即成为其远程端口。
2.服务器对于不同的进程最好使用不同的端口号,本次实验即采用了两个端口号,一个是登记端口号8888,文件传输端口号为6666,这样实现了可以在进行文件内容信息传输的同时也能传输登记控制信息的传输,避免了某次文件传输过后是登记控制信息也无法传递的情况。
3.特殊IP地址的使用。服务器端口ss绑定的IP地址为0.0.0.0,代表本主机的所有IP地址。而客户端准备绑定的地址为127.0.0.1或Localhost,两者其实本质上都是代表本机地址或者说本地服务器,他们的关系是通过操作系统中的hosts文件,将Localhost解析为127.0.0.1。他们的关系是通过操作系统中的hosts文件,将Localhost解析为127.0.0.1。Localhost好处在于不占用网卡资源。
4.本次实验是使用GUI界面最为复杂的一次,还包括了监听器的使用都是第一次,对Java中窗口的理解、三层面板的理解以及窗体布局都有了更加深刻的理解。
5.一开始对于整个实验也有一部分是基于FTP文件传输协议,比如客户端应该实现用户界面,而在服务器端只需要能够监控用户使用信息即可;又如FTP专门区别数据传送进程和控制进程,在本次实验一样受用.
6.对于要实现的不同功能,客户端都是专门的一个类去实现,使程序可读性变强。并且在服务器端采用while(true)无限循环的结构,使服务器监听打开后一直持续监听,采用if-else结构对于客户端传来的请求调用相应的方法实现。
7.关于上传和下载界面的进度条,我在网上先找到一个类似的Java的Socket的传输文件的程序,模仿其编了个类似的进度条,通过上网查找资料,我懂得了因为所编写的程序本身为单线程运行,而GUI界面发生改变需要另外的单线程,所以在代码中可以看到是新建了一个线程实现文件的上传与下载。
8.本次实验的核心是关于Socket通信的原理,通过学习我们已经知道TCP协议是面向字节流的,而Java是利用getOutputStream()和getInputStream()得以实现的;而且关于IP地址的设置,若设置为远程IP,这能实现远程的进程通信,这也是很多大型软件的基础;总之我对运输层以及Socket等方面的知识都有了更深的理解。
核心代码:
服务器端
package Server;
//Java中 socket通信已经被封装好了主要使用两个类ServerSocket和Socket
import java.net.ServerSocket;
import java.net.Socket;
import java.io.*;
import javax.swing.JOptionPane;
/*
*服务器具体
*/
publicclass Server{
//设定服务器端口号为8888
int port=8888;
public Server()throws Exception {
JOptionPane.showMessageDialog(null,"服务器开始启动","服务器端",
JOptionPane.INFORMATION_MESSAGE);
//套接字socket
Socket s=null;
//服务器段serversocket
ServerSocket ss=new ServerSocket(port);
System.out.println("监听端口:"+ ss.toString());
// socket监听,即为等待请求,此方法会一直阻塞,直到获得请求才往下走,返回类型为Socket类
s= ss.accept();
System.out.println("登记端口:"+ s.toString());
//用于接收客户端发来的请求
BufferedReader br=new BufferedReader(new InputStreamReader(
new DataInputStream(s.getInputStream())));
//用于发送返回信息
PrintWriter pw=new PrintWriter(new DataOutputStream(
s.getOutputStream()));
//服务器持续工作
while(true){
//接受客户端,看是哪种请求,并调用相应的函数
char clientchar=(char) br.read();
try{
if(clientchar=='L'){
System.out.println("用户正在进行登录");
log(br, pw);
}elseif(clientchar=='R'){
System.out.println("用户正在进行注册");
register(br, pw);
}elseif(clientchar=='C'){
System.out.println("用户正在修改密码");
changepassword(br, pw);
}elseif(clientchar=='U'){
System.out.println("用户正在进行上传操作");
upload(br, pw);
}elseif(clientchar=='D'){
System.out.println("用户正在进行下载操作");
download(br, pw);
}elseif(clientchar=='S'){
System.out.println("用户正在浏览数据库");
showdatabase(br, pw);
}
}catch(Exception e){
//打印异常
e.printStackTrace();
if(s!=null){
try{
ss.close();
ss=null;
}catch(Exception ex){
ex.printStackTrace();
}
System.out.println("远程连接异常,关闭连接");
}
}
}
}
/*
*登陆
*/
publicvoid log(BufferedReader br, PrintWriter pw)throws Exception{
//客户端账号
String account= br.readLine();
//密码文件相对路径
String userdatapath="用户资料.txt";
//从密码文件读取内容的IO流
BufferedReader userdata=new BufferedReader(new FileReader(
userdatapath));
//从文件中读取的每行内容
String strline;
//向客户端发送的账号是否存在结果,预置为no
String accountresult="no";
//循环读取文件内容,直至为空
while((strline= userdata.readLine())!=null){
//将分拣内容以逗号切分,第一个字符串为账号,第二个字符串为密码
String[] str= strline.split(",");
//已有账号和传来的账号匹配
if(str[0].equals(account)){
accountresult="yes";
break;
}
}
//回传账号是否存在的结果
pw.println(accountresult);
pw.flush();
//关闭读取密码文件的流
userdata.close();
//重新开启读取密码文件的流
userdata=new BufferedReader(new FileReader(userdatapath));
//如果账户存在
if(accountresult.equals("yes")){
//客户端密码
String password= br.readLine();
//密码与账号是否匹配的结果,预置为1
String passwordresult="no";
while((strline= userdata.readLine())!=null){
//将分拣内容以逗号切分,第一个字符串为账号,第二个字符串为密码
String[] str= strline.split(",");
//已有账号和传来的账号匹配
if(str[0].equals(account)){
//同时对应的密码也匹配
if(str[1].equals(password)){
//检验结果置为yes,代表密码和账号匹配
passwordresult="yes";
break;
}
}
}
//关闭读取密码文件的IO流
userdata.close();
//将检验结果发给客户端
pw.println(passwordresult);
pw.flush();
}
}
/*
*注册
*/
publicvoid register(BufferedReader br, PrintWriter pw)throws Exception{
//客户端账号
String account= br.readLine();
//密码文件相对路径
String userdatapath="用户资料.txt";
//读取密码文件的IO流
BufferedReader userdata=new BufferedReader(new FileReader(
userdatapath));
//密码文件的每行内容
String strline;
//账号是否存在的检验结果,预置为yes
String exist="yes";
// exist是否改动过的标志位,预置为0
int flag=0;
//循环检验账号是否已存在,逼迫客户端不断输入新的账号,直至传来的账号不存在
while(exist.equals("yes")){
//循环读取文件内容,直至为空
while((strline= userdata.readLine())!=null){
//将每行内容按逗号切分,第一字符串为账号,第二字符串为密码
String[] str= strline.split(",");
//已有账号和传来的账号匹配
if(str[0].equals(account)){
//标志位置为1,跳出
flag=1;
break;
}
}
//标志位为0,证明账户不存在
if(flag==0){
exist="no";
}
//向客户端发送检验结果
pw.println(exist);
pw.flush();
//如果账号存在,继续从客户端读取新的账号
if(exist.equals("yes")){
account= br.readLine();
}
}
//关闭从文件读取的流