共享参数的多线程编程课程设计_第1页
共享参数的多线程编程课程设计_第2页
共享参数的多线程编程课程设计_第3页
共享参数的多线程编程课程设计_第4页
免费预览已结束,剩余21页可下载查看

付费下载

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、共享参数的多线程编程课程设计哈尔滨理工大学成绩 :题 目 :共享参数的多线程编程班 级:姓名:学号:指导教师 :同组成员 :2011年 07月 06日一 . 设计目的1. 掌握 Tcp 客户 / 服务器的连接及并发服务器2. 掌握有关多线程程序的编写方法3. 掌握多线程并发服务器的实现二 . 实验环境Ubuntu 11.4 系统, GCC编译器三 . 实现方法和过程1. 客户 / 服务器模型In ternet 中的各种服务已广泛地使用客户机 ? 服务器模型 , 如目录服务、文件服务、地址解析等 . 首先由客户发出请求 , 服务器响应请求 . 这个过程看似简单 , 但是客户与服务器之间进行通信

2、, 必须建立一个全相关 . 客户的本地地址和本地端口都可以知道,服务器的地址是全局唯一的,也可以知道.但服务器的端口是由服务器主机独立分配的,客户进程无法知道.那么连接过程是怎样建立的? 原来TCP? IP协议规定,将端口分为保留端口和自由端口.其中保留端口只占少数,以全局方式分配.每一标准服务器都拥有一个全局公认的端口,如FTP ( F ile Tran sferP ro toco l)端口号为21 , HTTP (Hyper Tex t T ran sfer P rotoco l)为80 .不同机器的同一服务器的端口号相同,因此客户进程可以与WWW服务器的公认端口80建立连接 ,进行超文本

3、传输. 其连接时序图如图1所示.图1说明 ,服务器必须首先启动,建立本地半相关,调用accep t () ,进入阻塞等待状态后 ,方能接收客户请求 ,建立连接。.2. socket套接字技术2.1 套接字概述套接字 ( socket)是网络在传输层上提供给应用程序的接口之一, 其目的主要是网络进程通信。而网间网进程的首要问题是进程标识。在同一主机上, 可以用进程号唯一来标识进程。但在网络环境中, 各主机独立分配的进程号是不能作为进程标识的 , 所以网间网进程标识需要全网唯一的主机地址来参与。为了在主机范围内唯一标识进程 , Socket采用比进程号更低级的端口号作为进程标识。使用套接字技术 ,

4、 有面向连接和无连接两种方式。面向连接方式采用的是TCP 协议 , 能提供可靠的通讯流服务并能保持数据的发送顺序; 无连接的方式采用的高层协议是UDP,它不像 TCP 协议那样维护一个连接, 而是把目标 IP 地址包含在数据包内一起发送 , 不能保证数据传输的有序性和可靠性。本设计用面向连接的套接字编程技术。2.2 基本套接字函数1) 创建socket函数Int socket(int domain,int type,int protocol)该调用有三个参数domaintype protocol参数domain指定协议地址簇type为通信类型protocol指明socket所用的协议对于 TC

5、P 而言 取值分别为 PF_INET SOCK_STREAM 0 2)绑定套接字与地址信息int bind(int s,const struct sockaddr *name,int namelen)该函数将套接字地址本地主机地址和端口地址与所创建的套接字联系起来3) 监听连接int listen(int sockfd,int backlog)该函数有两个参数sockfd是调用socket()返回的套接字文件描述符backlog表示队列中允许的最大连接数目4)建立套接字连接int connect(int sockfd,struct sockaddr *serv_addr,int addrlen

6、)该函数有三个参数sockfd是调用socket()返回的套接字文件描述符, serv_addr是保存着目的地端口和IP地址的数据结构struct sockaddr addrlen为 struct sockaddr的长度5) 接受请求int accept (int sockfd,struct sockaddr *addr, int *addrlen)当服务器收到来自客户端的连接请求时调用该函数接受请求6) 数据传输int send(int sockfd, const void *msg, int len, int flags)intrecv(int sockfd, void*buf, int

7、len, unsigned int flags这两个函数用于流式套接字的通信send()函数返回实际发送的数据长度若小于len则说明数据没有全部发送需要进行再次发送剩余的部分recv()函数返回实际接受的数据长度sockfd是你想发送数据的套接字描述符msg是指向你想发送的数据的指针len是数据的长度flags通常为0,buf是接受的信息缓冲区7) 关闭套接字int close(int sockfd)该函数用于关闭套接字连接sockfd是套接字文件描述符 结束客户进程和服务进程在sockfd上的通信2.3. Tcp客户 / 服务器连接建立服务器客户SYN JConnect 调用在未完成队列建立

