计算机网络课程设计

更新时间:2024-07-10 18:25:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

计算机网络课程设计

实验报告

实验一 数据包的捕获与分析

1.1 实验目的

数据包捕获技术是网络管理系统的关键技术。本实验通过Wireshark软件的安装使用,监控局域网的状态,捕获在局域网中传输的数据包,并结合在计算机网络课程中学习到的理论知识,对常用网络协议的数据包做出分析,加深网络课程知识的理解和掌握。

1.2 实验内容

Wireshark是一种开源的网络数据包的捕获和分析软件,本实验通过Wireshark软件的安装使用,监控局域网的状态,捕获在局域网中传输的数据包,并结合在计算机网络课程中学习到的理论知识,对常用网络协议的数据包做出分析,加深网络课程知识的理解和掌握。具体内容及要求如下:

? Wireshark软件的安装; ? Wireshark软件的启动,并设置网卡的状态为混杂状态,使得Wireshark可以监控局

域网的状态;

? 启动数据包的捕获,跟踪PC之间的报文,并存入文件以备重新查; ? 设置过滤器过滤网络报文以检测特定数据流; ? 对常用协议的数据包的报文格式进行分析,利用协议分析软件的统计工具显示网络

报文的各种统计信息。

1. 3实验环境

Wirshark1.1.1

1.4 实验抓包情况

Ethernet Ⅱ数据链路层:设置过滤方式是捕获后过滤,过滤条件为udp

1

Destination: IPv4mcast_00:00:fc (01:00:5e:00:00:fc) 目的:厂名_序号(网卡地址) Source: 4c:80:93:25:d0:1e(4c:80:93:25:d0:1e) 源:厂名_序号(网卡地址) Type: IP (0x0800) 帧内封装的上层协议类型为IP(十六进制码0800)

以下为网络层IP包头信息

2

Version: 4 互联网协议IPv4

Header length: 20 bytes IP包头部长度

Differentiated Services Field:0x00(DSCP 0x00:Default;ECN:0x00) 差分服务字段 Total Length: 52 IP包的总长度 Identification:0x6992 (27062) 标志字段

Flags:0x00 标记字段(在路由传输时,是否允许将此IP包分段) Fragment offset: 0 分段偏移量(将一个IP包分段后传输时,本段的标识) Time to live: 1 生存期TTL

Protocol: TCP (0x11) 此包内封装的上层协议为TCP Header checksum: 0x64ba [correct] 头部数据的校验和 Source: 10.27.0.86(10.27.0.86) 源IP地址

Destination: 224.0.0.252(224.0.0.252) 目的IP地址

以下为传输层UDP包头

3

Source port : 65167(65167) 源端口名称 Destination port: llmnr(5355) 目的端口名称 Length: 32 头部长度

Checksum:0xd065[validation disabled] UDP数据段的校验和

以下是网络层的icmp包头

4

Type: 8 (Echo (ping) request) 类型为8(ping),响应请求信号 Code:0() 代码 Checksum:0x4d3e[correct] 校验和

Identifier:0x0001 标识code = 0, identifier用来匹配echo和echo reply消息 Sequence number: 29(0x001d) 顺序号

以下为传输层TCP数据段头部信息

5

Source port: http (80) 源端口名称(端口号) Destination port: 52449 (52449) 目的端口名

Sequence number: 1 (relative sequence number) 序列号(相对序列号) Header length: 20 bytes 头部长度

Flags: 0x14(RST,ACK) TCP标记字段(本字段是RST,ACK,置位表示复位TCP连接,置位表

示确认号字段有效)

Window size: 0 流量控制的窗口大小

Checksum: 0xd47e[validation disabled] TCP数据段的校验和

以下为数据链路层IEEE802.3报文格式

注意:IEEE802.3是有线以太网的协议,所以要想得到IEEE802.3的报文就必须要叉网线。 DSTADDR 6字节 SRCADDR 6字节 LEN DSAP SSAP 2字1字1字节 节 节 最大长度1518字节 CONTROL 1/2字节 INFO 信息

