计算机网络课程设计报告(p2p聊天)

点对点数据交换(P2P)

目录

Ⅰ 需求分析 ?????????????????????????????5

1.1课程设计目的 ??????????????????????????5

1.2课程设计要求 ??????????????????????????5

1.3选题与操作流程 ?????????????????????????5

1.4开发环境与开发平台 ???????????????????????5 Ⅱ 总体设计 ?????????????????????????????5

2.1总体设计概念 ??????????????????????????5

2.2系统功能 ????????????????????????????5

2.3系统架构 ????????????????????????????6

2.4 模块划分 ????????????????????????????6 Ⅲ 详细设计 ?????????????????????????????6

3.1软件层次模型 ??????????????????????????6

3.2协议结构 ????????????????????????????6

3.3数据流程图 ???????????????????????????8 Ⅳ 系统实现编码及运行结果 ??????????????????????9

4.1服务器端设计与编码 ???????????????????????9

4.2客户端设计与编码 ????????????????????????11

4.3运行结果 ????????????????????????????12 Ⅴ 结论与总结 ????????????????????????????19

5.1课程设计结论 ?????????????????????????? 20

5.2课程设计总结与体会 ???????????????????????20 Ⅵ 课程设计分工及参考文献 ??????????????????????20

6.1课程设计分工 ??????????????????????????20

6.2参考文献 ????????????????????????????21 Ⅶ 附录 ??????????????????????????????22

关键代码??????????????????????????? 22

-1-

点对点数据交换(P2P)

Ⅰ 需求分析

1.1课程设计目的

本次计算机网络课程设计,旨在通过该课程设计,使学生了解、掌握TCP、UDP协议的原理;了解、掌握Socket编程的方法;了解、掌握应用协议设计的思想;利用Winsock API或者Java Socket API编制一个能部署在Internet上的点对点数据交换(P2P)、HTTP/FTP服务器系统、共享白板。

1.2课程设计要求

要求每组学生从上述3个系统中任选一个,独立完成系统的功能设计和实现,使所实现的系统可以能够包含主要的内容要求,并要求学生必须在报告中明确具体分工情况。

1.3选题与操作流程

在网络越来越发达的今天,人们对网络的依赖越来越多,越来越离不开网络,由此而产生的聊天工具越来越多,类似QQ、网络聊天时一类的聊天系统的发展日新月异。因此,基于我们实际的知识结构构成以及网络聊天在当今时代的盛行趋势,本课程设计小组选择了课程设计题目点对点数据交换(P2P),用于实现基于服务器转发的任意多点间的数据共享与交换。其具体设计内容如下:

1)类似P2P的QQ聊天系统,有客户端和服务器端。

2)服务器端记录当前在线客户列表,把客户列表发送给每一个在线客户,并实时刷新。

3)任一个客户可以和任意其它的客户进行交互,即从在线客户列表中选择一个或一组其它客户通过服务器转发彼此进行交互,包括信息交互,文件交互。

Ⅱ总体设计

2.1总体设计概念

为实现网络聊天的功能,采用Java Socket编程,服务器与客户端采用了TCP/IP连接方式,在设计聊天方案时,实行将所有信息发往服务器端,再由服务器进行分别处理的思路,服务器端是所有信息的中心。

服务器端可以查看所有用户的聊天记录,监控所有用户的状态,发出用户上线提示等公告,客户端则提供接收公告的功能。

2.2系统功能

本课程设计按照实验的具体要求,首先应用Socket编程创建客户端和服务器端,它们之间通过一个交互的连接来实现数据通信;然后在客户端 设置一个

-2-

点对点数据交换(P2P)

监听器,用于监听服务器发来的消息;最后在客户端设置点对点的文件交互需要用到的接受和发送类,以及表征文件传输过程的进度条。

2.3系统架构

1)选择传输控制协议TCP,使用Java的Socket编程机制,分别建立客户端与服务器端;

2)分别设计客户端与服务器端的界面,并使用Java应用程序用户界面的开发工具包Swing进行窗体界面的布局,以及实现部分窗口事件的相应。

2.4 模块划分

1)服务器端,主要实现向各个客户端发布系统消息,接受来自客户端的各种信息并分别处理。具体功能如下:

①连接控制:

②管理作用:

③刷新列表:

④登陆信息:

⑤聊天记录:

⑥消息处理:

2)客户端:主要实现向服务器端发布消息,并且对来自服务器的消息做出相应的响应。具体功能如下:

①连接功能:

②登录设置:

③监听作用:

④消息处理:

⑤聊天记录:

⑥消息处理:

⑦传输进度:

⑧文件传输:

Ⅲ 详细设计

3.1软件层次模型

服务器层次结构和客户端层次结构。 在Java socket通信中使用的协议是TCP协议。TCP协议是TCP/IP协议中面3.2协议结构 向连接的运输层协议,它提供全双工的和可靠交付的服务。由于通信是全双工方式,因此TCP连接的任何一方都能够发送和接收数据。发送端的应用进程按照自

-3-

点对点数据交换(P2P)

