通过本实验,学习采用Socket(套接字)设计简单的网络数据收发程序,理解应用数据包是如何通过传输层进行传送的。
采用TCP进行数据发送的简单程序
采用UDP进行数据发送的简单程序
多线程\线程池对比
写一个简单的chat程序,并能互传文件,编程语言不限。
面向连接TCP连接的套接字工作过程是:服务器首先启动,创建套接字后等待客户的连接;客户启动以后,创建套接字,然后和服务器建立连接;连接建立后,客户机和服务器可以通过建立的套接字连接进行信息通信。
2)采用UDP进行数据发送的简单程序
无连接UDP的套接字工作过程是:服务器首先启动,创建套接字后等待客户发来信息;客户启动以后,创建套接字,然后给服务器发送消息,客户机和服务器就可以进行信息通信了。
1)TCP & UDP的区别与联系
TCP服务是进程间可靠传输的一种方式,主要特点是:面向连接、以字节流的方式传输、点对点、全双工的方式进行。示意图如下所示:
UDP服务提供的是在服务器和客户端之间的不可靠传输,主要特点是:无连接、以数据报的方式传输。所以在实际编程实现当中,使用不同的传输协议TCP和UDP,所调用函数也不同。在TCP中:
2)代码所调用的函数
3)文件互传需要注意的问题
Q1:文件大小不确定,有可能比缓冲区大很多,调用一次write()/send() 函数不能完成文件内容的发送。接收数据时也会遇到同样的情况。
A1:要解决这个问题,可以使用 while 循环。对于 Server 端的代码,当读取到文件末尾,fread() 会返回 0,结束循环。
对于 Client 端代码,有一个关键的问题,就是文件传输完毕后让 recv() 返回 0,结束 while 循环。(注:读取完缓冲区中的数据 recv() 并不会返回 0,而是被阻塞,直到缓冲区中再次有数据。)
Q2:Client 端应该如何判断文件接收完毕?
A2:最简单的结束 while 循环的方法是文件接收完毕后让 recv() 函数返回 0,那么,如何让 recv() 返回 0 呢?recv()返回 0 的唯一时机就是收到FIN包时。FIN 包表示数据传输完毕,计算机收到 FIN 包后就知道对方不会再向自己传输数据,当调用 read()/recv() 函数时,如果缓冲区中没有数据,就会返回 0,表示读到了”socket文件的末尾“。这里我采用调用 shutdown() 的方式发送FIN包:server 端直接调用 close()/closesocket() 会使输出缓冲区中的数据失效,文件内容很有可能没有传输完毕连接就断开了,而调用 shutdown() 会等待输出缓冲区中的数据传输完毕。
首先运行server程序,出现”Waiting…”表示server已经成功进入监听状态,等待client的连接请求:
再运行client程序,server程序的运行窗口立刻出现成功建立连接的提示以及对应的client地址,还有client发送的报文,然后server又循环进入了监听状态,等待client下一个连接请求:
对应的client也会收到来自server的报文:
首先运行server程序,只出现了空白的运行窗口:
这是因为UDP是无连接的,这也是与TCP最大的不同点,所以在client给server发送报文之前,server不知道谁要发送报文、发送报文的内容是什么。自然不会在运行窗口显示。
再运行client程序,server程序的运行窗口立刻出现收到来自所在地址的client发送的报文,处理完成之后,server又进入了最初的空白阶段,直到还会有下一个client给他发送报文:
对应的client也会收到来自server的报文:
1)多线程
首先运行server程序,出现为该server分配的端口号,以及出现”Waiting…”表示server已经成功进入监听状态,等待client的连接请求:
再运行client程序,client的运行窗口立刻出现连接成功,并提示用户输入信息:
同时server运行窗口发生变化,显示了与其连接的client端口号:
server运行窗口就会立刻响应有来自端口号196的client发来的消息,同时提示server构造响应报文:
在server运行窗口构造完响应报文(回车结束):
client运行窗口就会立刻响应有来自server发来的消息,同时提示client构造响应报文,又回到上述步骤开始往复进行:
这就实现了多线程的聊天程序,服务器可以并发地处理来自同一用户的多个请求(或者是来自不同用户的多个请求,操作类似,故不在本实验报告中展示截图步骤)。
2)线程池
在进行服务器/客户端通信之前,会出现在当前设置线程池大小(即:server在同一时间内处理client请求的线程数量)时、当前设置任务完成时间内,执行用户任务的个数。比如下图所示是线程池大小为1、完成时间为4s时仅能执行完1个任务:
下图所示是线程池大小为5、完成时间为4s时就能执行完全部3个任务:
这就是线程池和多线程最大的不同。线程池虽然也是单个线程处理客户的请求,但并不是每接受一个新的客户请求就新建一个线程,而是在双方开始通信之前先调用一个线程池函数(如上所示)确定了固定数量的线程,才开始惯例的通信交互。
运行server程序,出现为该server分配的端口号,以及出现”Waiting…”表示server已经成功进入监听状态,等待client的连接请求:
再运行client程序,client的运行窗口立刻出现连接成功,并提示用户输入信息:
同时server运行窗口发生变化,显示了与其连接的client端口号:
server运行窗口就会立刻响应有来自端口号196的client发来的消息,同时提示server构造响应报文:
在server运行窗口构造完响应报文(回车结束):
client运行窗口就会立刻响应有来自server发来的消息,同时提示client构造响应报文,又回到上述步骤开始往复进行:
首先把server程序和client程序分别放在两个不同路径下的文件夹内,server程序所在 的文件夹内还有一个待传送的文件:
client程序所在的文件夹内则没有这个文件:
运行server程序,出现提示表明server已经成功进入监听状态,等待client的连接请求:
再运行client程序,client的运行窗口立刻提示用户输入想要的文件名:
在client的运行窗口输入要传输的文件名(本例中为test.cpp),回车键结束后提示已传输成功:
server运行窗口就会立刻显示该文件已传输成功,并跳转循环回第一个步骤等待处理客户的下一个请求:
于是在client程序所在的文件夹内就能看到test.cpp文件了:
通过本次实验“网络基础编程”,我掌握了Wireshark的使用方法,能熟练运用Wireshark捕获报文并对捕获到的数据分组进行分析,对照课本知识分析HTTP 协议的报文格式,并了解了HTTP 协议的工作过程。整个实验难度系数不算高,但我还是遇到了一些困难。比如一开始捕获报文时,由于对Wireshark操作不熟练,没有对所有捕获的报文进行条件筛选,这时很难查看HTTP协议的报文以及分析HTTP的报文格式。通过独立完成本次实验,我学会了将课本上的知识学以致用,掌握了许多新知识。