美文网首页
一个简单的Hello World服务器

一个简单的Hello World服务器

作者: sansansan233 | 来源:发表于2020-11-24 23:33 被阅读0次

学习网络编程,基本上都是从一个简单的BIO服务器开始的,一个简单的C语言写的Helloworld服务器如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <memory.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MAXLINE 1024
#define BACKLOG 10

int main(int argc, char **argv)
{
    //监听文件描述符,客户端连接文件描述符
    int listenfd, connfd;
    //服务端socket地址配置
    struct sockaddr_in server_addr;
    //客户端socket地址配置
    struct sockaddr_in client_addr;
    //服务器端口号
    int portnumber;
    //用户提供绑定地址
    if (argc != 2)
    {
        printf("portnuber error!\n");
        exit(1);
    }
    if ((portnumber = atoi(argv[1])) < 0)
    {
        printf("socket error: %s.\n", strerror(errno));
        exit(1);
    }

    //服务器新建Socket文件描述符,稍后绑定地址
    //AF_INET--IPV4
    //SOCKET_STREAM--TCP
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        printf("socket error:%s.\n", strerror(errno));
        exit(1);
    }
    //清理server_addr内容,准备绑定
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    //设置socket协议
    server_addr.sin_family = AF_INET;
    //转换为网络地址绑定,INADDR_ANY,绑定本地任何地址
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    //绑定端口
    server_addr.sin_port = htons(portnumber);

    //helloworld服务器
    char hello[] = "hello world";
    //绑定socket和listernfd
    if (bind(listenfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1)
    {
        printf("bind error: %s.\n", strerror(errno));
        exit(1);
    }

    //服务器开始监听
    if (listen(listenfd, BACKLOG))
    {
        printf("listen error: %s.\n", strerror(errno));
        exit(1);
    }

    int sin_size = sizeof(struct sockaddr_in);
    //准备接受客户端请求
    while (1)
    {
        //服务器阻塞,直到有客户端进行链接

        if ((connfd = accept(listenfd, (struct sockaddr *)(&client_addr), &sin_size)) == -1)
        {
            printf("accept error:%s.\n", strerror(errno));
            continue;
        }
        //打印客户端IP地址
        printf("server get connection from %s\n", inet_ntoa(client_addr.sin_addr));
        //hello world服务器回显hello world
        if (write(connfd, hello, strlen(hello)) == -1)
        {
            printf("write error:%s.\n", strerror(errno));
            exit(1);
        }
        close(connfd);
    }
    close(listenfd);
    return 0;
}