己产生数据的规律,不断地将数据块(其长短可能各异)陆续写入到TCP的发送缓存中。TCP再从发送缓存中取出一定数量的数据,将其组成TCP报文段逐个传给IP层,然后发送出去。接收端从IP层收到TCP报文段后,先将其存在接收缓存中,然后让接收端的进程从接收缓存中将数据块逐个读取。

TCP协议的报文分为首部和数据两个部分。众所周知首部中含有源端口、目的端口、序号、确认号等内容。因此在利用socket通信时,要想实现不同的功能和数据传输,包括指令和纯数据的传输,可以通过在TCP报文的数据部分进行再分段来实现。由此指定一些套接字,并接利用这些套接字来对不同的数据进行分段,这样就能轻松地将提取所需数据以实现不同功能。

本课程设计正是利用上述方法来建立一个基于TCP协议的协议。这个协议的主要组成部分为套接字及数据段。在程序中主要使用的套接字有:“NICKNAME:”、“SPECIAL:”、“$SPECIAL$”、“FLENGTH:”、“FILEECHO:”以及“FILE:”等。其中各个套接字又有不同的使用方法。

如果数据没有使用套接字,则就被默认为这一数据是广播数据及聊天室的公开数据,这样的数据将会全部传给聊天室里的各个用户并且会保存在聊天室服务器里。

如果使用的是“NICKNAME:”,则其后数据为发此数据者所要变更的昵称。 另外,将“$SPECIAL$”当作划分两个数据的套接字,通常在一个套接字之如果使用的是“SPECIAL:”,则其后数据至“$SPECIAL$”之间的为目的昵称,后有两种数据的情况下使用。 而在“$SPECIAL$”之后的则为所要传输的信息数据,同时我们拟定这个套接字为私聊套接字,虽通过服务器转发但其内容不会在服务器上显示。

若套接字为“FILE:”,则表示该用户发出了一个对一的客户文件传送请求,此时,服务器端同样通过控制字头后的昵称,查找到对应的接收端用户的信息,并将该用户的IP地址传递给发送端,并在服务器端显示发送端想要对接收端传输文件的信息。协议格式如下:“FILE:”+目的昵称+“$FILE$”+文件名称。

若套接字为“FILEFINISH:”,则表示客户之间文件传输完毕,此时服务器端发出文件传输完毕的信息。

“FLENGTH:”的套接字表示文件长度,其格式为“FLENGTH:”+目的昵称+“$SPECIAL$”+文件长度。这样我们在文件传输过程中就可以知道所接收文件的大小同时有益于进度条的使用。

套接字“FILEECHO:”是为服务器发给客户端的目的地的IP地址。

使用套接字的方法能够把控制信息和数据部分一起传送达到服务器和客户端的不同沟通,这样我们就可以利用服务器来完成我们所需的各个服务,无论公

-4-

点对点数据交换(P2P)

开聊天还是私下聊天都可以,同时还能在某种意义上保护隐私。这个socket的协议就是利用TCP协议的数据流、面向连接和全双通来实现的。

3.3数据流程图

根据以上的程序模块划分,设计好服务器端和客户端的流程图,如下: ①服务器端流程:

计算机网络课程设计报告p2p聊天

②客户端流程图:

计算机网络课程设计报告p2p聊天

-5-

点对点数据交换(P2P)

Ⅳ 系统实现编码及运行结果

4.1服务器端设计与编码

4.1.1 服务器程序

该段功能由Server.java文件中的Server类实现(程序代码请见附录),具体实现过程如下:

①启动服务并接收连接:服务器的任务首先是建立一个由IP地址到昵称的映射的哈希表,用于存放用户的基本信息;启动服务器后,等候建立一个连接,然后用这个连接产生的Socket创建一个Client,同时检查该用户是否已存在哈希表中,若以存在,则提示该次连接请求失败,从而实现限制IP的目的;若不存在,则将该IP地址及其昵称或默认昵称的映射添加到哈希表中,接着服务器端向所有的用户发送管理信息,提示有新的用户登录,并且将刷新后的用户列表同时发送到各个客户端。

②关闭服务:服务器端遍历哈希表,关掉每一个用户对应的Socket,之后,关闭服务Server。

③管理作用之发送消息: 服务器端解析由客户端发送的请求,若控制字以“SPECIAL”开头,则通过控制字后的昵称从哈希表中找到对应的用户,实现服务器端到客户端的点对点消息交互;若不是以“SPECIAL”开头,则实现消息的广播发布。

④管理作用之修改昵称:首先判断新的昵称是否在哈希表中存在,若已存在则给出错误提示,否则通过该用户的旧昵称在哈希表中查找到该用户,并用欲修改的昵称替换掉原昵称;接着刷新服务器端的用户列表显示,最后刷新客户端的用户列表显示。

⑤管理作用之断开连接:若要断开某个客户端与服务器的连接,首先向该用户发送被管理员请出系统的信息,然后通过该用户的昵称从哈希表中找到对应socket、service及IP地址等信息,再从每一项信息对应的列表里删除该用户的信息,最后关闭该用户的socket,同时刷新服务器端以及各个客户端的用户列表显示。