8、条目SYN K, ackJ+1Connect 返回ack K+1该条目从未完成队列移至已完成队列, accept 阻塞图 2.TCP 客户 / 服务器建立连接图3. 多线程并发服务器的实现3.1 多线程并发技术服务器分为重复服务器和并发服务器 . 前者面向短时间能处理完的请求 , 由服务器亲自处理各请求后者面向不定长时间才能处理完的请求 , 对每个请求由服务器的线程处理 . 所谓线程是操作系统的基本调度单位 , 又称为轻量级进程 . 因为生成和管理它们是相当简单的操作 . 线程被用来建立请求驱动的服务程序 , 每个客户一个线程 , 多个线程可以并发执行 .线程使得服务器进程不随客户数的增加而线

9、性增加 , 可减少服务器进程的压力 , 降低开销 , 充分利用 CPU 的资源 . 其结构如图 2 所示 . 在某一瞬间 , 由同一服务器进程所产生的多个并发线程对多个客户的并发请求采取分而治之的措施 , 从而解决了并发请求的问题 . 各线程既可以独立操作 , 又可以协同作业 , 降低了服务器的复杂度 .图 并发服务器模型3.2 多线程并发服务器基本框架并发服务器同时可以处理多个客户的请求,其模型如下:并发服务器面向不定长时间才能处理完的请求 , 对每个请求由服务器的线程处理。线程被用来建立请,求驱动的服务程序 , 每个客户一个线程 , 多个线程可以并发执行。线程使得服务器的进程数目并不随客户

10、数目的增加而线性增加, 这样减少了服务器进程的压力, 降低了开销。并发服务器的结构如图1 所示 , 在某一个时刻 , 由同一服务器进程所产生的多个并发线程同时对多个客户的并发请求进行处理,从而解决了并发请求问题。各个线程既能独立操作, 又可以协同作业, 实现了简单而高效的服务器结构。在并发系统中 , 互斥锁 (Mutual exclusi on)是最基本的同步方式, 用于保护临界区的数据。多个线程或进程共享一个互斥锁的时候, 在任何时刻这个互斥锁只能被一个线程或进程所获得, 其他线程或进程试图获得被其他线程或进程锁定的互斥锁都将被阻塞或返回一个 EBUSY错误 , 这样的机制能保证在任何时刻只

11、有一个线程或进程执行临界区中的指令 , 从而达到保护共享数据的目的。互斥锁有一个明显的缺点 , 那就是它只有两种状态 : 锁定和非锁定。而条件变量通过允许线程阻塞和等到另一个线程发送信号的方法弥补了互斥锁的不足。条件变量在使用时一般都要和一个互斥锁进行关联。本文中通过互斥锁和条件变量的协调使用来解决并发服务器中的同步通信问题。并发服务器的工作流程按照三个步骤建立:1). 建立套接字并在某一约定端口上等待接收客户请求;2). 当接收到来自客户端的服务请求后 : 建立一新线程来处理 , 同时主线程继续等待其他客户连接。当新线程处理完成后 , 关闭新线程与客户的通信链路并终止新线程。3). 关闭服务

12、器。图 5面向连接的并发服务器算法基于多线程的 Linux系统下并发服务器能够同时并有效地运行多个任务。采用这种并发系统结构 , 既增加了服务器程序的功能, 又提高了其性能。只有一个处理器时 , 多个线程不能真正地并行执行。然而 , 经过固定的时间间隔或当一个线程处于等待状态时 , 就可从一个线程切换到另一线程 , 实现多个线程重叠执行。在有多个 CPU的计算机上 , 如果底层系统支持 , 则多个线程可真正地被并行执行, 服务的并行性潜力得以开发。3.3线程编程模块线程概述线程是进程内的独立执行实体和调度单元,又称为“轻量级”进程(lightwight process);创建线程比进程快101

