# 基于 Socket 接口实现自定义协议通信
# 一、实验目的
学习如何设计网络应用协议
掌握 Socket 编程接口编写基本的网络应用软件
# 二、 实验内容
根据自定义的协议规范,使用 Socket 编程接口编写基本的网络应用软件。
- 掌握 C 语言形式的 Socket 编程接口用法,能够正确发送和接收网络数据包。
- 开发一个客户端,实现人机交互界面和与服务器的通信。
- 开发一个服务端,实现并发处理多个客户端的请求。
- 程序界面不做要求,使用命令行或最简单的窗体即可。
- 功能要求如下:
1. 运输层协议采用 TCP
2. 客户端采用交互菜单形式,用户可以选择以下功能:
a) 连接:请求连接到指定地址和端口的服务端。
b) 断开连接:断开与服务端的连接。
c)获取时间: 请求服务端给出当前时间。
d)获取名字:请求服务端给出其机器的名称。
e)活动连接列表:请求服务端给出当前连接的所有客户端信息(编号、IP 地址、端口等)
f)发消息:请求服务端把消息转发给对应编号的客户端,该客户端收到后显示在屏幕上
g) 退出:断开连接并退出客户端程序
3.服务端接收到客户端请求后,根据客户端传过来的指令完成特定任务:
a)向客户端传送服务端所在机器的当前时间。
b)向客户端传送服务端所在机器的名称。
c)向客户端传送当前连接的所有客户端信息。
d)将某客户端发送过来的内容转发给指定编号的其他客户端。
e)采用异步多线程编程模式,正确处理多个客户端同时连接,同时发送消息的情况。
- 根据上述功能要求,设计一个客户端和服务端之间的应用通信协议。
- 本实验涉及到网络数据包发送部分不能使用任何的 Socket 封装类,只能使用最底层的 C 语言形式的 Socket API。
- 本实验可组成小组,服务端和客户端可由不同人来完成。
### 主要仪器设备
- 联网的 PC 机、Wireshark 软件
- Visual C++、gcc 等 C++ 集成开发环境。
# 三、操作方法与实验步骤
- 设计请求、指示(服务器主动发给客户端的)、响应数据包的格式,至少要考虑如下问题:
1. 定义两个数据包的边界如何识别。
2. 定义数据包的请求、指示、响应类型字段。
3. 定义数据包的长度字段或者结尾标记。
4. 定义数据包内数据字段的格式(特别是考虑客户端列表数据如何表达)。
- 小组分工:1 人负责编写服务端,1 人负责编写客户端。
- 客户端编写步骤(需要采用多线程模式)
1. 运行初始化,调用 socket(),向操作系统申请 socket 句柄。
2. 编写一个菜单功能,列出 7 个选项
3. 等待用户选择。
4. 根据用户选择,做出相应的动作(未连接时,只能选连接功能和退出功能)
1. 选择连接功能:请用户输入服务器 IP 和端口,然后调用 connect(),等待返回结果并打印。连接成功后设置连接状态为已连接。然后创建一个接收数据的子线程,循环调用 receive(),如果收到了一个完整的响应数据包,就通过线程间通信(如消息队列)发送给主线程,然后继续调用 receive(),直至收到主线程通知退出。
2. 选择断开功能:调用 close(),并设置连接状态为未连接。通知并等待子线程关闭。
3. 选择获取时间功能:组装请求数据包,类型设置为时间请求,然后调用 send()将数据发送给服务器,接着等待接收数据的子线程返回结果,并根据响应数据包的内容,打印时间信息。
4. 选择获取名字功能:组装请求数据包,类型设置为名字请求,然后调用 send()将数据发送给服务器,接着等待接收数据的子线程返回结果,并根据响应数据包的内容,打印名字信息。
5. 选择获取客户端列表功能:组装请求数据包,类型设置为列表请求,然后调用 send() 将数据发送给服务器,接着等待接收数据的子线程返回结果,并根据响应数据包的内容,打印客户端列表信息(编号、IP 地址、端口等)。
6. 选择发送消息功能(选择前需要先获得客户端列表):请用户输入客户端的列表编号和要发送的内容,然后组装请求数据包,类型设置为消息请求,然后调用 send()将数据发送给服务器,接着等待接收数据的子线程返回结果,并根据响应数据包的内容,打印消息发送结果(是否成功送达另一个客户端)。
7. 选择退出功能:判断连接状态是否为已连接,是则先调用断开功能,然后再退出程序。否则,直接退出程序。
8. 主线程除了在等待用户的输入外,还在处理子线程的消息队列,如果有消息到达,则进行处理,如果是响应消息,则打印响应消息的数据内容(比如时间、名字、客户端列表等);如果是指示消息,则打印指示消息的内容(比如服务器转发的别的客户端的消息内容、发送者编号、IP 地址、端口等)。
- 服务端编写步骤(需要采用多线程模式)
1. 运行初始化,调用 socket(),向操作系统申请 socket 句柄
2. 调用 bind(),绑定监听端口(请使用学号的后 4 位作为服务器的监听端口),接着调用 listen(),设置连接等待队列长度
3. 主线程循环调用 accept(),直到返回一个有效的 socket 句柄,在客户端列表中增加一个新客户端的项目,并记录下该客户端句柄和连接状态、端口。然后创建一个子线程后继续调用 accept()。该子线程的主要步骤是(刚获得的句柄要传递给子线程,子线程内部要使用该句柄发送和接收数据):
- 调用 send(),发送一个 hello 消息给客户端(可选)
- 循环调用 receive(),如果收到了一个完整的请求数据包,根据请求类型做相应的动作:
1. 请求类型为获取时间:调用 time()获取本地时间,然后将时间数据组装进响应数据包,调用 send()发给客户端
2. 请求类型为获取名字:将服务器的名字组装进响应数据包,调用 send()发给客户端
3. 请求类型为获取客户端列表:读取客户端列表数据,将编号、IP 地址、端口等数据组装进响应数据包,调用 send()发给客户端
4. 请求类型为发送消息:根据编号读取客户端列表数据,如果编号不存在,将错误代码和出错描述信息组装进响应数据包,调用 send()发回源客户端;如果编号存在并且状态是已连接,则将要转发的消息组装进指示数据包。调用 send()发给接收客户端(使用接收客户端的 socket 句柄),发送成功后组装转发成功的响应数据包,调用 send()发回源客户端。
主线程还负责检测退出指令(如用户按退出键或者收到退出信号),检测到后即通知并等待各子线程退出。最后关闭 Socket,主程序退出。
- 编程结束后,双方程序运行,检查是否实现功能要求,如果有问题,查找原因,并修改,直至满足功能要求
- 使用多个客户端同时连接服务端,检查并发性
- 使用 Wireshark 抓取每个功能的交互数据包
# 四、实验数据记录和处理
请将以下内容和本实验报告一起打包成一个压缩文件上传:
- 源代码:客户端和服务端的代码分别在一个目录
- 可执行文件:可运行的.exe 文件或 Linux 可执行文件,客户端和服务端各一个
以下实验记录均需结合屏幕截图(截取源代码或运行结果),进行文字�
- 1
- 2
- 3
前往页