同步和异步,阻塞和非阻塞的关系之代码实现异步http客户端

上一章讲解一些简单笔记

https://blog.csdn.net/jenie/article/details/106267740

今天来一个实例如何实现异步的http请求:按以下四步走

1 init_context  

  a.pthread_create

  b.epoll_create

2 uninit_context

  a.pthread_cancel

  b.close

3 commit()

  a.准备socket

  b connect

  c.protocol   #  dns/http/redis/mysql

  d.send

  e.epoll_ctl(add,sockfd);

4 callback()

   a.epoll_wait();

   b.recv()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
 
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>       /* close */
#include <netdb.h> 
 
#include <sys/epoll.h>
#include <pthread.h>
 
 
#define HTTP_VERSION    "HTTP/1.1"
#define USER_AGENT      "User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2\r\n"
#define ENCODE_TYPE     "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
#define CONNECTION_TYPE "Connection: close\r\n"
 
 
 
#define BUFFER_SIZE     4096
 
 
char *host_to_ip(const char *hostname) {
 
    struct hostent *host_entry = gethostbyname(hostname);
    if (host_entry) {
        return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);
    } else {
        return NULL;
    }
}
 
int http_create_socket( char *ip) {
 
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
    struct sockaddr_in sin = {0};
    sin.sin_addr.s_addr = inet_addr(ip);
    sin.sin_port = htons(80);
    sin.sin_family = AF_INET;
 
    if (-1 == connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
        return -1;
    }
 
    fcntl(sockfd, F_SETFL, O_NONBLOCK);
 
    return sockfd;
 
}
 
 
char *http_send_request(int sockfd, const char *hostname, const char *resource) {
 
    char buffer[BUFFER_SIZE] = {0};
     
    int len = sprintf(buffer,"GET %s %s\r\nHost: %s\r\n%s\r\n\r\n",
         resource, HTTP_VERSION,hostname,CONNECTION_TYPE);
    printf("==33333333333333333333333==\n");
    printf("request buffer:%s\n", buffer);
    send(sockfd, buffer, strlen(buffer), 0);
    printf("==44444444444444444444444==\n");
 
    struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;
 
    fd_set fdread;
    FD_ZERO(&fdread);
    FD_SET(sockfd, &fdread);
 
    char *result = malloc(sizeof(int));
    result[0] = '\0';
 
    while (1) {
 
        int selection = select(sockfd+1, &fdread, NULL, NULL, &tv);
        if (!selection || !(FD_ISSET(sockfd, &fdread))) {
            break;
        } else {
            len = recv(sockfd, buffer, BUFFER_SIZE, 0);
            if (len == 0) break;
 
            result = realloc(result, (strlen(result) + len + 1) * sizeof(char));
            strncat(result, buffer, len);
        }
    }
 
    return result;
 
}
 
 
int http_client_commit(const char *hostname, const char *resource) {
    char *ip = host_to_ip(hostname);
 
    int sockfd = http_create_socket(ip);
 
    char *content =  http_send_request(sockfd, hostname, resource);
    if (content == NULL) {
        printf("have no data\n");
    }
 
    puts(content);
    close(sockfd);
    free(content);

    return 0;
}
 
 
#define ASYNC_CLIENT_NUM        1024
#define HOSTNAME_LENGTH         128
 
typedef void (*async_result_cb)(const char *hostname, const char *result);
 
 
struct ep_arg {
    int sockfd;
    char hostname[HOSTNAME_LENGTH];
    async_result_cb cb;
};
 
 
struct async_context {
    int epfd;
    pthread_t thread_id;
};
 
struct http_request {
    char *hostname;
    char *resource;
};
 
 
int http_async_client_commit(struct async_context *ctx, const char *hostname, const char *resource, async_result_cb cb) {

    char *ip = host_to_ip(hostname);
    if (ip == NULL) return -1;
     
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
    struct sockaddr_in sin = {0};
    sin.sin_addr.s_addr = inet_addr(ip);
    sin.sin_port = htons(80);
    sin.sin_family = AF_INET;
 
    if (-1 == connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
        return -1;
    }
 
    fcntl(sockfd, F_SETFL, O_NONBLOCK);
 
    char buffer[BUFFER_SIZE] = {0};
     
    int len = sprintf(buffer,"GET %s %s\r\nHost: %s\r\n%s\r\n\r\n",
         resource, HTTP_VERSION,hostname,CONNECTION_TYPE);
 
    printf("request buffer:%s\n", buffer);
    int slen = send(sockfd, buffer, strlen(buffer), 0);
 
     
    struct ep_arg *eparg = (struct ep_arg*)calloc(1, sizeof(struct ep_arg));
    if (eparg == NULL) return -1;
    eparg->sockfd = sockfd;
    eparg->cb = cb;
 
    struct epoll_event ev;
    ev.data.ptr = eparg;
    ev.events = EPOLLIN;
 
    int ret = epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, sockfd, &ev); 
 
    return ret;
 
}
 