⑥在服务其中,需要同时处理多个客户端的请求,因此此处用到了多线程处理机制。在服务器程序里创建单个Server Socket,并调用accept()来等候一个新连接,一旦accept()返回,就取得结果获得的socket,并用它新建一个线程,令其只为那个特定的客户端服务,然后再调用accept(),等候下一次新的连接请求。

4.1.2功能函数

-6-

点对点数据交换(P2P)

该段功能由ChatTookit.java文件中的ChatTookit类实现(程序代码请见附录),具体实现过程如下:

①函数getAllNickname:该函数实现从IP地址——昵称对照哈希表中得到所有的昵称的功能,函数原型如下:

public static String getAllNickname(Hashtable ip2nickname)

②函数sendInfoToAll:该函数实现遍历所有已连接的客户端,并且发送输入的信息的功能,函数原型如下: public static void sendInfoToAll(ArrayList onLineUsers,String info) throws IOException

③函数sendInfo:该函数实现给某个特定用户发送一条信息的功能,函数原型如下:

public static void sendInfo(Socket client,String info) throws IOException

④函数getIP:该函数实现从socket中得到用户的ip地址的功能,函数原型如下:

public static String getIP(Socket socket)

⑤函数getIP:该函数实现从一个hashtable中根据value得到key的功能,函数原型如下: public static String getIP(Hashtable ip2nickname,String

nickname)

⑥函数getNickname:该函数实现从socket和IP地址——昵称对照hashtable中得到用户的昵称的功能,函数原型如下: public static String getNickname(Socket socket,Hashtable

ip2nickname)

⑦函数updateOnLineUsersList:该函数实现将在线用户列表的显示清空,并用最新的用户列表更新客户端的用户列表,只显示昵称,函数原型如下:

public static void updateOnLineUsersList(List

onLineUsersList,String allNickname)

⑧函数updateOnLineUsersList:该函数实现将在线用户列表的显示清空,并用最新的用户列表更新服务器端的用户列表:显示的是ip:昵称,函数原型如下:

public static void updateOnLineUsersList(List

onLineUsersList,Hashtable ip2nickname)

⑨函数getSocketByIP:该函数实现根据用户ip地址得到该用户所在的

-7-

点对点数据交换(P2P)

socket功能,函数原型如下:

public static Socket getSocketByIP(ArrayList onLineUsers,String ip)

⑩函数getServiceByIP,该函数实现根据用户ip地址得到该用户所在的service功能,函数原型如下:

public static Service getServiceByIP(ArrayList allService,String ip)

4.2客户端设计与编码

由于服务器端需要一次处理多个客户端的请求,所以客户端的设计均使用了Java的多线程处理机制实现。

4.2.1客户端程序

该段功能由Client.java文件中的Client类实现(程序代码请见附录),具①新建客户端:初始化该客户端的监听器、套接字、在线用户列表以及发送体实现过程如下: 消息的String流,并且将客户端连接状态设置为真。初始化一个客户端时,需要用到该客户端的IP地址、端口号、在线用户列表以及聊天内容列表等信息。

②建立监听:创建一个BufferedReader以及一个PrintStream之后,再通③发送消息:客户端循环发送消息给服务器,直到发送的String流遇到结过多线程机制建立对服务器发来消息进行监听的监听器Listener。 尾字符。

4.2.2文件发送

该段功能主要实现文件发送,默认端口为9900,由FileSender.java文件中① 初始化:使用接收端用户的昵称、需要被传输的文件名以及聊天记录列② 用需要被传输的文件名新建一个文件输入流,通过该文件输入流新建一的FileSender类实现(程序代码请见附录),具体实现过程如下: 表初始化FileSender相应的属性。 个缓冲输入流,用于缓冲文件输入数据;同时使用接收端用户的IP地址以及端口号新建一个套接字,并通过该套接字建立一个缓冲输出流,用于文件输出数据的缓冲及刷新,然后使用一个字节数组用于读取文件数据。

① 初始化进度条:设置其最小值为零,最大值为文件长度除以字节数组长

度。

④ 发送文件数据:循环从字节数组中读取数据,若没有到String流的结尾,则更新count值为原值与此时字节数组的长度之和,根据此时的count值修改进度条的值,再将字节数组中的数据写入到用于输出的套接字中,并刷新输出

-8-

点对点数据交换(P2P)

流。

⑤ 关闭输入输出流以及建立的套接字,系统使用对话框提示用户文件发送成功,再次将进度条设置为不可见,文件长度设置为零,以便下一次文件传输使用。 }

4.2.3文件接收

该段功能主要实现文件接收,默认端口为9900,由FileReceiver.java文件

① 初始化:使用需要被传输的文件名以及聊天记录列表初始化FileReciever相应的属性。

② 用需要被传输的文件名新建一个文件输出流,通过该文件输入流新建一个缓冲输出流,用于缓冲文件输出数据及刷新;同时使用发送端用户的端口号新建一个服务器套接字,并通过该套接字的accept()方法建立一个缓冲输入流,用于文件输入数据的缓冲,然后使用一个字节数组用于读取文件数据。

③ 接收文件数据:循环从字节数组中读取数据,若没有到String流的结尾,则更新count值为原值与此时字节数组的长度之和,根据此时的count值修改进度条的值,再将字节数组中的数据写入到用于输出的文件中,并刷新输出流。