13、00 倍。一个进程内的所有线程共享相同的内存空间、全局变量等信息( 这种机制又带来了同步问题) 。而且它们还共享以下信息 :共享信息私有信息进程指令线程 ID大多数数据寄存器集合 ( 包括程序计数器和栈指针) 打开的文件描述字栈( 用于存放局部变量 )信号处理程序和信号处置error当前工作目录信号掩码用户 ID 和组 ID 优先级主要线程函数调用1) 子线程的创建int pthread_create (pthread_t *thread, const pthread_attr_t*attr,void*(*start_routine) (void *), void *arg)调用此函数可以创建

14、一个新的线程 新线程创建后执行start_routine函数 其中参数attr是线程属性arg是向start_routine函数传递的参数当成功创建一个新线程时系统会自动为新线程分配一个线程ID号 并通过pthread返回给调用者2) 等待线程结束int pthread_join(pthread_t thread, void *value_ptr)该函数挂起当前进程直到所等待的线程结束3) 线程退出void pthread_exit(void *value_ptr)该函数用于终止当前线程 并返回状态值4. 实例实现下面我们通过一个具体的实例来说明多线程并发服务器的具体实现:服务器端 :循环等候

15、客户连接请求一旦有客户连接请求开启一个子线程接受并处理客户请求连接成功后向客户发送欢迎信息,接着接受来自客户的信息,然后将客户信息反转后再返回给客户端主线程继续等待其他客户请求服务器具有同时处理多个用户的能力 ;客户端 : 首先与服务器建立连接,接着接收用户输入的客户名字,将名字发给服务器 ; 然后向服务器发送数据,进行交互, 接受服务器的反馈信息并显示 之后继续等待用户输入直至用户输入 ctrl+D 结束通信 客户端接到输入 ctrl+D 后 客户端关闭连接并退出。服务器端程序如下 :/ 所需头文件#include <pthread.h> #include <unistd.

16、h>#include <stdio.h>#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h>#include<string.h>#include<stdlib.h>/ 定义端口号,最大允许连接的数量和缓冲区大小#define PORT 1234#define BACKLOG 2#define MAXCHARSIZE 1000

17、/ 声明客户处理函数,用来处理客户的请求void process_client(int connectfd,struct sockaddr_in client);/ 声明线程执行函数void *start_routine(void *arg);/ 声明 savedata() 函数,用于存储客户数据void savedata(char *recvbuf,int len,char *cli_data);/ 定义 ARG结构,用于主线程想新线程传递参数 struct ARG int connfd;struct sockaddr_in client; ;struct ARG *arg;int main

