计算机网络实验二——网络基础编程实验
一、 实验题目
通过本实验,学习采用Socket(套接字)设计简单的网络数据收发程序,理解应用数据
包是如何通过传输层进行传送的。
二、 实验内容
理解与熟练掌握TCP、UDP,借助python/JAVA语言已经封装的套接字接口实现下面几种简单的数据收发程序:
1、采用TCP进行数据发送的简单程序(java/python3.5)
2、采用UDP进行数据发送的简单程序(java/python3.5)
3、多线程\线程池对比(java/python3.5)
4、写一个简单的chat程序,并能互传文件,编程语言不限。
三、 实验原理
【基础原理】
Socket(套接字)是一种抽象层,应用程序通过它来发送和接收数据,就像应用程序打开一个文件句柄,将数据读写到稳定的存储器上一样。
一个socket 允许应用程序添加到网络中,并与处于同一个网络中的其他应用程序进行通信。一台计算机上的应用程序向socket写入的信息能够被另一台计算机上的另一个应用程序读取,反之亦然。
不同类型的socket 与不同类型的底层协议族以及同一协议族中的不同协议栈相关联。
现在TCP/IP 协议族中的主要socket 类型为流套接字(sockets sockets)和数据报套接字
(datagram sockets)。
流套接字将TCP 作为其端对端协议(底层使用IP 协议),提供了一个可信赖的字节流服务。一个TCP/IP 流套接字代表了TCP 连接的一端。数据报套接字使用
UDP 协议(底层同样使用IP 协议),提供了一个"尽力而为"(best-effort)的数据报服务,应用程序可以通过它发送最长65500 字节的个人信息。一个TCP/IP 套接字由一个互联网地址,一个端对端协议(TCP 或UDP 协议)以及一个端口号唯一确定。
【并行原理】
当一个客户端向一个已经被其他客户端占用的服务器发送连接请求时,虽然其在连接建立后即可向服务器端发送数据,服务器端在处理完已有客户端的请求前,却不会对新的客户端作出响应。
并行服务器:可以单独处理没一个连接,且不会产生干扰。并行服务器分为两种:一客户一线程和线程池。每个新线程都会消耗系统资源:创建一个线程将占用CPU周期,而且每个线程都自己的数据结构(如,栈)也要消耗系统内存。另外,当一个线程阻塞(block)时,JVM将保存其状态,选择另外一个线程运行,并在上下文转换(context switch)时恢复阻塞线程的状态。随着线程数的增加,线程将消耗越来越多的系统资源。这将最终导致系统花费更多的时间来处理上下文转换和线程管理,更少的时间来对连接进行服务。那种情况下,加入一个额外的线程实际上可能增加客户端总服务时间。
我们可以通过限制总线程数并重复使用线程来避免这个问题。与为每个连接创建一个新的线程不同,服务器在启动时创建一个由固定数量线程组成的线程池(thread pool)。当一个新的客户端连接请求传入服务器,它将交给线程池中的一个线程处理。
当该线程处理完这个客户端后,又返回线程池,并为下一次请求处理做好准备。如果连接请求到达服务器时,线程池中的所有线程都已经被占用,它们则在一个队列中等待,直到有空闲的线程可用。
四、 实验步骤
1. 语言环境
选择python3.5作为编程语言,pycharm作为编程平台,进行下面的网络程序编程。
2. 采用TCP进行数据发送的简单程序
2.1. TCP Server
可以看到,在python编程时,首先需要导入socket库,接下来通过socket函数进行欢迎套接字套接字的创建、绑定与监听,并通过accept函数实现TCP连接,tcp_server_socket为欢迎套接字,client_socket为与客户端建立的连接套接字;
数据的接受依靠recv函数,其中可以指定接受的数据大小,传输的数据需要进行编码与解码;
2.2. TCP Client
客户端比服务器端要简单,建立套接字后直接与服务器进行连接即可;
注:校园WIFI的IP地址是会在下次登陆时变化的,因此需要注意本机此时的IP;
数据的发送需要依靠发送函数,这里python提供了多种函数如sendto、sendall等等;
2.3. 程序结果
服务器端:
客户端:
握手成功所打印出来的tcp连接信息如上图所示,客户端端口为49902,客户端向服务器端发送数据以及服务器端向客户回应都没有问题。
3. 采用UDP进行数据发送的简单程序
UDP与TCP的编程类似,但UDP并没有真正的创建一个连接,是无连接的一种传输方式。
3.1. UDP Server
UDP显然没有TCP那么多的步骤,尤其是不需要listen与accept函数;
并且直接以改创建的套接字进行数据的接受与发送;
3.2. UDP Client
UDP客户端与TCP的客户端几乎相同,但是这里的connect函数的含义不同,前者表示建立TCP连接,这里仅仅是绑定目标端口。
发送数据部分的实现与TCP相同;
3.3. 程序结果
数据的发送与接受均可以正常进行。
4. 多线程\线程池的对比
4.1. 简单多线程实现Server端
在欢迎套接字的创建、绑定等过程与上述相同,这里唯一不同点就在于对于每一个建立的连接都使用一个线程来处理,处理函数为deal_work,函数功能很简单,就是进行消息的收发。
4.2. 运行结果
这里建立了三个连接,端口分别为50527、50528、50529,可以看到与三个客户端都可以进行数据的互发。
4.3. 基于线程池的多线程Server端实现
线程池非常有学习价值,因此我写了一个类来实现;
//工作函数,功能为接收数据,并将收到的小写字母转为大写字母后发回,较为简单,因此不再贴图;
可以看到,这里我在主函数中创建一个实体,并且调用类函数wait_accept函数来等待客户端的连接请求,收到请求则建立连接提交任务给空闲线程;
线程池的大小设定为了3,也就是同时只可以处理3个连接请求。
4.4. 线程池工作结果
当创建三个连接时,进程池可以很好的工作,然而当我建立第四个连接请求后,因为没有空闲的线程来进行处理,结果如下:
我的客户端可以建立起链接,然而当我发送数据时,因为没有空闲的线程进行处理,此时出现无法收到服务器需要返回的大写字母的情况;此时如果我关闭一个连接,效果如下:
此时的第四个端口为50636的连接收到返回的大写字母。
4.5. 多线程/线程池对比
首先,多线程并不会阻塞一个新连接的处理,也就是说它会一直耗费资源来进行连接的处理直到资源耗尽;随着线程数的增加,线程将消耗越来越多的系统资源。这将最终导致系统花费更多的时间来处理上下文转换和线程管理,更少的时间来对连接进行服务。
而线程池则不会有这个问题,因为线程池仅仅会在有空闲线程的时候才处理新的连接,否则阻塞。当一个新的客户端连接请求传入服务器,它将交给线程池中的一个线程处理。当该线程处理完这个客户端后,又返回线程池,并为下一次请求处理做好准备。
5. 一个简单的chat 程序,并能互传文件
程序稍微复杂,因此用一个个函数介绍的方式来进行。
首先是服务器端与客户端共有的
5.1. Chat_recv函数
该函数主要进行消息的接受,如果接受到“ft“的消息,表明对方想要进行文件的传输,那么此时进入接受方的文件接受函数:
5.2. Chat_send函数
发送函数也十分简单,这里如果我们输入的不是“ft“,那么只是普通消息,直接发送即可;否则,除了发送该标志给服务端外,还要进入文件传输的发送函数,这里文件发送成功则返回1,否则返回0需要重传。
5.3. ft_client函数
文件传输的发送程序,程序很长因此截取核心部分;
这里是文件传输的部分,文件描述符打开path路径上的文件后,每次读1024个字节进行传输,printf部分实时打印出上传的进度;
5.4. ft_server函数
该函数为文件接受函数;
首先接受发送函数中首先发送的文件信息,解析出要接受的文件大小和文件名,并且保存在以当前目录(相对路径)下的DATA_2文件中,文件名保持不变;
每次接受发送方发送的1024字节,保存在文件中;
上述四个函数在chat的两方都是需要的,是两者相同的部分,而TCP连接需要服务器端与客户端,因此TCP连接实现不同;
5.5. TCP连接
客户端:
服务器端:
为了保证双方可以互传文件与发送消息,这里我使用了两个线程,一个线程用于发送,一个用于接收,这样可以保证互不干预。
5.6. Chat结果
可以看到双方可以互发信息,并且可以互传文件。
参考代码
https://pan.baidu.com/s/1IPESNL997cqLygIjFaVNfw
提取:q5df