嵌入式linux的网络编程(6)--多线程文件服务器

2019-07-12 15:36发布

嵌入式linux的网络编程(6)--多线程文件服务器

1.简介

本文介绍一个多线程文件服务器的例子,服务器根据客户端的请求,将其所要求的文件发给客户端.采用多线程工作,可以同时为多个客户端提供服务.这样的需求情景在很多实际应用中都是存在. 整个服务器端程序由四个文件构成,分别为:fileserver.c,fileclient.c,rw.h和rw.c.其中fileserver.c为多线程服务器的主程序,fileclient.c是测试用的客户端程序,rw.h和rw.c主要提供了大数据量收发的功能函数.

2.文件服务器的主程序

在之前的文章中,我们曾讨论过多线程程序设计的问题,而且还与多进程的程序做过对比,得出在嵌入式系统中,应该优先考虑使用多线程来实现多任务处理.虽然采取创建子进程的方式来实现服务器同时对多个客户端服务的做法是最为常见的,但考虑嵌入式系统的实际,我们还是讨论多线程的实现方法.其实基本原理是一样的,就是为每个与客户端的连接创建一个处理子程序. 下面我们列出服务器主程序的代码./**************************************************************************************/ /*文件:fileserver.c */ /*简介:多线程文件服务器主程序。 */ /*************************************************************************************/ #include #include #include #include #include #include #include #include #include #include "rw.h" #define BACKLOG 10 /*工作线程函数*/ void *deal_request(void * arg) { int connfd = *((int *)arg); char file_name[MAX_LEN]; /*分离模式,线程工作完毕就清理资源退出*/ pthread_detach(pthread_self()); /*处理客户端请求*/ if(read_cmd(connfd,file_name,sizeof(file_name))==-1) { printf("read file name error! "); } else { /*发送请求的文件数据*/ if(send_file(connfd,file_name)==-1) { printf("Send file error "); } } /*对应于主程序中的malloc()函数*/ free(arg); close(connfd); pthread_exit(NULL); } int main(int argc, char* argv[]) { int sockfd; int *connfd; struct sockaddr_in servaddr; struct sockaddr_in cliaddr; struct sockaddr_in tempaddr; socklen_t templen; socklen_t clilen; /*建立TCP套接字*/ if((sockfd = socket(AF_INET, SOCK_STREAM,0)) == -1) { perror("socket"); exit(1); } bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = 0; /*随机的端口号*/ /*绑定TCP套接字*/ if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) == -1) { perror("bind"); exit(1); } templen = sizeof(struct sockaddr); /*获得套接字的本地地址*/ if (getsockname(sockfd, (struct sockaddr *)&tempaddr,&templen) == -1) { perror("getsockname"); exit(1); } printf("Server is listening on port %d ", ntohs(tempaddr.sin_port)); /*开始监听端口*/ if (listen(sockfd,BACKLOG) == -1) { perror("listen"); exit(1); } for(;;) { pthread_t th; clilen = sizeof(cliaddr); /*通信套接字,一定需要动态分配,以区别不同的线程*/ connfd = (int *)malloc(sizeof(int)); *connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&clilen); if(*connfd == -1) { perror("accept"); continue; } /*为每一个与客户端的连接建立一个线程来处理其请求*/ if(pthread_create(&th,NULL,deal_request,connfd)!=0) { perror("pthread_create"); break; } } return 0; } 接下来我们对这个程序进行详细的分析

3.动态分析监听端口

出于某种需求,服务器的监听端口需要动态的分配,getsockname()函数可以实现该功能.getsockname()函数的作用是获得指定套接字的本地地址,其原型为:#include int getsockname (int sockfd, struct sockaddr *addr, int *addrlen);其参数的含义如下:
  • sockfd:当前套接字的标示符;
  • addr:指向一个套接字地址结构,返回的本地地址信息保存在这个结构体中;
  • addrlen:表示addr所占的内存大小.
在绑定一个通配IP地址(INADDR_ANY)的TCP服务器上,通过调用该函数可以获得本地的IP地址和端口等地址信息.在上面的程序中,由于服务器的监听端口是随机的(为0),所以为了让客户端知道服务器的端口号,因此使用该函数以获得实际分配的端口号,这样就实现了动态分配监听端口的功能了.

4.多线程服务器的实现

现在我们具体来分析这段代码.实际上大部分的代码都与前面博客中的例子是相似的,一直到for循环的代码才有了比较明显的区别.所以我们主要分析for循环和线程处理函数的内容.为了便于叙述,这里将for循环单独列出,并标上行号: