admin 管理员组

文章数量: 887031


2023年12月22日发(作者:企业网站建设解决方案)

电子信息与通信工程学院

实验报告

实验名称

课程名称

姓名 顾康

多人聊天软件

计算机网络

学号

U201413323

日期 12月6日 地点

成绩

教师 刘威

一. 实验背景

1. Client/ Server 结构

在 TCP/IP 网络环境下,不同主机上的两个应用程序间通信普遍采用客户机服务器模型(client/server architecture, C/S).即通信双方一方作为服务器等待客 户提出请求并予以响应,客户则在需要服务时向服务器提出申请。服务器一般作 为守护进程始终运行,监听网络接口,一旦有客户请求,就启动一个服务进程来 响应客户,同时自己继续监听服务端口,使后续客户也能及时得到服务。 采用这种模型主要是由于网络中主机的软硬件资源、运算能力和信息分布不 均匀导致的,当需要信息共享时,运算能力强、拥有众多资源的主机就作为服务 器对外提供服务,资源相对较少的主机就成为客户机通过请求获得所需资源。 本编程训练的目的之一是通过编程了解客户机-服务器结构的网络通信模型。

2. Socket编程

本实验使用的语言为python, 目的是便于建立线程。而python的Socket 功能包含于“Soclet”模块中,直接调用即可, 且函数功能与Winsocket 一致。

服务器 首先启动,通过调用 socket()建立一个套接口,然后 bind()将该套接 口和本地地址(IP 地址和端口)绑定在一起,再 listen()使得套接口做好侦听准备,并规定它的请求队列的长度,之后就调用 accept()来接收连接,并获得客户 机的地址信息;

客户机在建立套接口之后就可以调用 connect()和服务器建立连接;连接一旦 建立,客户机和服务器之间就可以通过调用 send()和 recv()来发送和接收数据;

最后,待数据传送结束后,双方调用 closesocket()关闭套接口。

3,关于python的线程创建

Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。

class ion(lock=None)

本类用于实现条件变量对象。条件变量对象允许多条线程保持等待状态直到接收另一条线程的通知。

如果选择传入 lock 参数,只能使用 Lock 或 RLock

对象,而且它会被当做一个隐性锁使用。如果不传此参数,那么程序会自动隐性地创建一个 RLock 对象。

acquire(*args)

本方法用于获取隐性锁(关联锁),它调用隐性锁的 acquire() 方法,并返回其所返回的值

release()

同上,本方法无返回值

wait(timeout=None)

等待通知或超时。如果线程没有获取到锁就调用了此方法,那么将引发

RuntimeError 异常

本方法会释放隐性锁,然后阻塞直到被其他线程的调用此条件变量的 notify()

或 notify_all() 唤醒,或超时。一旦被唤醒或超时,该线程将立即重新获取锁并返回

timeout 参数是以秒为单位的浮点数

如果隐性锁是一个 RLock 对象,因为调用它的 release() 方法未必能够释放该锁,所以本方法会使用 RLock 对象的一个内部接口,该接口可以立即释放多重迭代的 RLock 锁。并且在需要重新获取锁的时候,也会使用一个类似的内部接口来恢复多重的迭代级别

本方法所阻塞的线程如果是被唤醒的,那么本方法会返回一个 True,如果是超时了,则返回 False

notify(n=1)

本方法默认用于唤醒处于等待本条件变量的线程。如果调用本方法的线程并没有获得锁,将引发 RuntimeError 异常

本方法至多可唤醒所有正在等待本条件变量的线程中的 n 个。如果调用时没有线程处于等待操作,那么本方法的调用是一个空操作

现在版本对本方法的实现为:在有足够多处于等待状态的线程的条件下,本方法将正好唤醒其中的 n 个,而不是像上一条中讲的“至多 n 个”。不过这种行为并不可靠。在将来,本方法很可能偶尔唤醒超过 n 条线程

notify_all()

唤醒正在等待本条件变量的所有线程。

二 . 代码简述:

以上代码可见Server对象 从创建端口,到绑定Socket接口和开始监听都与C语言实现大体无异,只是简化了繁琐的定义过程。

而反观client对象,也无需赘述,连接过程十分简单。

重点在于建立线程的部分:

每个client有两个线程,分别负责接收和发送,当没有发送时,在raw_input()那卡住,当没有接收时,在recv()那卡住

server为每个client开两个线程,分别处理接收和发送。每个发送的线程在()那阻塞,等待notify。每个接收的线程,在recv()那里等待来自client的输入,接收到输入后,发出一个notify,激活所有输出线程,自身则因为循环在下一个recv()那里等待。

该函数在Server中负责接受Client 的消息,当没有消息发生时处于挂起状态,只有在接受的消息后被激活,并且唤醒其它所有的线程。例如:Client A发送 hello, 则此时属于Client A的接受函数被激活,随后唤醒它的消息转发函数和Client B的线程。

该函数在Server中发送消息,同样也在无消息时被挂起。

调用Thread模块可以很方便地创建线程,这也是我选用python的目的,可以比C语言更直观的实现算法。

而Client部分同理,创建两个线程,用于发送和接受消息。

三 效果图展示

对于第一个客户Leo, 要求先输入昵称,并且服务器会广播xx加入聊天的提示

How R Y Eric 是Leo 对另一个客户Eric 打招呼的内容,以绿字代表自己的输入

对于Eric, 收到Leo打招呼的内容,回复了

I’m fine

这是Server端维护的窗口,将记录客户的连接,当前活跃人数,以及消息记录。

五 实验心得

这次实验是我初次接触socket编程,虽然整个流程是既定的,但是还是遇到了了一些问题。比如:在Client 与 Server 互传信息时,常常会混淆send() 和recv() 的端口是什么,对方还是自己。所以在测试时遇到的输入一串字符回车后,在自己窗口无限打印的情形,就是由上述原因所导致的。

而多人聊天的功能其实与Socket 编程没有过多的关系,基本流程仍相同,重点是在于如何达到多个ClIent同时活跃的目的:也就是python Condition 类与线程的应用。思想其实很简单,每个线程在没有工作时休眠,而一旦某个条件被满足,一个被唤醒的线程在完成工作后也会唤醒所有的线程,形成一个多米诺效应。运用线程最重要的是思路清晰,具体细节并不复杂:acquire() -> wait() -> release() . 在对功能切割后,分给不同的线程区实现,实际上提高了效率,也体现了算法的高度模块化。

从初步了解Socket 到完成了自己的小项目,让自己的认识从理论层面上升到了实际操作感受。遗憾的是我们的测试只能基于自己电脑上的模拟而非网络通信过程,所以认识还不够深刻,也无法观察各种异常情形的产生。虽然有着这些局限性,但这次实验的收获还是很有意义的。


本文标签: 线程 服务器 方法 条件 等待