18、(void)int listenfd,connectfd;struct sockaddr_in server,client;int sin_size;int opt=SO_REUSEADDR;pthread_t tid;sin_size=sizeof(struct sockaddr_in);/ 产生 TCP套接字if(listenfd=socket(AF_INET,SOCK_STREAM,0)=-1)perror("Create socket failed.");exit(-1);/ 设置套接字选项为SO_REUSEADDRsetsockopt(listenfd,SOL_S

19、OCKET,SO_REUSEADDR,&opt,sizeof(opt);/ 调用 bind 函数,将套接字绑定到相应的地址bzero(&server,sizeof(server);server.sin_family=AF_INET;server.sin_port=htons(PORT);if(bind(listenfd,(struct sockaddr *)&server,sizeof(structsockaddr)=-1)perror("Bind error.");exit(-1);/ 监听网络连接if(listen(listenfd,BACKLO

20、G)=-1)perror("Listen error.");exit(-1);/ 主线程接受客户端的连接请求while(1)if(connectfd=accept(listenfd,(struct sockaddr*)&client,&sin_size)=-1)perror("Accept error.");exit(-1);/ 分配空间给 arg ,然后把自己的连接描述符和客户地址相信赋给argarg=(struct ARG *)malloc(sizeof(struct ARG);arg->connfd=connectfd;mem

21、cpy(void *)&arg->client,&client,sizeof(client);/ 创建新线程if(pthread_create(&tid,NULL,start_routine,(void *)arg)perror("Pthread_create error.n");exit(-1);close(listenfd);/ 定义 TSD关键字变量 keystatic pthread_key_t key;/ 定义变量 once 并赋值static pthread_once_t once = PTHREAD_ONCE_INIT;/ 定义

22、ST_DATA结构,用于存储 TSD struct DATA_THRint index;/ 定义析构函数 key_destro() ,在线程退出时该析构函数将被调用,以释放为 TSD分配的空间static void key_destroy(void *buf)free(buf);/ 定义函数,以产生 TSD关键字static void buffer_key_alloc()pthread_key_create(&key,key_destroy);/ 定义函数 savedata()void savedata(char *recvbuf,int len,char *cli_data)stru

23、ct DATA_THR *data;pthread_once(&once,buffer_key_alloc);if(data=(struct DATA_THR *)pthread_getspecific(key)=NULL)data=(struct DATA_THR *)calloc(1,sizeof(struct DATA_THR);pthread_setspecific(key,data);data->index=0;int i;for(i=0;i<len;i+) cli_datadata->index+=recvbufi;cli_datadata->ind

24、ex='0'/ 处理客户请求void process_client(int connectfd,struct sockaddr_in client) char recvbufMAXCHARSIZE; char sendbufMAXCHARSIZE;char client_nameMAXCHARSIZE;char client_data5000;int recvlen,i;printf("You get a connect from %sn",inet_ntoa(client.sin_addr);send(connectfd,"Welcome to

25、my server.n",22,0);recvlen=recv(connectfd,client_name,MAXCHARSIZE,0);if(recvlen=0)close(connectfd);printf("Client disconnected.n");return;else if(recvlen<0)close(connectfd);printf("Connect broked.n");return;client_namerecvlen='0'printf("Client name is %s.n&qu

26、ot;,client_name);bzero(recvbuf,1000);while(recvlen=recv(connectfd,recvbuf,MAXCHARSIZE,0)savedata(recvbuf,recvlen,client_data);recvbufrecvlen='0'printf("Receive from client(%s) message:%sn",client_name,recvbuf); for(i=0;i<recvlen;i+)sendbufi=recvbufrecvlen-i-1;send(connectfd,send

27、buf,strlen(sendbuf),0);bzero(recvbuf,1000);printf("Client :%s disconnected.User's data:%sn",client_name,client_data);close(connectfd);/ 实现线程的执行函数void *start_routine(void *arg)struct ARG *info;info=(struct ARG *)arg;process_client(info->connfd,info->client);free(arg);pthread_exit(

28、NULL);客户端程序 :/ 定义所需头文件#include<unistd.h>#include<sys/socket.h> #include<netinet/in.h> #include<netdb.h>#include<string.h>#include<stdlib.h>#include<stdio.h>/ 定义端口号和缓冲区的大小#define PORT 1234#define MAXDATASIZE 1000;int main(int argc ,char * argv) int fd,numbyte

29、s;char buf1000;struct hostent *he;struct sockaddr_in server;int i=1;/ 检查用户输入的格式if(argc!=2)printf("Usage:%s <IP address> n",argv0);exit(-1);/ 通过字符串形式的 IP 地址获得服务器的地址信息if(he=gethostbyname(argv1)=NULL)perror("gethostbyname error.");exit(-1);/ 产生 TCP套接字if(fd=socket(AF_INET,SOCK_

30、STREAM,0)=-1)perror("Create socket failed.");exit(1);/ 设置与之连接的服务器套接字的地址结构,并连接到该服务器bzero(&server,sizeof(server);server.sin_family=AF_INET;server.sin_port=htons(PORT);server.sin_addr=*(struct in_addr *)he->h_addr);if(connect(fd,(struct sockaddr *)&server,sizeof(struct sockaddr)=-1

31、)perror("Bind error.");exit(1);/ 接受信息if(numbytes=recv(fd,buf,1000,0)=-1)perror("recv error .");exit(1);bufnumbytes='0'printf("Server Message :%sn",buf);/ 发送信息printf("Input your name: ");scanf("%s",buf);if(numbytes=send(fd,buf,strlen(buf),0)=-1)perror("Send error.");exit(1);bzero(buf,1000);while(i)printf("

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论