6

Destination: Spanning-tree-(for-bridges)_00 (01:80:c2:00:00:00) 目的:厂名_序号(网卡地址) Source: Hangzhou_07:f2:e0(00:0f:e2: 07:f2:e0) 源:厂名_序号(网卡地址) Length:132 长度

1.4 总结

Wireshark是一款基于winpcap的抓包软件,它的界面是友好的,功能是强大的,上手是容易的,掌握是困难的。通过短暂的学习研究,我发现Wireshark之于我么学生的主要功能便是帮助我们更好地学习和理解协议。正如你知道西瓜是甜的,但只有当你吃过了西瓜,你才能体会到西瓜特有的味道。而Wirkshark就给我们提供了吃西瓜所需的西瓜刀。

Wireshark是一款网络嗅探器,它以明文的方式显示捕获的包信息,这对于分析协议格式和内容是非常有帮助的。而且Wireshark的显示比较人性化。过滤器方式可以是捕获前过滤也可以是捕获后过滤,可以根据自己的需要自行选择。

本次实验,让我深刻地去了解了网络各协议栈的协议,以及对应的包头格式及内容,尤其是对IP包头和ICMP包头的了解又更上一层楼了,这对于理解网络通信的原理特别有帮助,理解了各协议栈的任务以及基本原理。

7

实验二 网络层实验—Ping程序的设计与实现

一、实验内容和要求

本实验为ICMP实验。实验内容:Ping命令实现的扩充,在给定的Ping程序的基础上做如下功能扩充:

? -h 显示帮助信息

? -b 允许ping一个广播地址,只用于IPv4 ? -t 设置ttl值,只用于IPv4

? -q 安静模式。不显示每个收到的包的分析结果,只在结束时,显示汇总结果 Ping命令的基本描述

Ping的操作是向某些IP地址发送一个ICMP Echo消息,接着该节点返回一个ICMP Echo reply消息。ICMP消息使用IP头作为基本控制。IP头的格式如下

0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Total Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identification |Flags| Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time to Live | Protocol | Header Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Version=4

IHL Internet头长 Type of Service = 0

Total Length IP包的总长度

Identification, Flags, Fragment Offset 用于IP包分段 Time to Live IP包的存活时长 Protocol ICMP = 1

Addresses 发送Echo消息的源地址是发送Echo reply消息的目的地址,相反,发送Echo 消息的目的地址是发送Echo reply消息的源地址。

Ping实际上是使用ICMP中的ECHO报文来实现的。Echo 或 Echo Reply 消息格式如下: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Code | Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identifier | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Data ... +-+-+-+-+-

8

Type

echo消息的类型为8

echo reply 的消息类型为0。 Code=0

Checksum 为从TYPE开始到IP包结束的校验和 Identifier

如果 code = 0, identifier用来匹配echo和echo reply消息 Sequence Number

如果 code = 0, identifier用来匹配echo和echo reply消息 功能描述:

收到echo 消息必须回应 echo reply 消息。

identifier 和 sequence number 可能被发送echo的主机用来匹配返回的 echo reply消息。例如: identifier 可能用于类似于TCP或UDP的 port 用来标示一个会话, 而sequence number 会在每次发送echo请求后递增。 收到echo的主机或路由器返回同一个值与之匹配

1、 数据结构的描述

1) IP包格式 struct ip {

BYTE Ver_ihl; //版本号与包头长度 BYTE TOS; //服务类型 WORD Leng; //IP包长度

WORD Id; //IP包标示,用于辅助IP包的拆装,本实验不用,置零 WORD Flg_offset; //偏移量,也是用于IP包的拆装,本实验不用,置零 BYTE TTL; //IP包的存活时间

BYTE Protocol; //上一层协议,本实验置ICMP

WORD Checksum; //包头校验和,最初置零,等所有包头都填写正确后,计算并替换。 BYTE Saddr[4]; //源端IP地址 BYTE Daddr[4]; //目的端IP地址 BYTE Data[1]; //IP包数据

};

