基于C语言的协程和Java混合编程的服务器

栏目: C · 发布时间: 5年前

内容简介:写在前面的话:笔者在学习《UDP》中的网络模型之后,已经尝试使用Java语言写过阻塞IO模型、非阻塞IO模型、IO多路复用模型以及异步IO模型。每种网络模型的特点这里就不再赘述,本文主要利用谷歌开发的GoLang语言由于Goroutine协程在web服务器上的强大性能而渐渐被人熟知,对于每一个web请求,协程服务器都会使用一个go协程去处理,从而让一个服务器可以轻松同时开启上万个协程。
  • 协程
  • JNI
  • 进程间通信

写在前面的话:

笔者在学习《UDP》中的网络模型之后,已经尝试使用 Java 语言写过阻塞IO模型、非阻塞IO模型、IO多路复用模型以及异步IO模型。每种网络模型的特点这里就不再赘述,本文主要利用 云风的协程库 和JNI技术调用笔者之前写过的解析HTTP请求的Java代码( 笔者的HTTP服务器Demo ),从而实现一个新的HTTP服务器。这个基于协程的服务器只是笔者学习新知识的一个小demo,简单地使用webbench测试的QPS结果并不理想,由于已经开学写毕业论文了,所以接下来没时间来改善该demo的性能,故在此写下该博客进行总结。

协程

谷歌开发的GoLang语言由于Goroutine协程在web服务器上的强大性能而渐渐被人熟知,对于每一个web请求,协程服务器都会使用一个 go 协程去处理,从而让一个服务器可以轻松同时开启上万个协程。

为了更好的理解协程的概念,先来看以下关于协程的例子,类似于多线程操作,在两个协程之间切换进行任务:

void foo(struct scheduler *s, int start) {
    for (int i = 0; i < 3; i++) {
        printf("coroutine %d: %d\n", coroutine_running_id(s) , start + i);
        coroutine_yield(s); //挂起协程
    }
}

void test() {
    struct scheduler * scheduler = scheduler_open();
    int id1 = coroutine_new(scheduler, foo, 0);
    int id2 = coroutine_new(scheduler, foo, 10);
    // while判断协程是否存活
    while (coroutine_status(scheduler, id1) && coroutine_status(scheduler, id2)) {
        coroutine_resume(scheduler, id1);   //  恢复协程
        coroutine_resume(scheduler, id2);
    }
}

输出:
coroutine 0: 0  
coroutine 1: 10
coroutine 0: 1
coroutine 1: 11
coroutine 0: 2
coroutine 1: 12

协程是一种用户态的轻量级线程,通过上面的例子,知道协程把正在运行的任务暂时挂起,经过后续的操作恢复该协程继续进行任务(可把协程当成能够保存局部变量的可重入函数)。

协程的优点

  1. 线程的切换需要系统内核的操作,花费时间较多,而协程的切换只在用户态空间内进行操作,花费的时间是低于线程切换的。

  2. 协程可以简单地使用全局变量进行协程间通信,对比于较为麻烦的线程同步技术。

  3. 协程的切换为运行间的协程主动让出CPU,而非是抢占式的占用。

JNI

笔者使用了 C语言 和Java代码混合编程,需要使用JNI技术,如非项目真的需要,并不建议使用JNI技术。笔者以C语言为主程序调用Java代码,例子如下:

