加减乘除服务线程池Word文件下载.docx
- 文档编号:20494583
- 上传时间:2023-01-23
- 格式:DOCX
- 页数:23
- 大小:1.02MB
加减乘除服务线程池Word文件下载.docx
《加减乘除服务线程池Word文件下载.docx》由会员分享,可在线阅读,更多相关《加减乘除服务线程池Word文件下载.docx(23页珍藏版)》请在冰豆网上搜索。
④等待下一个请求到来,如此循环。
重复型交互的缺点是在服务器进行客户服务处理时,不能接受其他客户的请求;
而并发型交互就可以解决此问题,但需要多线程技术的支持。
由于作业要求请求与计算是多对多关系,并且有队列保存请求,所以设计采用重复型交互方式进行加减乘除服务线程池设计可以满足设计要求,并且需要分别编写服务器端和客户端的程序,服务器和客户端之间相互通信的同步关系和各自的程序流程如图2所示。
图2客户/服务器通信流程图
事实上,send()和recv()函数默认都是阻塞方式通信的,但由于本次设计要求的通信数据小于1024字节,所以为了通信方便,编程时可以把服务器端的套接字修改为非阻塞方式,这样一般不会造成数据的丢失,且可以方便客户端的并发请求得到响应。
2.1网络套接字(socket)的概念
上世纪中后期,在美国国防部高研署(DARPA)将TCP/IP的软件提供给加利尼亚大学Berkeley分校后,TCP/IP很快被集成到Unix中,同时出现了许多成熟的TCP/IP应用程序接口(API)。
这个API称为Socket接口(套接口)。
Socket在计算机中提供了一个通信端口,可以通过这个端口与任何一个具有Socket接口的计算机通信。
应用程序在网络上传输,接收的信息都通过这个Socket接口来实现。
在应用开发中就像使用文件句柄一样,可以对Socket句柄进行读、写操作。
今天,Socket接口是TCP/IP网络最为通用的API,也是在Internet上进行应用开发最为通用的API。
Linux操作系统具有良好的稳定性和出色的网络性能,因此被广泛应用于网络服务领域。
而在Linux下开发高性能的网络通信程序,是充分发挥Linux网络特性的一个关键因素。
当用电话与他人通信时,必须拿起话筒,拨叫对方的电话号码,然后等待对方的应答;
当双方进行通话的时候,就建立了一个具有两个端点的通信线路,这两个端点是:
本地的电话号码(在本地位置);
对方的电话号码(在对方的位置处)。
双方的通信与通信的两个端点和他们之间的通信线路有关。
Linux中的套接口与电话非常相似。
套接口代表通信线路中的端点,两端点之间就是数据通信网络。
套接口与电话的相似性还表现在另一方面。
当给某人打电话时,拨叫的是对方用户的电话号码。
而套接口中的网络地址就相当于电话号码。
通过在程序中指定远程套接口的地址,就可以建立从本地套接口到远端套接口的通信。
TCP/IP是计算机互连最常使用的网络通讯协议,TCP/IP的核心部分由网络操作系统的内核实现,应用程序通过编程接口来访问TCP/IP。
套接字(Socket)是介于网络应用层和传输层之间的编程接口,套接字接口提供了访问下层通信协议的大量系统调用和相应的数据结构。
在Linux中,套接字接口是应用程序访问下层的网络协议的惟一方法。
具体讲,套接字在用户级实现了两个应用程序之间的网络连接和数据交换,所以Linux中的套接字意味着网络上的连接。
套接字在TCP/IP网络模型中的地位如图3所示。
图3socket的TCP/IP模型结构图
Socket分为以下三种类型。
流式套接字(StreamSocket):
是最常用的套接字类型,文件传送协议(FTP)即使用流式套接字。
提供一个面向连接、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺序接收。
内设流量控制,避免数据流超限;
数据被看作是字节流,无长度限制。
数据报套接字(DatagramSocket):
TCP/IP协议族中的UDP协议使用此类接口,它是无连接的服务,数据通过相互独立的报文进行传输,提供了一个无连接服务。
数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。
原始数据报套接字(RawSocket):
该接口允许对较低层协议,如IP、ICMP直接访问。
常用于检验新的协议实现或访问。
2.1.Socket通信过程
基于TCP可靠连接的客户与服务器连接进程流程图如图4所示。
图4基于TCP的客户服务器模型
Socket工作过程如下:
服务器首先启动,通过调用Socket(),建立一个Socket,然后调用bind()将该Socket和本地网络地址绑定在一起,再调用listen()使Socket做好侦听的准备,并规定它的请求队列的长度,之后就调用accept()来接收连接。
客户在建立Socket后就可调用connect()和服务器建立连接。
连接一旦建立,客户机和服务器之间就可以通过调用send()和recv()来发送和接收数据。
最后,待数据传送结束后,双方调用close()关闭Socket。
2.2.Socket通信程序开发
下面详细说明Socket通信程序的开发过程:
①步骤1:
建立套接口socket()
intsockfd=socket(intdomain,inttype,intprotocol)
其中domain参数指定socket协议族,包括PF_LOACL和PF_INET,PF_LOACL表示使用本地套接口,PF_INET表示使用Internet套接口。
Type参数定义了套接口的类型,包括SOCK_STREAM和SOCK_DGRAM,SOCK_STREAM指定为流套接口,SOCK_DGRAM指定为数据报套接口。
protocol通常赋值0,意味套接口使用默认的TCP/IP协议。
socket()调用返回一个非负整型socket描述符,可以在后面的调用使用它,当其值为-1时,说明有错误发生。
②步骤2:
绑定套接口bind()
当用socket()建立套接口后,该套接口还是处于无名状态的,无名套接口就像没有号码的电话一样,别人无法向发送信息(在同一linux内核下可实现无名状态下通信)。
为了像电话分配电话号码一样,可以通过bind()为建立的套接口绑定一个名字,即分配IP地址和端口。
这一步对客户端不是必需的。
intbind(intsockfd,structsockaddr_my_addr,intaddrlen)
sockfd是一个socket描述符,my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;
addrlen常被设置为sizeof(structockaddr),如果函数调用成功,就返回0,否则就返回-1。
需要指出的是,可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号:
my_addr.sin_port=0;
系统随机选择一个未被使用的端口号:
my_addr.sin_addr.s_addr=INADDR_ANY;
填入本机IP地址,通过将my_addr.sin_port置为0,函数会自动为选择一个未占用的端口来使用。
同样,通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址。
bind()函数在成功被调用时返回0;
遇到错误时返回-1。
另外要注意的是,当调用函数时,一般不要将端口号置为小于1024的值,因为1~1024是保留端口号,可以使用大于1024中任何一个没有被占用的端口号。
③步骤3:
请求连接connect()
当客户端绑定地址后,发送请求连接信号connect()来与远端TCP服务器建立连接,一般用于客户端编程。
connect()函数原型为:
intconnect(intsockfd,structsockaddrserv_addr,intaddrlen)
sockfd是客户端的socket描述符;
serv_addr是包含目的机IP地址和端口号的指针,addrlen为结构的大小。
遇到错误时返回-1。
进行客户端程序设计无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,内核会自动选择一个未被占用的端口供客户端来使用。
④步骤4:
监听连接listen()
在服务器端程序中,当socket与某一端口捆绑以后,就需要监听该端口,以便对到达的服务请求加以处理。
intlisten(intsockfd,intbacklog)
sockfd是Socket系统调用返回的socket描述符;
backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept()它们。
backlog对队列中等待服务的请求的数目进行了限制,对于小型服务器,队列长度应该为5或是稍大一些的值,而对于网站服务器,我们就需要更大的值,比如说16或是更大。
当调用socket()函数建立套接口时,该接口默认为主动套接口,主动套接口的含义是指该套接口是用于主动发起连接的客户端套接口,一旦调用listen()函数,则套接口变为被动套接口,就是说,套接口变为被动接受连接的服务器端套接口。
Listen()函数返回值为0表示调用成功,-1表示调用出错。
⑤步骤5:
连接端口的服务请求
当某个客户端试图与服务器监听的端口连接时,该连接请求将排队等待服务器accept()它。
通过调用accept()函数为其建立一个连接,accept()函数将返回一个新的socket描述符,来供这个新连接来使用。
而服务器可以继续在以前的那个socket上监听,同时可以在新的socket描述符上进行数据发送send()和接收recv()操作。
intaccept(intsockfd,void*addr,int*addrlen)
sockfd是被监听的socket描述符,addr通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息(某台主机从某个端口发出该请求);
addrlen通常为一个指向值为sizeof(structsockaddr_in)的整型指针变量。
错误发生时返回一个-1。
accept()之前,通常将addrlen初始化为0。
⑥步骤6:
数据传输send()和recv()
send()和recv()这两个函数是用于面向连接的socket上进行数据传输。
它们并不是真的从网络中发送/接收数据,而是对套接口的数据缓冲区进行发送/读取,二者默认方式都是阻塞方式。
send()函数原型为:
intsend(intsockfd,constvoid*msg,intlen,intflags)
sockfd是想用来传输数据的socket描述符,msg是一个指向要发送数据(可以是字符型、整型、浮点型等)的指针,len是以字节为单位的数据的长度。
flags一般情况下置为0。
send()函数返回实际上发送出的字节数,可能会少于希望发送的数据。
所以需要对send()的返回值进行测量。
当send()返回值与len不匹配时,应该对这种情况进行处理。
recv()函数原型为:
intrecv(intsockfd,void*buf,intlen,unsignedintflags)
sockfd是接收数据的socket描述符;
buf是存放接收数据的缓冲区;
len是缓冲的长度。
flags也被置为0。
recv()返回实际上接收的字节数,或当出现错误时,返回-1。
⑦步骤7:
关闭连接close()或shutdown()
当所有的数据操作结束以后,可以调用close()或shutdown()函数来关闭套接口。
TCP连接是全双工的连接,即在同一个连接上既可以发送数据,又可以接收数据。
在调用close()函数后,套接口既不能用于读取数据,也不能用于发送数据。
而调用shutdown()函数允许指定只关闭一个方向(读或写)的通信或者关闭双向通信。
2.4多线程的概念
上述点对点通信的实现知识完成了主机进程与服务器进程之间的连接,建立连接的进程之间是一对一的联系,即主机的一个进程与服务器的一个进程之间建立的连接。
而每个进程进行通信的环节都包括了发送信息和接口信息两个任务,这两个任务通过一个端口地址发送和接收。
对于多个并发的任务需要创建多个线程去实现。
使用一个进程去完成发送信息是没有问题的,因为发送总是主动的;
而使用同一个进程再去完成接受信息去不一定会成功,因为接受信息是被动的,所以当没有信息可以接收时,该进程就会被阻塞,从而导致发送任务也一起被阻塞。
同一个端口的发送和接收是两个并发任务,应该由两个不同的任务去分别完成信息的发送和接收。
这样,当接收信息任务因没有信息而被阻塞时,不至于影响发送任务的执行。
那么,发送和接收两个任务是使用两个进程还是两个进程去完成呢?
在网络通信中,端口地址是以进程为单位进程分配的,而一个进程与外界的消息发送与接收必须通过分配给它的同一个端口进行。
因此,不能通过创建进程方式来解决上诉问题,因为两个进程会分别对应两个不同的端口,而发送和接收必须使用同一端口。
线程不是资源分配的单位,所以如果使用两个线程不会对线程分配新的端口。
因此,本实验需要使用两个线程去分别完成发送和接收信息的任务,这两个线程共享其进程拥有的统一个端口地址。
由于创建进程的进程本身会作为一个线程来调度,所以只需要再创建一个线程专门负责接收信息就可以了。
因此,对于从每个客户端发来的请求,服务器端都要创建相应的线程去接收并处理;
同理,对于客户端而言,也要创建一个线程去读取服务器端发来的信息。
3.程序设计实现
①传输数据结构形式
为保证计算数据的正确发送给服务器,需要首先设计传输数据结构。
具体定义如下:
typedefstructmathopt
{
intoprate;
floatvalue1;
floatvalue2;
}mopt;
其中,oprate为操作类型,取值为1、2、3、4、5、6,分别代表加、减、乘、除、帮助、退出;
value1与value2是加减乘除的第一和第二操作数。
②线程状态标志数据结构
线程状态主要有请求队列、当前处理的套接字、线程工作状态标志位等,具体结构定义如下:
typedefstructthreadatom
queue<
int>
waitque;
//请求队列
intcurrentsocket;
//当前处理的请求套接字
intflag;
//工作标记值
}tatom;
其中,请求队列waitque中保存的是请求客户端的套接字;
currentsocket是线程当前处理的请求套接字,主要用于线程池状态的显示;
flag是线程工作状态标志位,置1表示线程正在处理客户端请求,置0表示线程处于空闲等待状态。
③服务端线程处理函数
线程处理函数是整个服务端的核心,主要负责请求数据的接收、结果计算、返回计算结果等任务。
线程处理函数的流程图如图5所示:
图5线程程序流程图
四、测试
1.程序编译
因为线程pthread的库不是linux系统的库所以在对程序编译的时候要加上-lpthread,编译用法如下:
#gccfilename–lpthread–oresultname
服务端程序server2.c和客户端程序client.c编译过程截图如图6所示:
图6server2.c和client.c编译过程
2.程序调试
在实际调试中,发现由于处理服务请求的时间非常短,所以无法明确看出保存客户端套接字的队列是否起作用,所以为了真正测试程序,在处理服务请求程序段中使用sleep()函数适当延长服务时间,方便调试队列作用。
另外,考虑到电脑屏幕有限,所以设定计算线程数为2,客户端数量上限受到listen()函数限制,设定最大为16,在实际调试中,采用一个服务器,三个客户端的形式,端口号为5678,IP地址为127.0.0.1,以下图片为测试过程截图:
图7服务端启动后界面
图8添加一个客户端后界面
图9添加三个客户端后界面
图10处理计算请求后界面
图11客户端退出界面
五、对本课程或本作业的建议和意见
通过本作业的设计学习,我更加熟悉了linux下的网络套接字和线程编程过程,对于以后的学习打下了很好的基础。
关于对课程的建议,我希望以后可以更多的在机房上课,让学生在学习理论的同时,更加直观的实际操作,这样或许对于学习的知识印象更加深刻。
六、附录
1.服务端程序server2.c:
#include<
iostream>
stdlib.h>
string.h>
queue>
fcntl.h>
pthread.h>
sys/socket.h>
arpa/inet.h>
usingnamespacestd;
#defineSERVER_PORT5678//定义端口号
#defineMAX_NUM2//定义计算线程数
typedefstructmathopt//定义计算数据的数据结构
typedefstructthreadatom//定义线程状态的数据结构
//请求队列
//当前工作套接字
//工作状态标志位:
1working;
0waiting
structthreadatomthreadpoolatom[MAX_NUM];
//定义线程状态数组
pthread_tthreadid[MAX_NUM];
//定义线程ID数组
voidcleanthreadpool()//线程池初始化
inti;
for(i=0;
i<
MAX_NUM;
i++)
{
while(!
threadpoolatom[i].waitque.empty())threadpoolatom[i].waitque.pop();
//队列清空
threadpoolatom[i].currentsocket=0;
threadpoolatom[i].flag=0;
threadid[i]=0;
}
}
voidshowthreadstatus()//显示线程池里各线程的状态信息
for(i=0;
cout<
<
"
i="
i<
:
threadid["
]clientsocket["
threadpoolatom[i].currentsocket<
]flag["
;
if(threadpoolatom[i].flag>
=1)
cout<
working]waitsize["
else
waiting]waitsize["
cout<
threadpoolatom[i].waitque.size()<
'
]'
endl;
void*processthread(void*para)//线程处理函数
intindex=(int)para;
//线程标号
Enterytheprocessthread"
index<
andwaiting"
while
(1)
{
threadpoolatom[index].waitque.empty())//队列非空开始处理
{
intiDataNum;
charbuffer[1024];
inttempclient=threadpoolatom[index].waitque.front();
//取客户端套接字
fcntl(tempclient,F_SETFL,O_NONBLOCK);
//将套接字变为非阻塞
iDataNum=recv(tempclient,buffer,1024,0);
//接收计算数据
intlength=sizeof(structmathopt);
if(iDataNum<
length)//数据不完整,跳出
{
threadpoolatom[index].waitque.pop();
//将套接字弹出放到队尾
threadpoolatom[index].waitque.push(tempclient);
threadpoolatom[index].flag=0;
//工作标志位清零
continue;
}
threadpoolatom[index].cur
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 加减乘除 服务 线程
![提示](https://static.bdocx.com/images/bang_tan.gif)