声明:仅个人小记
这篇文章是对我上一篇文章的增加:
增加内容: 多线程,实现的是一个并发服务器。其中,用到了一把锁(pthread_mutex_t),用到了一个信号量(信号量的值为我规定的服务器处理队列长度值),然后就是将run函数里面的内容移植到void * thread(void * argc)函数。
只是贴出源码:
效果展示(这个效果,得看清我的源码顺序过程才好理解,额不好意思。多线程并发过程是结果输出交错的感觉。)
三个源码文件:
server.c
#include "server_function.h"
int main(void)
{
int server = configure();
run(server);
return 0;
}
server_preset.h
#ifndef SERVER_PRESET_H
#define SERVER_PRESET_H
#include // 套接字
#include
#include // struct sockaddr_in 数据类型
#include // exit() 函数
#include // memset
#include // close(socket)
#include
#include
#include
#include
"#009944"> jack
sem_t runner;
pthread_mutex_t mutex;
//#define PORT 63633
#define BUFFER_SIZE 1024 // 这是接收缓冲区的大小, 发送缓冲区动态指定,动态分配
#define QUEUE 10
#define METHOD_NAME_SIZE 10 // HTTP 交互方式名字长度
#define URL_SIZE 256
char * text_header = "HTTP/1.x 200 OK\nContent-Type:text\n\n";
char * image_header = "HTTP/1.x 200 OK\nContent-Type:image\n\n";
//int text_header_length = strlen(text_header);
//int image_header_length = strlen(image_header);// error: initializer element is not constant ; gcc报错,原因是gcc要求变量值在编译是确定 比如int i= 90; 这是明确值,而strlen函数只是在运行时候执行,编译时候不会执行,所以返回值不确定。因此我改用下面两句,只是声明,赋值放在init函数中
int text_header_length;
int image_header_length; //
char ROOT[URL_SIZE]; // 只用来存放网站根目录
#endif // SERVER_PRESET_H
server_function.h
#ifndef SERVER_FUNCTION_H
#define SERVER_FUNCTION_H
#include "server_preset.h"
const char METHOD[8][10] = {
"GET","POST","PUT","DELETE","OPTIONS","HEAD","TRACE","CONNECT",
};
int configure();// 启动之前的相关配置,返回配置好的socket描述符号
void run(int server); // 服务器正式运行
void *thread(void * arg);
short numberizeMethod(char method[]);// 将交互方式数字化
void sendTextfile(int client, char * url);// 给定套接字client,给这个client发送文本文件
void sendImagefile(int client, char * url); // 给定套接字client,给这个client发送图片文件
int configure()// 启动之前的相关配置
{
if (sem_init(&runner,0,QUEUE) != 0) {
perror("semaphore init error");
exit(-1);
}
if (pthread_mutex_init(&mutex,NULL) != 0) {
perror("mutex init error");
exit(-1);
}
text_header_length = strlen(text_header);
image_header_length = strlen(image_header);
FILE * fp = fopen("INIT","r");
if (!fp){ perror("INIT file open fail"); exit(-1);}
//while(!feof(fp)) {// 这里不使用循环纯粹是简化对INIT的设置,以及代码简化
fgets(ROOT,256,fp);// 换行符也被读了进去
int t = strlen(ROOT);
ROOT[t-1] = '\0';
printf("%s",ROOT);
//}
// TODO: 这里对INIT文件的使用以及约定还没有设计好
fclose(fp);
int PORT;
printf("input the port: \n");
scanf("%d",&PORT);
int server = socket(AF_INET,SOCK_STREAM,0);// 创建socket套接字
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定本地地址 0.0.0.0 这样我们可以再本机上通过 127.0.0.1:63633 或者 192.168.1.2:63633(局域网) 或者 120.27.125.158:63633 (公网) 来访问,意思就是绑定所有本机的ip
if (bind(server,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1) {
perror("bind error perror");// 打印出编译器提供的报错信息
printf("bind error");
exit(-1);
}
if (listen(server,QUEUE) == -1) {// 设置侦听,其实不是真的侦听
perror("listen\n");
exit(-1);
}
return server;
}
void run(int server)// 服务器正式运行
{
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
int client;
int client_temp;
int len;
while(1) {
printf("listen...\n");
client = accept(server,(struct sockaddr*)&client_addr,&length);// 实行真正的侦听,对套接字server进行侦听(所谓侦听就是检测server这个套接字,socket的信息我们已经指定,一旦有对这个socket发送数据,我们将数据源的识别信息(也就是发送方的socket信息)用本地变量client_addr记录下来,并且将client_addr和本地的client变量绑定(所以accept需要第三个变量length),然后脱离侦听状态)
if (client < 0) { // 如果accept失败, client 将返回 -1 表示出错
perror("clientect"); // 打印连接出错信息
continue;
}
char* ipBuf = (char*)malloc(16);
memset(ipBuf,0,16);
inet_ntop(client_addr.sin_family,&client_addr.sin_addr,ipBuf,16);
ipBuf[15] = '\0';
printf("ip : %s : %d\n",ipBuf,ntohs(client_addr.sin_port));
free(ipBuf);
pthread_mutex_lock(&mutex);// 上锁,对client_temp 的保护
client_temp = client;// 引入client_temp的原因: client 专门用来 存放accept函数的返回值,一旦有访问,立刻扔入一个线程,client可能会立即被下一个访问者导致accept阻塞解除,client的值即刻被重置,而我有不能针对client加锁,因为client是和accept绑定在一起的。这就相当于阻塞了accet函数了,那又变成了单线程的处理。
pthread_t id;
pthread_create(&id,NULL,thread,(void *)&client_temp);
printf("client file descriptor : %d\n",client);
}
close(server);
return ;
}
void * thread(void * arg)
{
int client = (int)*((int *)(arg));
printf("thread client = %d\n",client);
pthread_mutex_unlock(&mutex);// 在这个地方对client_temp 进行解锁
char url[URL_SIZE]; // 用来记录客户端请求的url
char buffer[BUFFER_SIZE]; // 缓冲区设定
char method[METHOD_NAME_SIZE]; // 用来存放 http协议中客户端与服务端的交互方式 通常: GET POST PUT DELETE 查改增删(好像一定是大写) 一共八种,详细查阅同目录下 相关资料.txt 文件
memset(buffer,0,BUFFER_SIZE);// 对缓冲数据全部置零处理
sem_wait(&runner);
//while((len = recv(client,buffer,BUFFER_SIZE,0)) > 0) {// 将来自client的数据写入缓冲区buffer ,并且最大写入量就是BUFFER_SIZE,返回真实的写入数据量
int len;
len = recv(client,buffer,BUFFER_SIZE,0);
buffer[1023] = '\0';
printf("receive data len: %d \n",len);
int j = 0;
while(buffer[j] != ' ') {
method[j] = buffer[j];
j ++;
}
method[j] = '\0'; // 构成字符串
j ++;
int t = 0;
while(buffer[j+t] != ' '){
url[t] = buffer[j+t];
t++;
}
url[t] = '\0';
switch(numberizeMethod(method)){
case 0: printf("it is method GET \n");
sendTextfile(client,url);
break;
//TODO: 暂时只是实现GET方式
default : break;
}
printf("method isss : %s the number is : %d\n",method,numberizeMethod(method));
printf("url is : %s\n",url);
//printf("\n\ninfo:\n%s\n",buffer);
memset(buffer,0,BUFFER_SIZE); //break;
close(client);
sem_post(&runner);
}
short numberizeMethod(char method[])// 将交互方式数字化
{
int i;
//printf("it is numberizeMethod method is %s\n",method);
for (i = 0; i < 8; i ++) {
if (!strcmp(method,METHOD[i]))
return i;
}
return -1;
}
void sendTextfile(int client, char * url)// 给定套接字client,给这个client发送文本文件
{
//printf("\nsendTextfile()\n");
char root[256];
strcpy(root,ROOT);
if (strcmp(url,"/") == 0) {
strcpy(url,"/index.html");
}
strcat(root,url);
//printf("the final url is %s\n",root);
FILE * fp = fopen(root,"rb");
if (!fp) {
//TODO 这里应该返回一个 404 NOT FOUND 页面给用户
printf("url is %s\n",root);
perror("sendTextfile file open fail ");
return ;
}
fseek(fp,0,SEEK_END);
int fileSize = ftell(fp);
rewind(fp);
char * send_buffer = (char*)malloc(sizeof(char)*(fileSize+text_header_length));
memcpy(send_buffer,text_header,text_header_length); // 按照http的发送协议数据格式,加头
fread(send_buffer+text_header_length,sizeof(char),fileSize,fp);
if (send(client,send_buffer,text_header_length+fileSize,0) <= 0) {
perror("Send Failed ");
return ;
}
//printf("send_buffer start\n");
//for (int i = 0; i < fileSize+text_header_length; i ++) {
//printf("%c",send_buffer[i]);
//}
//printf("send_buffer stop\n");
free(send_buffer);
fclose(fp);
// printf("exit sendtextfile\n\n");
return ;
}
void sendImagefile(int client, char * url) // 给定套接字client,给这个client发送图片文件
{
char root[256];
strcpy(root,ROOT);
strcat(root,url);
printf("the final url is %s\n",root);
FILE * fp = fopen(root,"rb");
if (!fp) {
//TODO 这里应该返回一个 404 NOT FOUND 页面给用户
printf("url is %s\n",root);
perror("sendTextfile file open fail ");
return ;
}
fseek(fp,0,SEEK_END);
int fileSize = ftell(fp);
rewind(fp);
char * send_buffer = (char*)malloc(sizeof(char)*(fileSize+image_header_length));
memcpy(send_buffer,image_header,image_header_length); // 按照http的发送协议数据格式,加头
fread(send_buffer+image_header_length,sizeof(char),fileSize,fp);
if (send(client,send_buffer,image_header_length+fileSize,0) <= 0) {
perror("Send Failed ");
return ;
}
printf("send_buffer image start\n");
for (int i = 0; i < fileSize+image_header_length; i ++) {
printf("%c",send_buffer[i]);
}
printf("send_buffer image stop\n");
free(send_buffer);
fclose(fp);
printf("exit sendimagefile\n");
return ;
}
#endif // SERVER_FUNCTION)H
(Markdown 不太会用。。。)
2016-09-11 11:22:02 By Jack Lu