/** C代码 */
void jni_test() {
    JavaVM *jvm;
    JNIEnv *env;
    jclass clazz;
    jmethodID method_id;

    env   = jni_init(jvm, "-Djava.class.path=/root/demo/classes");
    clazz  = load_class(env, "edu/xmu/TestJavaClass");
    if (clazz == 0) {
        printf("error: call method failed.\n");
        return NULL;
    }
    method_id = (*env)->GetStaticMethodID(env, clazz, "testMethod", "()I");
    if (method_id == 0) {
        printf("error: call method  .\n");
        return NULL;
    }
    
    jint i = ((*env)->CallStaticIntMethod(env, clazz, method_id);
    printf("get result of Java: %d.\n", (int)i);
    
    return jni_data;
}

// Java代码
package com.edu.TestJavaClass

public class TestJavaClass {
    public static int testMethod() {
        return 111;
    }
}

上面的例子只是展示一下使用C语言和Java如何进行混合编程,具体的调用过程笔者将会在后续的文章中贴出教程,有兴趣了解的可以关注一下。

进程间通信

笔者尝试在子协程和多线程使用JNI调用Java代码,二者方法均无法顺利进行。比如如下代码

// 全局变量
JavaVM *jvm;
JNIEnv *env;
jclass clazz;
jmethodID method_id;

// 主协程初始化JVM
void jni_data_init() {
    JavaVM *jvm;
    JNIEnv *env;
    jclass clazz;
    jmethodID method_id;

    env   = jni_init(jvm, "-Djava.class.path=/root/demo/classes");
    clazz  = load_class(env, "edu/xmu/networkingModel/coroutineIOComponent/CoroutineIOServer");
    if (clazz == 0) {
        printf("error: call method failed.\n");
        return;
    }
    method_id = (*env)->GetStaticMethodID(env, clazz, "test", "()I");
    if (method_id == 0) {
        printf("error: call method  .\n");
        return;
    }
}

// 子协程调用
void foo() {
    // ...
    JavaVM *jvm;
    JNIEnv *env;
    jclass clazz;
    jmethodID method_id;

    jint   y  = (*env)->CallStaticIntMethod(env, clazz, method_id);
    printf("lallalal: %d\n", (int)y);
    
    //...
}

由于主协程和子协程进行切换之后,CPU运行子协程的任务,子协程调用JVM相关的代码导致JVM运行出错。所以笔者只好使用进程间通信,使用子进程进行非阻塞的数据读取HTTP请求之后,将数据传给父进程处理得到HTTP响应之后,子进程再次从父进程中读取数据,最后返回给客户端;使用父进程读取子进程传来的请求数据,调用Java处理HTTP请求和响应的代码之后,将响应返回给子进程。

这里是为了解决代码的缺陷而使用进程间的通信,数据在进程间的一次来回传输,以及进程间数据的同步处理都会消耗CPU的时间以及IO的负担,最终造成本Demo能够提供的并发量相当有限。

if ((pid = fork()) < 0) {
        printf("fork() error.\n");
        return;
    }
    else if (pid == 0) {
        // 父进程
        char req_buf[REQUEST_BUF];
        ssize_t n_read;
        // 先从子进程中读取数据
        while ((n_read = read(pipe_fd[0], req_buf, REQUEST_BUF)) != -1) {
            // 调用Java代码处理请求
            client_fd = get_client_fd(req_buf);
            char *r_buf = get_message_buf(req_buf);
            jstring request = (*env)->NewStringUTF(env, r_buf);
            jstring j_string = (jstring)((*env)->CallStaticObjectMethod(env, clazz, method_id, client_fd, request));
            char *response = (char *)((*env)->GetStringUTFChars(env, j_string, NULL));
            // 将请求得到响应写回子进程
            char *w_buf = set_message_buf(response, client_fd);
            write(pipe_fd[1], w_buf, strlen(w_buf));
        }
        printf("(parent process) read error.");
        close(pipe_fd[0]);
        close(pipe_fd[1]);
        return ;
    }
    else {
        while (1) {
            //子进程的处理与父进程的处理是镜像的,具体代码就不贴了...
        }
    }

后续更新的博客:

学习云风协程库

消灭本Demo中处理协程出现的Bug

Linux 上使用JNI代码


以上所述就是小编给大家介绍的《基于C语言的协程和Java混合编程的服务器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

突破之道

突破之道

(美)基思 R. 麦克法兰(Keith R. McFarland) / 江南、江维 / 机械工业出版社 / 2017-4 / 49.00

《从优秀到卓越》前传。 深入调查7000多家公司、1500多名关键管理人员,总结提炼出6大突破式发展策略。 解析创业成功后的公司如何腾飞,以几何速度增长,突破10亿关口。 阐述为什么创始人自己才是企业实现突破的最大障碍。一起来看看 《突破之道》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试