2)ICMP包格式 struct icmp {

BYTE Type; //ICMP类型,本实验用 8: ECHO 0:ECHO REPLY BYTE Code; //本实验置零

WORD Checksum; //ICMP包校验和,从TYPE开始,直到最后一位用户数据,如果为

字节数为奇数则补充一位

WORD ID; //用于匹配ECHO和ECHO REPLY包 WORD Seq; //用于标记ECHO报文顺序 BYTE Data[1]; //用户数据

};

二、实验环境

Windows平台 +Linux平台

9

三、 程序的需求分析与逻辑框图 需求分析

允许ping广播地址 打印要获取包的数量 显示使用帮组信息 设置时间间隔

设置安静模式,不显示每个收到的包的分析结果,只在结束时,显示汇总结果 设置数据的长度

设置ttl(生存时间)值

除了打印ECHO-RESPONSE数据包之外,还打印其它所有返回的ICMP数据包 逻辑框图

main为SIGALARM 建立信号处理程序sig_alarmreadloopsendrecvfrom无限接收循环proc每秒发送一个Echo消息ping程序函数概貌

1)main函数

10

设置随同Echo请求一起发送的可选数据长度处理命令行参数为SIGALARM信号建立一个处理程序处理主机名参数调用readloop处理分组

2)readloop函数

创建套接口设置套接口缓冲区大小发送第一个分组读取返回给ICMP原始套接口的每个分组记录收到分组的时间调用proc来处理这些分组

3)proc函数

11

获取ICMP头部指针检查ICMP Echo replay输出收到的所有ICMP消息

4)send函数

构造ICMP消息计算校验和发送数据报 四、程序核心功能的实现机制

程序分为两大部分:一部分读取收到的所有消息,并输出ICMP Echo replay消息,另一部分每隔一秒钟发送一个Echo消息。另一部分由SIGALARM信号每秒驱动一次。

首先需要在main()函数里面的循环getopt()的参数里面增加参数,本次实验加的是b,c,h,i,q,s,t,v.然后不同的分支执行不同的操作。

在主函数的最后也就是readloop()函数之后再加一个statistics()数据统计函数,目的是在readloop()中返回时直接执行这个函数。 五、程序源代码(核心部分) 黑体字部分是修改部分