④ 关闭输入输出流以及建立的套接字,系统使用对话框提示用户接收文件成功,再次将进度条设置为不可见,文件长度设置为零,以便下一次文件传输使用。

4.2.4消息监听

该类主要实现对服务器发送的消息进行监听的功能,由Listener.java文件① 使用缓冲输入流、在线用户列表以及聊天记录列表初始化Listener对象

② 循环接收从服务器发送来的信息,直到输入流读到了String流的末尾,接着开始判断消息的类型。

③ 若控制字以“NICKNAME”开始,则证明需要更新用户列表,此时客户端进行相关操作。

④ 如控制字以“FILEECFO”开始,则客户端需要通过控制字以后的字符获得目的端的IP地址。

⑤ 若控制字以“FLENGHTH”开头,则客户端可以获得需要传输的文件长度。 ⑥ 若控制字都不满足②—⑤的条件,则将该信息作为是交谈内容更新在聊天记录列表中。 中的Listener类实现(程序代码请见附录),具体实现过程如下: 的相关属性,并启动多线程。 中的FileReceiver类实现(程序代码请见附录),具体实现过程如下:

4.3运行结果

-9-

点对点数据交换(P2P)

4.3.1服务器启动服务

计算机网络课程设计报告p2p聊天

4.3.2客户端登陆

计算机网络课程设计报告p2p聊天

-10-

点对点数据交换(P2P)

4.3.3服务器实现其管理功能 ①向所有客户端发送消息

计算机网络课程设计报告p2p聊天

② 向某一个客户端发送消息

计算机网络课程设计报告p2p聊天

③ 制修改某个客户端的昵称

-11-

点对点数据交换(P2P)

4.3.4客户端向所有客户端广播

计算机网络课程设计报告p2p聊天

4.3.5客户端与客户端间实现“私聊”

计算机网络课程设计报告p2p聊天

-12-

计算机网络课程设计报告p2p聊天

点对点数据交换(P2P)

4.3.6客户端与客户端之间实现文件传输功能

①文件发送

②文件接收

-13-

点对点数据交换(P2P)

计算机网络课程设计报告p2p聊天

④ 文件传输过程中

计算机网络课程设计报告p2p聊天

⑤ 传输完成

-14-

点对点数据交换(P2P)

计算机网络课程设计报告p2p聊天

用户离开

计算机网络课程设计报告p2p聊天

-15-

点对点数据交换(P2P)

服务器关闭

计算机网络课程设计报告p2p聊天

Ⅴ结论与总结

5.1课程设计结论

通过4.3运行结果,可以看出,该课程设计利用Java应用程序Socket编程实现了以下功能:使用Java的多线程处理机制建立两个套接字分别作为服务器端和客户端。

在服务器端实现了向各个客户端发布系统消息,接受来自客户端的各种信息并分别处理的功能,可以控制客户端的连接以及对用户的管理作用,并且能够适时检测已登录用户的连接状态且刷新在线用户列表,更能够解析客户端与服务器端的消息交互并做出相应处理。

在客户端实现了向服务器端发布消息,并且对来自服务器的消息做出相应的响应,可以连接到特定的服务器,设置自己的昵称,并且能够监听服务器端发送过来的消息并做出相应的响应,更加能够与其他用户进行广播或私聊,以及实现文件传输等功能。

由此,可见,本次课程设计完成了最初的设计要求,即实现了类似P2P的QQ

-16-

点对点数据交换(P2P)

聊天系统,有相应的客户端和服务器端;服务器端可以记录当前在线客户列表,把客户列表发送给每一个在线客户,并实时刷新;任何一个用户都可以和任意其它的用户进行交互,即从在线客户列表中选择一个或一组其它用户通过服务器的转发彼此进行信息的交互以及文件的交互。

5.2课程设计总结与体会

在本次课程设计中,我们综合应用所学过的知识,在使用Java应用程序的Socket编程机制的基础上,根据传输层的传输控制协议TCP协议的原理,设计点对点数据交换程序,并且实现服务器端和客户端的主要功能。另外,根据自己编写的程序,成功地实现了用户之间的聊天及文件传输功能,这一次的课程设计,使我们掌握了网络传输数据的概念、原理以及设计方法,也加深了对传输控制协议TCP的理解。通过本次课程设计,我们清楚地了解到TCP、UDP协议的原理及二者的区别,学会了网络中数据传输的基本概念、基本原理和设计步骤、设计思路和调试步骤,体会到了程序设计结构在整个设计中起到的重要作用,最终能够清晰地解析计算机网络中数据传输这一概念,为进一步的网络知识学习奠定了基础,并且完成对《计算机网络》这门课程的综合应用,真正体现了“学以致用”的机制。

本次课程设计中,因为有老师的辛勤指导,我们设计小组对理论知识进行了充分研究,发扬了创新实践精神,积极探索,努力勤勉,共同越过了设计路上的每一处障碍,攻克了一个又一个的难关。最终,友好的协作关系与团队凝聚力促使我们顺利地完成了此次课程设计。

