指标

在Redis源码-3 网络编程, 学习了redis封装的网络库。其中用了循环不断去执行anetTcpAccept, 这是机制效率差,为了进步程序的并发数,操作系统引入了epoll等相似的机制,防止了死等,能够做到当事件产生时告诉用户。Redis没有才用现有的异步库,自研了一个适宜redis的库。比拟玲珑,适宜学习。本文引入Redis中的ae模块,革新之前的流程。

可运行代码

源码

筹备工作:从redis源码中拷贝代码

 cp /home/vagrant/github/server_installer/servers/redis/redis-6.2/src/ae* . ...

批改server.c

#include "anet.h"#include "zmalloc.h"#include <sys/socket.h>#include <unistd.h>#include "sys/socket.h"#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */#include "ae.h"void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);#define MAX_ACCEPTS_PER_CALL 1000#define UNUSED(V) ((void) V)void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {    int cport, cfd, max = MAX_ACCEPTS_PER_CALL;    char cip[NET_IP_STR_LEN];    UNUSED(el);    UNUSED(mask);    UNUSED(privdata);    while(max--) {        char* neterr;        neterr = zmalloc(100);        cfd = anetTcpAccept(neterr, fd, cip, sizeof(cip), &cport);        if (cfd == ANET_ERR) {            continue;        }        anetCloexec(cfd);        printf("accept...%d\n",cfd);        char buf[1024];        recv(cfd, buf, sizeof(buf), MSG_WAITALL);        printf("recv from %s:%d  %s\n",cip, cport, buf);        close(cfd);    }}int main() {    // 错误信息    char *neterr = zmalloc(10);    printf("staring...\n");    aeEventLoop *el;    el = aeCreateEventLoop(100);    // 端口6380    int serverSocket = anetTcpServer(neterr, 6380,"*" , 2);    if ( ! neterr ) {        printf("start err %s \n", neterr);        return 1;    }    printf("listening...%d \n",serverSocket );    if (aeCreateFileEvent(el, serverSocket, AE_READABLE, acceptTcpHandler,NULL) == AE_ERR) {        aeDeleteFileEvent(el, serverSocket, AE_READABLE);        return 1;    }    aeMain(el);    aeDeleteEventLoop(el);    return 0; }

去掉了while(1)循环,调用anetTcpServer创立事件循环,调用aeCreateFileEvent注册文件事件。之后进入事件循环aeMain。当有文件事件产生时,会调用acceptTcpHandler, 在这里执行anetTcpAccept, 对文件描述符进行读操作。

创立Makefile

all: server client    @echo "anet demo"server :  anet.o zmalloc.o ae.o server.o monotonic.o    $(CC)   -o $@   $^client : anet.o zmalloc.o client.o    $(CC)   -o $@   $^%.o: %.c     $(CC) -O0 -DREDIS_TEST=1 -MMD -o $@ -c $<.PHONY: cleanclean:    rm -rf *.o *.d server client

输入

同Redis源码-3 网络编程

性能简略压测

为了看下应用事件循环和不应用事件循环的区别,进行简略压测。网络上找了写tcp压测工具,没找到适合的,就本人写个简略的程序。

测试代码

新建 main.go

连贯server端,Hello server

package mainimport (    "fmt"    "net"    "os")func main() {    Conn("localhost:6380")}func Conn(service string) {    // 绑定    tcpAddr, err := net.ResolveTCPAddr("tcp", service)    checkError(err)    // 连贯    conn, err := net.DialTCP("tcp", nil, tcpAddr)    checkError(err)    //for {    // 发送    _, err = conn.Write([]byte("Hello server"))    checkError(err)    //}    conn.Close()}func checkError(err error) {    if err != nil {        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())        os.Exit(1)    }}

新建 main_test.go

基准测试

package mainimport "testing"func BenchmarkConn(b *testing.B) {    for n := 0; n < b.N; n++ {        Conn("localhost:6380")    }}

测试后果

应用异步事件
go test -bench=.

goos: linuxgoarch: amd64pkg: testcpu: Intel(R) Celeron(R) N4100 CPU @ 1.10GHzBenchmarkConn-4             6278            356108 ns/opPASSok      test    2.264s

不应用异步事件

goos: linuxgoarch: amd64pkg: testcpu: Intel(R) Celeron(R) N4100 CPU @ 1.10GHzBenchmarkConn-4              100          10393680 ns/opPASSok      test    1.048s

可见异步编程能进步程序相应速度的。

参考

  • understanding-the-redis-event-model
  • ae