int main(int argc, char **argv) {

extern char *optarg; int c;

struct addrinfo *ai; char *e;

opterr = 0; /* don't want getopt() writing to stderr */

while ( (c = getopt(argc, argv, \ switch (c) { case 'b':

options |= F_BROADCAST; break; case 'c':

npackets = strtol(optarg, &e, 10);

12

if (npackets <= 0 || *optarg == '\\0' || *e != '\\0') errx(1, \ break; case 'h': usage(); exit(1); break;

case 'i': /* wait between sending packets */ interval = strtol(optarg, &e, 10);

if (interval <= 0 || *optarg == '\\0' || *e != '\\0') errx(1, \ options |= F_INTERVAL; break; case 'q':

options |= F_QUIET; break;

case 's': /* size of packet to send */ datalen = strtol(optarg, &e, 10);

if (datalen <= 0 || *optarg == '\\0' || *e != '\\0') errx(1, \ if (datalen > MAXPACKET)

errx(1, \value too large, maximum is MAXPACKET);

break; case 't':

ttl = strtol(optarg, &e, 10);

if (datalen <= 0 || *optarg == '\\0' || *e != '\\0') errx(1, \ options |= F_TTL; break; case 'v':

options |= F_VERBOSE; break; //case '?':

//err_quit(\ default: usage(); } }

if (optind != argc-1) usage(); host = argv[optind]; pid = getpid();

signal(SIGALRM, sig_alrm);

ai = host_serv(host, NULL, 0, 0);

if (options & F_BROADCAST)

13

%d\ printf(\

printf(\ Sock_ntop_host(ai->ai_addr, ai->ai_addrlen), datalen);

/* 4initialize according to protocol */ if (ai->ai_family == AF_INET) { pr = &proto_v4; #ifdef IPV6

} else if (ai->ai_family == AF_INET6) { pr = &proto_v6;

if (IN6_IS_ADDR_V4MAPPED(&(((struct sockaddr_in6 *) ai->ai_addr)->sin6_addr))) err_quit(\#endif } else

err_quit(\

(void)signal(SIGINT, onint);

pr->sasend = ai->ai_addr;

pr->sarecv = calloc(1, ai->ai_addrlen); pr->salen = ai->ai_addrlen;

readloop();

statistics(); exit(0); }

////////////////////////////////////////////////////////////// void readloop(void) {

int size;

char recvbuf[BUFSIZE]; socklen_t len; ssize_t n; struct timeval tval; int yes = 1;

sockfd = socket(pr->sasend->sa_family, SOCK_RAW, pr->icmpproto);

setuid(getuid()); /* don't need special permissions any more */

size = 60 * 1024; /* OK if setsockopt fails */

setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));

if (options & F_TTL)

14

setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); if (options & F_BROADCAST) setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));

sig_alrm(SIGALRM); /* send first packet */ for ( ; ; ) {

len = pr->salen;

n = recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, pr->sarecv, &len); if (n < 0) {

if (errno == EINTR) continue; else

err_sys(\ }

gettimeofday(&tval, NULL);

(*pr->fproc)(recvbuf, n, &tval);

if (npackets && nreceived >= npackets) break; } }

///////////////////////////////////////////////////////////// void usage() {

err_quit(\ [-b] [-q] [-s packetsize]\\n\\

[-t ttl] [-v] [-h] \}

///////////////////////////////////////////////////////////// void statistics()

{ printf(\

printf(\packets transmitted, %ld received , %%%d lost\\n\nsend, nreceived, (nsend-nreceived)/nsend*100); close(sockfd); exit(1); }

六、程序扩展功能的需求分析与实现

-b分支,主要是允许ping广播地址,设置option |=F_BROADCAST,然后在reedloop()函数

里面增加

if (options & F_BROADCAST)

setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));语句 -c分支,主要是要获取包的数量,通过npackets = strtol(optarg, &e, 10);来实现。 -h分支,主要用来显示使用帮组信息的,只需要通过函数usage(),就可显示帮助信息。

15

-i分支,主要用来设置时间间隔,这里需要两步操作,一步是获取interval值,通过interval

= strtol(optarg, &e, 10)获取,一步是要设置options |= F_INTERVAL;然后在sig_alrm()函数里面添加alarm(interval);语句,执行函数signal(SIGALRM,sig_alrm).

-q分支,设置安静模式,不显示每个收到的包的分析结果,只在结束时,显示汇总结果,

主要设置options |= F_QUIET;然后在proc_v4()函数中打印东西之前增加 if (options & F_QUIET) return; 语句,那么就跳过了打印每一个结果。 -s分支,主要用来设置数据的长度,通过datalen = strtol(optarg, &e, 10);语句来获取。 -t分支,用来设置ttl(生存时间)值,同样要经过两步操作,一步是通过ttl = strtol(optarg,

&e, 10);语句来获取ttl值,第二部是设置options |= F_TTL,最后还要在readloop()函数里面通过语句 if (options & F_TTL) setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));

来改变ttl值。

-v分支,除了打印ECHO-RESPONSE数据包之外,还打印其它所有返回的ICMP数据包

七、实验数据、结果分析

-b 允许ping广播地址

-c 设置包的数量

16

-h 显示帮助信息

-i 设置时间间隔

-q 设置为安静模式,只显示结果,不显示具体的包

17

-s 设置数据的长度

-t 设置ttl值,抵达本机时如果,ttl小于设定的值就丢掉该包

-v 除了打印ECHO-RESPONSE数据包之外,还打印其它所有返回的ICMP数据包

18

八、总结

实验二的实践过程中掌握了之前没有弄明白的网络层协议的原理,但是在 最后实现部分仍然存在很多的问题,连接广播地址时没有实现同一网段内的所有主机都对发送消息的主机返回echo replay消息,主机只收到自己的返回确认消息,这个问题纠结了很久,并且也上网收集了一些资料,很遗憾的是这个问题最后还是没有成功的解决。

通过程序设计,对ICMP报文的结构模式进一步认识,对基于linux的c语言编程进一步掌握。程序设计中,对各种函数之间的协同工作运用理解更加深刻。更掌握了如何使用信号量进行通信。比如ping不同目的端,可能是目的端的不存在,但若雨目的及联通状态ping不同,可能是权限不够,即要通过root身份才可以成功执行程序;再如执行完某一段函数时未如预期般停止,则可能少了exit()函数。

九、同组人分工情况

学号 10118124

姓名 邓广晖 承担任务 -q -t 19

实验三 应用层实验—简单聊天程序的设计与实现

一、实验内容和要求

本实验要求设计并实现一个简单的聊天程序,包括服务器实现和客户端实现,具体内容和要求如下:

? 使用MSN或者QQ,分析聊天程序的功能需求;

? 在给定的参考程序的基础上,参考MSN或者QQ,对功能做出扩充,不局限于以

下的范围:

? 在客户端界面上显示所有联入聊天服务器的用户; ? 支持两个用户之间的聊天;

? 支持增加好友的功能,好友上线时如果该用户在线,则做出提醒; ? 增加用户的个人信息修改、保存和查询; ? 在聊天内容中支持中文;

? 在聊天内容中支持图片等多媒体信息; ? 支持在用户之间传输文件等附件; ? 其他扩展。

二、实验环境

本人这个实验实验是使用老师给的eclipse软件。 三、程序的需求分析与逻辑框图 需求分析:

1.个性登陆:登录框中包含个人名称,设置服务器和端口,选择性别。 E-R图为

登陆界面 名称 性别 端口号

2.聊天功能: 群聊天功能:任意用户直接在编辑区编辑文字后点击发送,所有在线用户均可在群聊天界面看到聊天内容。

私聊功能:若在用户显示界面双击某位在线用户并且在聊天室界面最下部勾选“私聊”,则可以进行私聊,只有聊天双方可在悄悄话界面看到自己的聊天内容。

E-R图为

聊天功能 群聊 私聊 服务器

3.文字属性:聊天内容的文字有字体、样式、字号和颜色。 字体有宋体,黑体,Dialog,Gulim。 样式有常规,斜体,粗体,斜粗体 字号有12、14、18、22、30、40

20

颜色有黑色、红色、蓝色、黄色、 绿色。 E-R图为

文字 样式 字号 颜色 字体 宋体 黑体 Dialog Gulim 常规 斜体 粗体 斜粗体 12、14 18、22 30、40 黑色 红色 蓝色 黄色 绿色

4. 截屏功能:此软件支持截屏功能,单击“截屏”按钮弹出截屏功能界面可以选择界面风格。单击“开始截取”按钮在屏幕上可截取画面,双击后可在截屏界面预览截屏效果。可选择将截屏图片复制到剪贴板或者保存到本地硬盘。 5. 文件传输功能:用户在用户显示界面双击要发送到的联系人,勾选“私聊”选框后,把要发送的文件拖拽到“欲发送的文件”区域,点击“发送文件”按钮即可。若开始或中途想放弃传送则可单击“取消发送”按钮。用户点击发送后,在悄悄话区域会告知接收者发来文件,若接收者接收则单击“接收文件”按钮,然后设定本地保存位置;若不接收则点击“拒绝接收”按钮。

6.清空功能:可以清空群聊区信息,也可以清空私聊区信息。清空主要是用来避免信息冗余,当信息量太多时,旧的信息就没有多大用了,这时你可以清空以前的信息,接下来就只显示后来信息了。

7.主界面:主界面包含以下功能,在线好友、群聊区,私聊区,文字编辑区,文件发送区。在线好友主要用来确定好友是否在线,方便发信息和发文件。群聊区主要用来显示群发的信息,以及自己发的信息。私聊区是用来显示私聊信息的,只在好友方的私聊区显示,不在群聊区显示。文件发送区主要用来显示文件的目录,而文件通过进度条来显示文件的传输过程。

E-R图为

主界面 在线好友 文件发送区 群聊区 逻辑框图: 服务器

私聊区 文字编辑区

21

开始 启动服务器并使用端口初始化服务器实例化一个client线程类 每一个对应一个客户连接 将客户socket信息存储并显示 启动线程,处理并显示客户端信息 是否退出? 是 关闭服务器 客户端

设计客户端界面 并初始化

登陆 用户是否请求连接 是 将客户端线程实例化 启动线程 监听服务器传来的信息 是 请求断开连接 否 是 发送聊天消息 定义并实例化一个字符缓冲存储信息 用打印流发送信息 关闭所有窗口并结束监听线程 线程处理服务器传来的信息

22

四、程序核心功能的实现机制

实现聊天程序,并要考虑服务器能够同时处理多个客户的连接请求,实现客户端通过服务器的转发通信。则通信过程分析如下: 服务器:1)建立socket绑定监听端口; 2)等待客户端的连接;

3)当客户与服务器建立连接后,服务器记录客户所有信息,如ID号; 4)当客户消息到达服务器后,服务器分析客户信息:

a.若为转发消息,根据消息格式提供的转发ID号,服务器立即查找转发列表,转发消息,若查找后发现需要转发的客户ID不存在则返回错误信息给原客户,提示不存在要发送的客户端ID号;

b.若为询问消息,服务器返回当前登录到服务器的所有客户的ID号; c.若为退出消息,服务器清楚当前客户的所有信息,更新客户列表; 5)以上2-4步都是随时进行的:

a.任何新进客户端都能随时连接服务器; b.任何以连接客户端都能发送消息,并且服务器能同时处理多个同时到来的消息,实现并发的处理客户端消息。

6)当服务器停止工作时,关闭监听套接字。 客户端:1)建立连接到服务器的socket;

2)发送登录信息,信息中需要包含自身的ID号,使服务器能识别本机;

3)使用信号机制,随时能接收服务器传来的消息,不阻塞,及时的显示到来信息,并记录到来信息,以方便用户查看聊天记录; 4)能随时向服务器发送消息,有三种消息:

a.询问消息,询问服务器当前已连接到服务器的客户列表;

b.转发消息,告知服务器需要转发的ID号和信息,通过服务器进行聊天; c.退出消息,告知服务器本客户端要退出聊天;

5)要获取当前以登录服务器的客户列表,则会发送询问消息,得到返回结果后更新本地的用户登录列表;