static void *http_async_client_callback(void *arg) {
 
    struct async_context *ctx = (struct async_context*)arg;
    int epfd = ctx->epfd;
 
    while (1) {
 
        struct epoll_event events[ASYNC_CLIENT_NUM] = {0};
 
        int nready = epoll_wait(epfd, events, ASYNC_CLIENT_NUM, -1);
        if (nready < 0) {
            if (errno == EINTR || errno == EAGAIN) {
                continue;
            } else {
                break;
            }
        } else if (nready == 0) {
            continue;
        }
 
        printf("nready:%d\n", nready);
        int i = 0;
        for (i = 0;i < nready;i ++) {
 
            struct ep_arg *data = (struct ep_arg*)events[i].data.ptr;
            int sockfd = data->sockfd;
             
            char buffer[BUFFER_SIZE] = {0};
            struct sockaddr_in addr;
            size_t addr_len = sizeof(struct sockaddr_in);
            int n = recv(sockfd, buffer, BUFFER_SIZE, 0);
 
            data->cb(data->hostname, buffer); //call cb
             
            int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
            //printf("epoll_ctl DEL --> sockfd:%d\n", sockfd);
 
            close(sockfd); /
 
            free(data);
 
        }
         
    }
 
}
 
struct async_context *http_async_client_init(void) {
 
    int epfd = epoll_create(1); // 
    if (epfd < 0) return NULL;
 
    struct async_context *ctx = calloc(1, sizeof(struct async_context));
    if (ctx == NULL) {
        close(epfd);
        return NULL;
    }
    ctx->epfd = epfd;
 
    int ret = pthread_create(&ctx->thread_id, NULL, http_async_client_callback, ctx);
    if (ret) {
        perror("pthread_create");
        return NULL;
    }
    usleep(1); 
 
    return ctx;
 
}
 
int http_async_client_uninit(struct async_context *ctx) {
 
    close(ctx->epfd);
    pthread_cancel(ctx->thread_id);

    return 0;
}
 
struct http_request reqs[] = {
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=beijing&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=changsha&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=shenzhen&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=shanghai&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=tianjin&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=wuhan&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=hefei&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=hangzhou&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=nanjing&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=jinan&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=taiyuan&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=wuxi&language=zh-Hans&unit=c" },
    {"api.seniverse.com", "/v3/weather/now.json?key=0pyd8z7jouficcil&location=suzhou&language=zh-Hans&unit=c" },
};
 
 
static void http_async_client_result_callback(const char *hostname, const char *result) {
     
    printf("hostname:%s, result:%s\n\n\n\n", hostname, result);

}
 
 
 
int main(int argc, char *argv[]) {
 
#if 0
     
    int count = sizeof(reqs) / sizeof(reqs[0]);
    int i = 0;
    for (i = 0;i < count;i ++) {
        http_client_commit(reqs[i].hostname, reqs[i].resource);
    }
     
#else
 
    struct async_context *ctx = http_async_client_init();
    if (ctx == NULL) return -1;
 
    int count = sizeof(reqs) / sizeof(reqs[0]);
    int i = 0;
    for (i = 0;i < count;i ++) {
        http_async_client_commit(ctx, reqs[i].hostname, reqs[i].resource, http_async_client_result_callback);
    }
 
 
    getchar();
#endif
 
}
gcc -o async_http async_http.c  -pthread


./async_http
nready:1
hostname:, result:HTTP/1.1 200 OK
Date: Fri, 22 May 2020 11:24:44 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 263
Connection: close
X-Instance-Id: 48ea5e05-e8a6-4732-88fc-92d75c03f819
X-RateLimit-Limit-hour: 400
X-RateLimit-Remaining-hour: 390
X-Powered-By: Express
ETag: W/"107-jmbiYCctU5xHZvZwLKBQFaq+8Q4"
X-Kong-Upstream-Latency: 3
X-Kong-Proxy-Latency: 15
Via: kong/0.14.1