本次课程设计,加深了我们对理论知识的理解,也锻炼了我们的实践能力,更多的是在实践中收获了太多的感触和心得,.虽然计算机组成原理的课程设计已经结束,可我们明白“学无止境”的道理,我们会继续刻苦钻研,求实创新,不断地用知识来充实自己,跟上科技时代前进的步伐。

在此次的设计中,非常感谢老师对我们的帮助和指导。才疏学浅,过程难免还不够完善,望老师再次不吝赐教!

Ⅵ 课程设计分工及参考文献

6.1课程设计分工

刘龙: 服务器端程序的设计以及实现,包括服务器端的连接控制、管理

作用、刷新列表以及消息处理等功能的实现: 服务器端界面

的设计和窗口事件的处理,实现服务器端和客户端的的登录信

息、聊天记录功能的实现.

奉光阳: 客户端界面的设计和窗口事件的处理,以及实现客户端的连接功

-17-

点对点数据交换(P2P)

能、登录设计以及,提出消息处理的基本模式,以及客户端消息

处理功能的实现,实现客户端和服务器端的登录信息、聊天记录

功能的实现.

韩超: 点对点信息交互的实现,监听功能,文件传输进度条的开发与实

现,搜集资料,文件传输机制的设计与实现,同时对进度条和文

件保存功能的实现。课程设计报告资料的搜集与整理,补充及修

改课程设计报告

体现了作为一个团队应有的凝聚力,完成了本次课程设计。

6.2参考文献

[1]《计算机网络》第4版 谢希仁 电子工业出版社 20xx年6月

[2]《用TCP/IP进行网络互联》(第一卷) D.E.Comer 电子工业出版社 20xx年11月第四版

[3]《TCP/IP网络原理与技术》 周明天、汪文勇 清华大学出版社:1993

[4《Java程序设计之网络编程》李芝兴、杨瑞龙、朱庆生 清华大学出版社 20xx年3月第一版

-18-

点对点数据交换(P2P)

Ⅶ附录

实现代码(内容已被演说,仅附关键性代码)

7.1.1 服务器程序

//Server.java

在服务器端维护一个hashtable,用来存放各客户端的IP地址与用户昵称之间的对照关系映射。

Hashtable ip2nickname=new Hashtable();

ArrayList allService=new ArrayList();

String clientIP=client.getInetAddress().getHostAddress();

//判断该客户端已经连接的话,提示退出

if(ip2nickname.get(clientIP)!=null)

{

ChatTookit.sendInfo(client,"已经有客户端从该ip地址连接服务器,本次连接将退出!"); break;

}

//把新建连接的用户加入当前用户列表

onLineUsers.add(client);

//默认情况下,即在还没有收到用户自定义昵称的清空下,把用户的IP地址做为其昵称维护入ip2nickname

ip2nickname.put(clientIP,clientIP);

//在服务器端提示有新的客户连接

this.onLineUsersList.add(clientIP);

this.chatContentList.add(clientIP+" 来了");

//对所有已连接的客户端,发送两条信息,1提示有新用户连接,2最新在线用户昵称列表

ChatTookit.sendInfoToAll(onLineUsers,ChatTookit.getAllNickname(ip2nickname));

ChatTookit.sendInfoToAll(onLineUsers,clientIP+" 来了");

//针对每个client连接启动其特定服务线程

Service(client,ip2nickname,onLineUsersList,chatContentList,onLineUsers,chatContent); this.allService.add(service);;

} catch (IOException ex)

{

JOptionPane.showMessageDialog(null,"接收客户端连接时出现问题!", "提示",JOptionPane.INFORMATION_MESSAGE);

}

}

}

//强制更改用户的昵称

public void changeNickname(String oldName,String newName)

{

//如果该昵称已经被别人使用则不进行更改

if(ip2nickname.contains(newName))

-19-

点对点数据交换(P2P)

{

chatContent.add("该昵称已经有人使用,所以不做任何更改");

this.chatContentList.select(this.chatContentList.getItemCount()-1);

return;

}

//找到旧昵称所在的ip地址

String ip=ChatTookit.getIP(ip2nickname,oldName); //用新昵称更新旧昵称 ip2nickname.put(ip,newName); //将负责该用户的Service里的昵称变量进行修改

Service service=ChatTookit.getServiceByIP(allService,ip);

service.changeNickname(newName);

//刷新服务器端的显示

chatContentList.add("系统管理员 将 "+oldName+" 的昵称改为 "+newName); this.chatContentList.select(this.chatContentList.getItemCount()-1);

ChatTookit.updateOnLineUsersList(onLineUsersList,ip2nickname);

//刷新各客户端的显示

//先给该客户端提供提示信息

ChatTookit.sendInfo(client,"你已经被系统管理员踢出聊天室");

client.close();

}

catch (IOException ex)

{

JOptionPane.showMessageDialog(null, "关闭客户端"+ip+"时出错!", "提示",JOptionPane.INFORMATION_MESSAGE);

}

//刷新服务器端的显示

chatContentList.add("系统管理员 将 "+nickname+" 踢出聊天室!");

this.chatContentList.select(this.chatContentList.getItemCount()-1);

ChatTookit.updateOnLineUsersList(onLineUsersList,ip2nickname);

//刷新各客户端的显示

try