当然,这个服务器的问题很多,比如:
1、代码没有模块化,分工不明确,比如简单的将服务器的功能分割开来分为init系统初始化和connect等待客户端连接两个部分
2、服务器的输入应当读取配置文件或者从其他服务器上读取。可以提供一个抽象类或者某个函数,服务器调用这个抽象类或者函数,来实现系统配置的加载。而这个抽象类或者函数可以有不同的实现,来提供服务器不同的加载配置文件
3、在服务器中,最重要的可能就是同时尽可能的给多个客户端来提供服务。而在这个while循环中,同时只能够处理一个链接,即同时只能有一个客户端进行访问(可以在write函数前后添加一个sleep来模拟服务器需要处理一个复杂的操作)。
3.1、对此,可以在accept之后,使用多线程技术解决,通过pthread_create函数来开启一个线程。
3.2、也可以提供一个线程池,通过在init时候读取配置文件,配置线程池的信息,在收到connfd的时候,封装成任务,提交给线程池进行处理
3.3、也可以使用Linux系统提供的IO复用技术(select,poll,epoll),设置socket为no_block,然后使用单线程处理
3.4、最后,也可以使用3.2和3.3两个解决方法综合起来,类似于Netty中通常使用的提供两个线程池,一个boss线程池负责处理监听端口,一个worker线程池专门提供connfd的处理。当boss创建出一个connfd的时候,就提交给worker线程池的阻塞队列中。worker从队列中提取出connfd,来进行处理。
4、在这个服务器中,对于客户端的输入,完全没有处理。我们可以提供一个缓冲区,来接收客户端的输入。并且解析出里边儿的内容,根据客户端的输入来进行相应的操作。比如一个HTTP服务器,在init过程中,生成一个处理方法和问题的map,收到内容后,根据HTTP的版本、域名、HTTP方法、url路径,来在map中查询到对应的处理方法,来进行处理,并写回到connfd中。
可以简单的认为现在复杂的网络应用内容一个最简单的静态服务器,可以由客户端发送某个文件名,然后服务端在本地文件系统中打开文件,并读取到文件的内容,再写回到客户端中,客户端可以在黑框框中读取文件,这样看一些公告,或者类似于Linux中man手册的话,可能还是够用的,但是更好的效果就没有了。
后来这样的结构都觉得不好用,对于富文本,最简单的添加一些文字的大小都不好使,于是就有了html,客户端也可以使用某个可以显示的文本的显示框框来进行查看,可以认为这是浏览器的简单雏形,这时候支持了html里的<head><title><body><h1>等这些简单的标签。后面为了显示效果更好,浏览器可以支持一些样式格式,只需要服务器提供出来相应的css格式,浏览器根据html里<link>标签向服务器请求对应的文件,并加载css格式,并根据html中对于对应元素来进行配置,就可以显示了。
但是我们对于文档来说,还是一个个单独的,分离的文档,并没有产生联系。每次查看一个新的文档,均需要在地址框里重新输入url,向服务器请求。于是出现了<a href="">标签。浏览器可以看到<a>标签,生成一个超链接,用户点击后,服务器请求文件并加载。
这时候我们已经可以像查找一个字典一样查看文档了。但这仍然不够,用户的互动性仍然不足。比如,可能想要隐藏,鼠标滑过某些部分,产生一些动作,如弹出标识,显示高亮颜色等等。于是又加入了js,通过浏览器事件,在浏览器端和用户进行交互,和css文件一样,通过<link>标签,浏览器加载文件,执行相应的动作。
这时候回看我们的前后端程序。我们用户通过输入服务器的ip地址和端口号以及请求的文件路径,仍然略显繁琐。于是有了域名系统,用户输入域名,浏览器查看本地配置,本地缓存,或者向DNS服务器请求来解析出来服务器的ip地址,我们就不用记录繁琐的ip地址,也可以随意的更换ip地址,只需要向DNS服务器备案提供配置就好了,同时可以提供出来第一层的负载均衡策略---在DNS服务器中一个域名对应多个IP地址。然后大家形成一个共识,http均使用80端口。
这时候,服务器仍然不能提供出来客户定制化的需求。对于客户端来说,请求服务器也没有状态等操作,于是,形成一个简单的http协议,如有http版本号,请求方法,请求路径,请求域名,响应状态,相应内容等等。可以提供一些简单的操作,并且一个服务器上,可以根据域名不同,版本不同等,提供不同的服务了。从TCP上来看HTTP,就像是一段文字,一串string,服务器在接收到请求的时候,就像剥洋葱一样,剥离http的包裹,找到body,并把包裹上的信息,装入到对应的位置(如java中request对应的各个字段)
但这仍然不够细化,于是有了cookie,在用户请求的时候,每次都带上用户的信息(如userId和password,通过<form>标签,或者浏览器提供缓存服务,每次自动添加等)。服务器就可以判断出用户到底谁是谁,可以查询本地数据库,远程数据库或者本地文件系统,找到对应用户的内容,修改所要响应的html内容。提供定制化的服务,如jsp。
但这样仍显繁琐。于是就有了session,用户在登陆的时候,提供userId和password,服务器校验过后,生成一个id,颁发给用户,浏览器。浏览器自动添加上这个id(可以在cookie中),或者服务器直接修改用户将要点击的链接url,添加上这个id。服务器在本地、远端redis中保存住用户的信息,并根据这个id查找用户的信息。这样,就可以提供一定的定制化服务。
但现在这时候,每次用户点击的时候,服务器都需要重新生成(渲染)html文档,发送回浏览器,这样,在大并发的过程中,对于服务器的压力就比较大了。于是发明了ajax、前后端分离、服务器的动静分离。
浏览器在请求的时候,首先请求静态文件。nginx/cdn可以读取本地的静态文件,提供静态的文件服务。用户登陆后,通过ajax请求相应的动态内容,服务器收到请求后,根据请求的方法、内容,选出本地的处理方法,进行处理,生成响应结果,返回浏览器。浏览器收到结果后,根据之前请求的静态文件里的js代码,对于结果进行处理,并且进行相应的操作,将响应内容加载或者处理到相应的位置,这样,就形成了用户看到的定制化的内容。这时候,前端看后端就是一个统一的服务器。后端看前端就是不断地动态请求。
对于后端的复杂处理,随着业务的增多,单体的应用无法处理这么多的请求或者根据业务的垂直划分和希望的水平扩展,可以通过nginx进行动静分离和负载均衡,后面的动态请求可以设置一个统一的动态网关,进行相应的转发和负载均衡。业务服务器调用某些基础的服务,形成了整合内容更加有利于业务的开发隔离。这样,就形成了微服务,RPC调用的基础。后端服务之间的相互通讯必须知道对方的ip和端口号,类似于之前的DNS,我们也可以提供一个应用,来提供类似的服务,于是有了eureka,nacos,zookeeper这样的服务发现治理功能。
于是,就形成了现在千千万万复杂的互联网应用。关于这些,本文写的仍然显得十分浅薄,但可以看作简单的发展应用过程。后面会写一个静态的light http的服务器,来提供出相应的文件阅读操作,对于应用进行模块化的设计。

相关文章

网友评论

      本文标题:一个简单的Hello World服务器

      本文链接:https://www.haomeiwen.com/subject/mdcgiktx.html