{"results":[{"location":{"id":"WWE0TGW4PX6N","name":"济南","country":"CN","path":"济南,济南,山东,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"晴","code":"0","temperature":"27"},"last_update":"2020-05-22T18:51:00+08:00"}]}



request buffer:GET /v3/weather/now.json?key=0pyd8z7jouficcil&location=taiyuan&language=zh-Hans&unit=c HTTP/1.1
Host: api.seniverse.com
Connection: close



request buffer:GET /v3/weather/now.json?key=0pyd8z7jouficcil&location=wuxi&language=zh-Hans&unit=c HTTP/1.1
Host: api.seniverse.com
Connection: close



nready:1
hostname:, result:HTTP/1.1 200 OK
Date: Fri, 22 May 2020 11:24:44 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 263
Connection: close
X-Instance-Id: 48ea5e05-e8a6-4732-88fc-92d75c03f819
X-RateLimit-Limit-hour: 400
X-RateLimit-Remaining-hour: 389
X-Powered-By: Express
ETag: W/"107-qIPUvgpfAqSASWxasY0cX7/kHDs"
X-Kong-Upstream-Latency: 1
X-Kong-Proxy-Latency: 25
Via: kong/0.14.1

{"results":[{"location":{"id":"WW8P3NH2TPDT","name":"太原","country":"CN","path":"太原,太原,山西,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"晴","code":"0","temperature":"29"},"last_update":"2020-05-22T18:51:00+08:00"}]}



nready:1
hostname:, result:HTTP/1.1 200 OK
Date: Fri, 22 May 2020 11:24:44 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 266
Connection: close
X-Instance-Id: 48ea5e05-e8a6-4732-88fc-92d75c03f819
X-RateLimit-Limit-hour: 400
X-RateLimit-Remaining-hour: 388
X-Powered-By: Express
ETag: W/"10a-EJbpfcFnewi3WWw2VMYKiH1hTiU"
X-Kong-Upstream-Latency: 2
X-Kong-Proxy-Latency: 9
Via: kong/0.14.1

{"results":[{"location":{"id":"WTTE97PU94T9","name":"无锡","country":"CN","path":"无锡,无锡,江苏,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"多云","code":"4","temperature":"25"},"last_update":"2020-05-22T18:51:00+08:00"}]}



request buffer:GET /v3/weather/now.json?key=0pyd8z7jouficcil&location=suzhou&language=zh-Hans&unit=c HTTP/1.1
Host: api.seniverse.com
Connection: close



nready:1
hostname:, result:HTTP/1.1 200 OK
Date: Fri, 22 May 2020 11:24:44 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 266
Connection: close
X-Instance-Id: 48ea5e05-e8a6-4732-88fc-92d75c03f819
X-RateLimit-Limit-hour: 400
X-RateLimit-Remaining-hour: 387
X-Powered-By: Express
ETag: W/"10a-gvE2bZhbXqxBpE6pM9cXpdtHZO4"
X-Kong-Upstream-Latency: 4
X-Kong-Proxy-Latency: 9
Via: kong/0.14.1

{"results":[{"location":{"id":"WTTDPCGXTWUS","name":"苏州","country":"CN","path":"苏州,苏州,江苏,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"多云","code":"4","temperature":"23"},"last_update":"2020-05-22T18:51:00+08:00"}]}

代理自己简单走读一下,就很容易理解。

大家看一下这句代码很有意思,为什么要这么做? 

其实目的就是主线程和子线程的关系。如果创建子线程,是主线程跑还是子线程跑?

    usleep(1); 

涉及到操作系统原理。通常情况是主线程先跑,因为创建的子线程是放到操作系统调试的就绪队列,等到下个时间片处理。

但有一种情况,刚好这时主线程要时间片到了,这个时候子线程在跑。为了避免问题,特别是在写服务器的时候(今天这个程序是客户端),需要让子线程跑,这样的话,停那么一点点,让子线程先跑。因为加上了 usleep(1).

另外还有一点,申请资源时,资料要如何free,一发时申请,一接收时再释放资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值