{

ChatTookit.sendInfoToAll(onLineUsers,"系统管理员 将 " +nickname+" 踢出聊天室!"); ChatTookit.sendInfoToAll(onLineUsers,ChatTookit.getAllNickname(ip2nickname));

}

catch (IOException ex)

{

JOptionPane.showMessageDialog(null, "发送信息出错!", "提示",JOptionPane.INFORMATION_MESSAGE);

}

}

}

7.1.3功能函数

-20-

点对点数据交换(P2P)

public class ChatTookit {

//从IP地址——昵称对照hashtabel中得到所有的昵称

public static String getAllNickname(Hashtable ip2nickname){

String allNickname="NICKNAME:";

Enumeration e=ip2nickname.elements();

while(e.hasMoreElements()){

allNickname+=(String)e.nextElement()+" ";

}

return allNickname;

}

//遍历所有已连接的客户端,发送输入的信息

public static void sendInfoToAll(ArrayList onLineUsers,String info) throws

IOException {

for (int i = 0; i < onLineUsers.size(); i++) {

Socket c = (Socket) onLineUsers.get(i);

PrintStream ps = new PrintStream(c.getOutputStream());

ps.println(info);

}

}

//给某个特定用户发送一条信息

public static void sendInfo(Socket client,String info) throws IOException {

PrintStream ps = new PrintStream(client.getOutputStream());

ps.println(info);

}

//从socket中得到用户的ip地址

public static String getIP(Socket socket){

return socket.getInetAddress().getHostAddress();

}

//从socket和IP地址——昵称对照hashtable中得到用户的昵称

public static String getNickname(Socket socket,Hashtable ip2nickname){

return (String)ip2nickname.get(getIP(socket));

}

//将在线用户列表的显示清空,并用最新的用户列表更新

//客户端的:第二个参数是字符串,只显示昵称

public static void updateOnLineUsersList(List onLineUsersList,String allNickname){ onLineUsersList.removeAll();

StringTokenizer st = new StringTokenizer(allNickname);

while (st.hasMoreTokens()) {

onLineUsersList.add(st.nextToken());

}

}

//服务器端的:第二个参数是hashtable,显示的是ip:昵称

public static void updateOnLineUsersList(List onLineUsersList,Hashtable ip2nickname){ onLineUsersList.removeAll();

-21-

点对点数据交换(P2P)

Enumeration e=ip2nickname.keys();

while(e.hasMoreElements()){

String ip=(String)e.nextElement();

onLineUsersList.add((String)ip2nickname.get(ip)+"@"+ip);

}

}

//根据用户ip地址得到该用户所在的socket

public static Socket getSocketByIP(ArrayList onLineUsers,String ip){

Socket client=null;

for(int i=0;i<onLineUsers.size();i++){

client=(Socket)onLineUsers.get(i);

if(getIP(client).equals(ip)){

break;

}

}

return client;

}

//根据用户ip地址得到该用户所在的service

Service service=null;

for(int i=0;i<allService.size();i++){

service=(Service)allService.get(i);

if(service.getIP().equals(ip)){

break;

7.1.4信息处理

//Service.java

//接收信息,将用户发过来的信息显示在服务器端面板上,并遍历发送给所有的客户端。 while((word=br.readLine())!=null)

{

//判断是否是修改昵称

if(word.startsWith("NICKNAME:"))

{

word=word.substring(9);

//如果用户的昵称已经被别的用户使用,提示用户重新起昵称

if(ip2nickname.contains(word))

{

ChatTookit.sendInfo(client,"系统管理员:该昵称已存在,请重新起新的昵称!"); continue;

}

this.ip2nickname.put(clientIP,word);

//更新服务器端的显示

this.chatContentList.add(this.nickname+" 将昵称改为 "+word);

ChatTookit.updateOnLineUsersList(onLineUsersList,ip2nickname);

//将更新信息发送给所有客户端

ChatTookit.sendInfoToAll(onLineUsers,this.nickname+" 将昵称改为 "+word);

-22-

点对点数据交换(P2P)

ChatTookit.sendInfoToAll(onLineUsers,ChatTookit.getAllNickname(this.ip2nickname)); this.nickname=word;

}

else if(word.startsWith("FLENGTH:"))

{

word=word.substring(8);

String toNickname=word.substring(0,word.indexOf("$SPECIAL$")); word=word.substring(word.indexOf("$SPECIAL$")+9);

Socket

client=ChatTookit.getSocketByIP(onLineUsers,ChatTookit.getIP(ip2nickname,toNickname)); ChatTookit.sendInfo(client,"FLENGTH:"+word);

}

//是否是只发送给特定用户

else if(word.startsWith("SPECIAL:"))

{

word=word.substring(8);

String toNickname=word.substring(0,word.indexOf("$SPECIAL$")); word=word.substring(word.indexOf("$SPECIAL$")+9);

//根据昵称找到该客户端

Socket

client=ChatTookit.getSocketByIP(onLineUsers,ChatTookit.getIP(ip2nickname,toNickname)); ChatTookit.sendInfo(client,this.nickname+" 悄悄对你说:"+word);

this.chatContent.add(this.nickname+" 对 "+toNickname+" 悄悄的说:"+word); }

//是否是想对某个用户发送文件

else if(word.startsWith("FILE:"))

{

word=word.substring(5);

String toNickname=word.substring(0,word.indexOf("$FILE$"));

word=word.substring(word.indexOf("$FILE$")+6);

//根据昵称找到该客户端

clientD=ChatTookit.getSocketByIP(onLineUsers,ChatTookit.getIP(ip2nickname,toNickname)); ChatTookit.sendInfo(clientD,this.nickname+" 想发送文件 "+word+" 给你,请点“保存”按钮选择要保存的位置,再点“开始”按钮开始接收文件"); //将目标用户的ip地址发送给原用户

ChatTookit.sendInfo(this.client,"FILEECHO:"+clientD.getInetAddress().getHostAddress()); this.chatContent.add(this.nickname+" 想给 "+toNickname+" 发送文件 "+word); }

//文件传输完毕

else if(word.startsWith("FILEFINISH:"))

{

String toNickname=word.substring(11);

Socket

clientD=ChatTookit.getSocketByIP(onLineUsers,ChatTookit.getIP(ip2nickname,toNickname));

-23-

点对点数据交换(P2P)

ChatTookit.sendInfo(clientD,"FILEFINISH:"+this.nickname);

this.chatContent.add(this.nickname+" 给 "+toNickname+" 的文件传送完毕"); System.out.println("客户端文件传输完毕。");

}

else

{

this.chatContentList.add(this.nickname+"("+this.clientIP+"):" + word); ChatTookit.sendInfoToAll(onLineUsers,word);

}

}

} catch (Exception ex)

{

JOptionPane.showMessageDialog(null, clientIP+"断开连接", "提示",JOptionPane.INFORMATION_MESSAGE);

}

finally

{

//更新在线用户Arraylist和ip2nickname

this.onLineUsers.remove(client);

this.ip2nickname.remove(ChatTookit.getIP(client));

//更新服务器端的显示

chatContentList.add(this.nickname+" 走了");

ChatTookit.updateOnLineUsersList(onLineUsersList,ip2nickname);

try

{

//将更新信息发送给所有客户端

ChatTookit.sendInfoToAll(onLineUsers, this.nickname + " 走了"); ChatTookit.sendInfoToAll(onLineUsers,ChatTookit.getAllNickname(this.ip2nickname)); client.close();

}

catch (IOException ex2)

{

}

}

}

7.2客户端编码

7.2.1客户端程序

//Client.java

public void run()

{

BufferedReader br=null;

PrintStream ps=null;

try

{

-24-

点对点数据交换(P2P)

br = new BufferedReader(new InputStreamReader(client.getInputStream())); ps=new PrintStream(client.getOutputStream(),true);

//建立对服务器发送来的信息进行监听的线程

listener=new Listener(br,this.onLineUsersList,this.chatContentList);

//循环发送消息给服务器

while(!isClosed)

{

if(this.word.length()>0)

{

ps.println(this.word);

this.word="";

}

else

{

sleep(100);

}

}

//关闭回收操作

listener.destroy();

client.close();

}

catch (Exception ex)

{

JOptionPane.showMessageDialog(null,"对不起,有错误,请重启程序。","错误",JOptionPane.INFORMATION_MESSAGE);

}

}

7.2.2界面设计

//信息清空

public void jButton8_actionPerformed(ActionEvent e)

{

if(JOptionPane.showConfirmDialog(this,"确定要清空所有信息吗?","清空信息",JOptionPane.YES_NO_OPTION)==0)

{

this.list2.removeAll();

}

}

//修改昵称

public void jButton3_actionPerformed(ActionEvent e)

{

jnickname.setText(JOptionPane.showInputDialog("请输入昵称:")); if(jnickname.getText().length()>0)

{

this.client.send("NICKNAME:"+jnickname.getText());

-25-

点对点数据交换(P2P)

}

}

//保存消息

public void jButton6_actionPerformed(ActionEvent e)

{

if(fileChooser.showSaveDialog(this)==JFileChooser.APPROVE_OPTION) {

File file = fileChooser.getSelectedFile();

try

{

PrintStream ps = new PrintStream(new FileOutputStream(file));

for (int i = 0; i < list2.getItemCount(); i++)

{

ps.println(list2.getItem(i));

}

ps.close();

}

catch (FileNotFoundException ex)

{

JOptionPane.showMessageDialog(this, "保存失败!", "提示",JOptionPane.ERROR_MESSAGE);

}

JOptionPane.showMessageDialog(this,

",JOptionPane.INFORMATION_MESSAGE);

}

}

//文件传输开始

public void jButton10_actionPerformed(ActionEvent e)

{

if(fileTransFlag==null)

{

JOptionPane.showMessageDialog(this, "请选择是接受或者是发送文件!", "提示"保存成功!", "提示",JOptionPane.ERROR_MESSAGE);

}

else if(fileTransFlag.equalsIgnoreCase("sender"))

{

bar.setVisible(true);

fileSender.start();

this.client.send("FLENGTH:"+list1.getSelectedItem()+"$SPECIAL$"+filelength); }

else if(fileTransFlag.equalsIgnoreCase("receiver"))

{

bar.setVisible(true);

fileReceiver.start();

-26-

点对点数据交换(P2P)

this.client.send("SPECIAL:"+list1.getSelectedItem()+"$SPECIAL$我已经准备好了,你开始发送文件吧");

}

fileTransFlag=null;

}

//发送文件

public void jButton9_actionPerformed(ActionEvent e)

{

if(list1.getSelectedItem()==null)

{

JOptionPane.showMessageDialog(this,"请选择要发送文件的用户,不能对所有人同时发送文件!","提示",JOptionPane.ERROR_MESSAGE);

}

else

{

bar.setVisible(true);//设置进度条可见

if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {

filelength=fileChooser.getSelectedFile().length();

filedir=fileChooser.getSelectedFile().getAbsolutePath();

fileSender=new FileSender(list1.getSelectedItem(),filedir,list2);

this.client.getListener().setFileSender(fileSender);

this.client.send("FILE:"+list1.getSelectedItem()+"$FILE$"+fileChooser.getSelectedFile().getName()+"("+fileChooser.getSelectedFile().length()

+"字节)");

fileTransFlag="SENDER";

list2.add("你想给 "+list1.getSelectedItem()+" 发送文件 "+filedir+" ,正在等待对方确认..");

}

}

}

//保存文件

public void jButton11_actionPerformed(ActionEvent e)

{

if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {

jLabel5.setText(fileChooser.getSelectedFile().getName()); filedir=fileChooser.getSelectedFile().getAbsolutePath();

fileReceiver=new FileReceiver(filedir,list2);

this.client.getListener().setFileReceiver(fileReceiver);

fileTransFlag="RECEIVER";

list2.add("你准备将文件保存到 "+filedir+" ,按“开始”按钮接收文件.."); }

-27-

点对点数据交换(P2P)

}

}

7.2.3文件发送

//FileSender.java

public void run()

{

try

{

Socket s=new Socket(InetAddress.getByName(this.descIP),this.port); File file =new File(fileName); FileInputStream fis=new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos=new BufferedOutputStream(s.getOutputStream()); byte[] buff = new byte[1024]; int i = 0; ClientFrame.jLabel5.setText(file.getName());

ClientFrame.bar.setMinimum (0);

ClientFrame.bar.setMaximum((int)(file.length()/1024));

while ((i = bis.read(buff, 0, buff.length)) != -1)

{

} count+=buff.length; ClientFrame.bar.setValue ((int)(count)/1024); bos.write(buff,0,i); bos.flush();

bis.close();

s.close();

JOptionPane.showMessageDialog(null, "发送文件成功!", "提示",JOptionPane.INFORMATION_MESSAGE);

ClientFrame.bar.setVisible(false);

ClientFrame.jLabel5.setText("");

ClientFrame.filelength=0;

}

catch (Exception ex)

{

ex.printStackTrace();

}

}

7.2.4文件接收

//FileReciever.java

public void run()

{

BufferedOutputStream bos=null;

BufferedInputStream bis=null;

-28-

点对点数据交换(P2P)

try

{

ss=new ServerSocket(port);

Socket s=ss.accept();

bos=new BufferedOutputStream(new FileOutputStream(fileName)); bis=new BufferedInputStream(s.getInputStream());

byte[] buf=new byte[1024];

int i=0;

while((i=bis.read(buf,0,1024))!=-1)

{

count+=1024;

ClientFrame.bar.setValue ((int)(count)/1024);

bos.write(buf,0,i);

bos.flush();

}

bis.close();

s.close();

bos.close();

ss.close();

JOptionPane.showMessageDialog(null, "接受文件成功!",JOptionPane.INFORMATION_MESSAGE);

ClientFrame.bar.setVisible(false);

ClientFrame.jLabel5.setText("");

ClientFrame.filelength=0;

}

catch (Exception ex)

{

ex.printStackTrace();

}

}

public void setOver(boolean over)

{

this.isOver=over;

}

}

7.2.5消息监听

//Listener.java

public void run()

{

String word=null;

try {

//循环接收服务器端发送来的信息

while (!isClosed && (word = br.readLine()) != null)

{

-29- ", "提示

点对点数据交换(P2P)

//判断其是否是更新在线用户列表的信息

//已修改。由NICKNAME开头,不再有以ONLINEUSERS开头的消息 if(word.startsWith("NICKNAME:"))

{

word=word.substring(9);

onlineUsersList.removeAll();

StringTokenizer st = new StringTokenizer(word);

while (st.hasMoreTokens())

{

onlineUsersList.add(st.nextToken());

}

}

//得到服务器端相应过来的目标用户的ip地址

else if(word.startsWith("FILEECHO:"))

{

word=word.substring(9);

this.fileSender.setDescIP(word);

}

else if(word.startsWith("FLENGTH:"))

{

} String filelength=word.substring(8); ClientFrame.filelength=Long.parseLong(filelength); ClientFrame.bar.setMinimum (0); ClientFrame.bar.setMaximum((int)(ClientFrame.filelength/1024));

else

{

//否则就将其打到交谈信息里

this.chatContentList.add(word);

}

}

}

catch (IOException ex)

{

isClosed=true;

}

}

-30-

相关推荐