简版服务器(c语言实现) (多线程)

声明:仅个人小记

这篇文章是对我上一篇文章的增加:
增加内容: 多线程,实现的是一个并发服务器。其中,用到了一把锁(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 <sys/socket.h> // 套接字
#include <stdio.h>
#include <netinet/in.h> // struct sockaddr_in 数据类型
#include <stdlib.h> // exit() 函数
#include <string.h>// memset
#include <unistd.h> // close(socket)
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <arpa/inet.h>
<font color="#009944"> jack </font>
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

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值