6)当想退出时,向服务器端发送退出消息后,关闭套接字,关闭程序。

五、程序源代码(核心部分)

本人负责的是服务器端的所有任务,现将核心代码摘录如下 /////////ChatServer.java package Server; import java.net.*; import java.util.*; import java.io.*;

public class ChatServer {

static final int DEFAULT_PORT = 6000; static ServerSocket serverSocket;

static Vector connections;// 连接 static Vector clients;

/**

23

*

* 发送信息给所有的人 */

public static void sendAll(String s) {

if (connections != null) {

for (Enumeration e = connections.elements(); // Enumeration过时的接口,

// 可以用for each循环

e.hasMoreElements();) {

try {

PrintWriter pw = new PrintWriter(((Socket) e.nextElement()) .getOutputStream()); pw.println(s); pw.flush();

} catch (IOException ex) {

ex.printStackTrace(); } } }

System.out.println(s); }

/** *

* 发送信息给单独一个人 */

public static boolean sendOne(String name, String msg) {

if (clients != null) {

for (Enumeration e = clients.elements(); e.hasMoreElements();) {

ClientProc cp = (ClientProc) e.nextElement(); if ((cp.getName()).equals(name)) {

try {

PrintWriter pw = new PrintWriter((cp.getSocket())

24

}

.getOutputStream()); pw.println(msg); pw.flush();

return true; // 返回值为真,找到了这个人可以进行聊天 } catch (IOException ioe) {

ioe.printStackTrace(); } } } }

return false;// 没有找到这个人,应该是此人已经退出了聊天室

public static void addConnection(Socket s, ClientProc cp) {

if (connections == null) {

connections = new Vector(); }

connections.addElement(s); }

if (clients == null) {

clients = new Vector(); }

clients.addElement(cp);

public static void deleteConnection(Socket s, ClientProc cp) throws IOException {

if (connections != null) {

connections.removeElement(s); s.close(); }

if (clients != null) {

clients.removeElement(cp); } }

public static Vector getClients()

25

{

return clients; }

/** *

* 服务器端在此启动 */

public static void main(String[] arg) {

int port = DEFAULT_PORT; try {

serverSocket = new ServerSocket(port);

System.out.println(\服务器已经启动,正在监听...\ } catch (IOException e) {

System.out.println(\异常\ System.err.println(e); System.exit(1);

}

while (true) { // 死循环 try {

Socket cs = serverSocket.accept();

ClientProc cp = new ClientProc(cs); // 启动一个用户线程 Thread ct = new Thread(cp); //Thread ar = new Thread(ct.) ct.start();

addConnection(cs, cp); } catch (IOException e) {

System.err.println(e); } } } }

★★★★★★★★★★★★★★★★★★★★★★ ////////////////// ClientProc.java package Server;

26

import java.net.*; import java.util.*; import java.io.*;

class ClientProc implements Runnable {

/**

* 为某个用户服务的一个用户线程 */

Socket s;

BufferedReader in; PrintWriter out;

private String name = null; private String sex = null;

public ClientProc(Socket s) throws IOException {

this.s = s;

in = new BufferedReader(new InputStreamReader(s.getInputStream()));// 得到输入的端口流

out = new PrintWriter(s.getOutputStream());// 从端口得到一个输出流 this.updateList(); }

public String getName() {

return name; }

public String getSex() {

return sex; }

public Socket getSocket() {

return s; }

private void updateList() {

// 更新用户列表(即发送当前在聊天室的用户到新来的用户列表中) Vector cs = ChatServer.getClients();

27

if (cs != null) {

for (Enumeration e = cs.elements(); e.hasMoreElements();) {

ClientProc cp = (ClientProc) e.nextElement(); String exist_name = cp.getName(); String exit_sex = cp.getSex();

System.out.println(\ out.println(\ out.flush(); } } }

public void run() {

while (name == null) {

try {

String inmsg;

inmsg = in.readLine();

ChatServer.sendAll(\+ inmsg); new&zhangsan

String[] userInfo;

userInfo = inmsg.split(\ name = userInfo[0]; sex = userInfo[1]; } catch (IOException ee) {

ee.printStackTrace(); } }

while (true) {

try {

String line = in.readLine(); System.out.println(line); // 处理退出事件(读取信息) if (line.equals(\ {

28

// 发送信息更新用户列表 天室\ 息

消息 文件消息

ChatServer.sendAll(\【系统消息】 \退出了聊

ChatServer.deleteConnection(s, this); return;

// 处理刷新用户列表请求

} else if (line.equals(\{

this.updateList();

// 一般消息,又可以分为几种,对大家说, 与某个人交谈,或者私聊 } else {

String[] inmsg = line.split(\

if (inmsg[0].compareTo(\ {

ChatServer.sendOne(inmsg[1], \ + this.name);

} else if (inmsg[0].compareTo(\发送文件消 {

// String[] sendfile = line.split(\

ChatServer.sendOne(inmsg[1], \

+ this.name + \ + \

} else if (inmsg[0].compareTo(\接收文件 {

// String[] acceptfile = line.split(\ ChatServer

.sendOne(inmsg[1], inmsg[0] + \ } else if (inmsg[0].compareTo(\拒绝接收 {

// String[] refusefile = line.split(\ ChatServer

.sendOne(inmsg[1], inmsg[0] + \ } else if (!line.startsWith(\ { // 对所有人说

ChatServer.sendAll(this.name + \ }

// String[] inmsg = line.split(\ else if (inmsg[1].equals(\ {

if (ChatServer.sendOne(inmsg[2], \

29

+ name + \ { // success

out.println(\ + inmsg[2] + \ // 再发给自己一份

out.flush(); // 一定要有 } else {

out.println(inmsg[2] + \已经离开聊天室\ out.flush(); // 一定要记住要 } } else {

ChatServer.sendAll(\ + inmsg[2] + \ } // 发给所有的人

}

} catch (IOException e) {

System.out.println(e.toString()); try {

s.close();

} catch (IOException e2) { }

return; } }

}//end run }

30

六、程序扩展功能的需求分析与实现

实现了用户端界面上显示所有联入聊天服务器的用户,详情请参考客户端PList.java文件中的public PList(ChatFrame sup)。

实现了支持两个用户之间的聊天,主要是通过服务器端的sendOne()函数实现的。详细的代码请参考ChatServer.java文件里面的public static boolean sendOne(String name, String msg)。 实现了好友上线时如果该用户在线,则做出提醒,主要是通过服务器端的updateList()函数实现对客服端监听的,并把监听的信息返回到客户端的PList()。

实现了在聊天内容中支持中文,本程序主要是通过PrintWriter来实现支持中文; 实现了在聊天内容中文字的修改,比如字体、颜色等,主要是通过客户端ChatFrame.java 文件里面private FontAttrib getFontAttrib()实现的;

实现了在用户之间传输文件附件,主要是通过客户端ChatFrame.java文件中的sendfilethread()函数与acceptfilethread()函数来发送和接收文件。

实现了截屏功能,截屏的主要原理是确定对角两点的坐标,并以此坐标界限来截取屏幕,详细内容请参考客户端CaptureScreen.java 中的public CaptureScreen()函数。

七、实验数据、结果分析

个性登陆:

如果服务器没有先启动就会有错误提示:

文字属性设置:包括了字体,样式,字号和颜色。

31

群聊功能:

私聊功能:

文件传输:、我们组认为,文件传输的一大亮点就是在传输时显示进度条,当文件的容量很小时,有可能看不见进度条的动态,这时你可以传一个比较大的文件,

32

方便观看进度条,也方便截图。如下图

截图功能:

点击上图的“开始截取”按钮,界面会提示你“请按住鼠标左键不放选择截图区”,如下图所示

33

然后进入下面界面,选中的区域是用“红线”框起来的。

双击上图红线区域,红线区域的内容就会出现在截屏面板上,可同时多截几张图,依次命名为,图片1、图片2、??。

34

清空信息:右键聊天区域时会弹出下拉菜单,你可以选择“清空群聊天信息”也可以选择“清空私聊区信息”。

选中“清空群聊区信息”后,群聊区的内容就会被清空,如下图

35

服务器监听客户端信息:

36

八、总结

通过这次Java课程设计,我收获了很多,当老师布置课程设计时,我心里就想,好多东西和知识点都不懂,这怎么做啊。但还是坚持下来了,于是提前看书,在网上找点资料,这激起了我学习的主动性 ,于是在组员的共同努力下,我们顺利的完成了本次任务。

通过聊天程序的编写,我知道了两台机器间通信的机制,以及客户端套接字和服务器端套接字的连接,java中的Socket是基于流的,服务器端的Socket在java.net.ServerSocket里,客户端的Socket在java.net.Socket里。客户端需要用地址(或主机名)和端口号来指定一个套接字,而服务器端只需要端口就可以指定一个套接字。

在编写过程中,老师给的代码不支持中文,所以我们通过图书馆和上网的方式查到可以用PrintWriter来保存输入输出流。所以服务器端的原理就是PrintWriter通过getInputStream来缓存客服端的OutputStream,然后再通过服务器端的getOutputStream把数据流发送出去。

九、同组人分工情况

学号 10118124

姓名 邓广晖 承担任务 服务器端,需求分析,E-R图设计 37

本文来源:https://www.bwwdw.com/article/i95.html

Top