ubuntu下搭建简易web服务器(一)

前言

本文所搭建的是局域网内可访问的web服务器,主要功能包括ls目录和cat文件。使用HTTP协议,通过浏览器与服务器进行通信。为防止遗忘,特此写下这篇博客,有不妥之处还望指正。

主要操作

服务器/客户端的模型类似于我们日常打电话。服务器搭建与工作流程可以分为六个步骤,每个步骤对应于一个系统调用(参考《UNIX/LINUX编程实践教程》)。

行为系统调用
1获取电话线socket
2.分配号码bind
3.允许接入调用listen
4.等待电话accept
5.传送数据read/write
6.挂断电话close

函数介绍

socket: 在man手册里介绍,它创建一个通信端点。返回一个socket描述字,它存在于协议族(address family,AF_XXX)空间中,但没有一个具体地址。如果想给他复制一个地址,必须调用bind()函数。它类似于获取一根电话线。它的函数原型为:

#include<sys/types.h>
#include<sys/socket.h>
int socket(int  domain,int type,int protocal);

参数domain定义了该socket的通信域,我们常用的是AF_INET(ipv4)和AF_INET6(ipv6),还用AF_UNIX,它用于本地通信。
参数type:指出了程序将要使用的数据类型。在本文我们使用SOCK_STREAM,它是双向的管道类型,数据作为连接的字节流从一段写入,再从另一端流出。
参数protocol:内核中网络代码所使用的协议,并不是客户端和服务器之间的协议。一个0的值代表选择标准的协议。

bind:把一个地址分配给socket。该地址分配类似于把电话号码分配给一根电话线。它的函数原型为:

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)

参数sockfd:socket系统调用返回的socket描述字。
参数struct sockaddr *addr:包含了需要绑定的socket的基本信息,比如地址、通信域、端口号等。但一般编程中并不直接针对次数据结构操作,而是使用另一个与sockaddr等价的数据结构,因为本文搭建的是web服务器,且通信域为ipv4,所以使用与之等价的sockaddr_in结构,其定义在/usr/include/netinet/in.h中。

listen:坚挺socket上的连接,函数原型为:

#include<sys/types.h>
#include<sys/socket.h>
int listen(int sockfd,int backlog)

参数sockfd:socket系统调用返回的socket描述字。
参数backlog:sockfd的等待队列的最大长度。

accept:接收socket上的一个连接,就像接电话拿起话筒,它会返回一个文件描述符,此时的网络I/O操作就类似于文件I/O操作。accept系统调用是否阻塞取决于socket描述字的属性,可以使用fcntl函数来对socket描述字进行设置O_NOBLOCK,函数原型为:

#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,sockklen_t *addrlen)

参数sockfd:socket系统调用返回的socket描述字。
参数*addr:储存连接到服务器连接的信息,类似于来电显示,其中包含了连接地址、连接端口号等信息。

read()/write():在前一步骤中,accept()返回了一个文件描述符,此时就可以想文件IO一样对其进行操作,可以使用系统调用read()和write(),也可以使用fdopen()函数,将其转换成文件流的方式进行操作。

close(fd):类似于挂断电话,关闭这个文件描述符相当于结束此次连接。

搭建服务器

搭建服务器的步骤上述已经描述,本节姜葱代码层面进行分析讲解。搭建服务器的代码独立封装成文件socklib.c,以便于后续调用。文件中也封装了connect_to_server的函数,与搭建服务器大同小异,我就偷个懒了。

//函数:make_server_socket
//参数:portname:端口号;
//		queue:队列长度;
//返回值:-1:失败;
//       sockid:成功并返回socket描述字;
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<strings.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<ifaddrs.h>
#include<linux/if_link.h>
#include<string.h>

#define oops(m) {perror(m);exit(1);}

int make_server_socket(int portname,int queue)
{
        int sock_id;
        struct sockaddr_in saddr;
        struct hostent *hp;
        char hostname[100];
//        char **pptr;
//        char str[32];
//        struct ifaddrs *ifaddr,*ifa;
//        int family,n;
//        char host[NI_MAXHOST];

        if((sock_id=socket(PF_INET,SOCK_STREAM,0))==-1)//获取socket描述字
        {
                oops("socket");
                return -1;
        }
        if((gethostname(hostname,100))==-1)//获取本主机的标准主机名
        {
                oops("gethostname");
                return -1;
        }
        bzero((void *)&saddr,sizeof(saddr));//初始化saddr;
        hp=gethostbyname(hostname);//返回包含本机主机名字和地址信息的hostent结构体指针。用域名或主机名获取IP地址。
        bcopy((void *)hp->h_addr,(void *)&saddr.sin_addr,hp->h_length);
        //inet_aton(host,(struct in_addr *)&saddr.sin_addr.s_addr);
        saddr.sin_port=htons(portname);//将整形变量从主机字节书序变成网络字节顺序。
        saddr.sin_family=AF_INET;
        if((bind(sock_id,(struct sockaddr *)&saddr,sizeof(saddr)))==-1)//绑定sockid
        {
                oops("bind");
                return -1;
        }
        if((listen(sock_id,queue))==-1)//监听sockid,最大连接数queue
        {
                oops("listen");
                return -1;
        }
        return sock_id;
}

//函数connect_to_server:
//参数:*host:主机名:
//		postname:端口号;
int connect_to_server(char *host,int postname)
{
        int sock_id;
        struct sockaddr_in saddr;
        struct hostent *hp;

        if((sock_id=socket(AF_INET,SOCK_STREAM,0))==-1)
        {
                oops("socket");
                return -1;
        }

        bzero(&saddr,sizeof(saddr));
        hp=gethostbyname(host);
        if(hp==NULL)
        {
                //oops("gethostbyname");
                printf("gethostbyname error\n");
                return -1;
        }
        bcopy(hp->h_addr,(struct sockaddr *)&saddr.sin_addr,hp->h_length);
        //printf("%d\n",hp->h_length);
        saddr.sin_port=htons(postname);
        saddr.sin_family=AF_INET;
        if((connect(sock_id,(struct sockaddr *)&saddr,sizeof(saddr)))!=0)
        {
                oops("connect");
                return -1;
        }
        printf("connect success\n");
        return sock_id;
}

简单介绍下在make_server_socket中涉及到几个函数:gethostname,gethodtbyname,htons.

int gethostname(char name,size_tlen);

这个函数获取当前主机的主机名;

struct hostent *gethostname(const char *name);

这个函数返回对应于主机名的hostent结构指针。name可以是主机名也可以是标准点表示法的IPV4地址。我们这里使用的是主机名。它返回一个hostent结构体。 hoetent结构体:

struct hostent{
	char *h_name; //表示主机的规范名,如www.google.com;
	char **h_aliases; //表示主机别名,如google;
	int h_addrtype; //表示主机ip地址的类型。即ipv4或者ipv6;
	int h_length; //表示主机ip地址的长度;
	char **h_addr_list; //表示主机的ip地址。这个是网络字节顺序存储,不能用printf的%s格式打印,需要的话调用inet_ntop();
	#define h_addr h_addr_list[0]
}

实现时间查询服务器

在上一小节简述了如何搭建服务器,并封装了make_server_socket和connect_to_server两个函数,在这一节讲简述如何利用前面编写的socklib运作和访问服务器。本文以带时间查询功能的服务器为例。
直接上代码:
dateser.c

#include "socklib.h"
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<netinet/in.h>
#include<stdlib.h>

#define oops(m) {perror(m);exit(1);}

int process_request(int fd)
{
        int pid=fork();
        switch(pid)
        {
                case -1: return -1;
                case 0:  dup2(fd,1);
                         close(fd);
                         execlp("date","date",NULL);
                         oops("execlp");
                default:wait(NULL);
        }
}
int main()
{
        int sock_server,sock_client;
        struct sockaddr_in saddr;
        int len;

       // printf("begin\n");
        if((sock_server=make_server_socket(13000,1))==-1)
        {
                printf("sock_server error\n");
                exit(1);
        }
        while(1)
        {
                printf("wait for connect\n");
                if((sock_client=accept(sock_server,(struct sockaddr *)&saddr,&len))==-1)
                        break;
                printf("connected\n");
                //printf("get a connect:%d %d\n",saddr.sin_addr.s_addr,saddr.sin_port);
                process_request(sock_client);
                printf("over\n");
                close(sock_client);
        }

}

dateclient.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include"socklib.h"

void talk_with_server(int sock_fd)
{
        char buf[BUFSIZ];
        int n;

        n=read(sock_fd,buf,BUFSIZ);
        write(1,buf,n);
}

void del_str_line(char *str)
{
        char *p=str;

        while(*p!='\n')
        {
                p++;
                if(*p=='\0')
                        break;
        }
        *p='\0';
}


int main()
{
        int sock_fd;
        char hostname[100],portname[50];
        int port;

        printf("hostname:");
        fflush(stdout);
                read(0,hostname,100);
        del_str_line(hostname);
        printf("port:");
        fflush(stdout);
        read(0,portname,50);
        port=atoi(portname);
        printf("hostname:%s port:%d\n",hostname,port);
//      fflush(stdout);


        if((sock_fd=connect_to_server(hostname,port))==-1)
        {
                printf("sock_fd error%d\n",sock_fd);
                exit(1);
        }
        talk_with_server(sock_fd);
        close(sock_fd);
        return 1;
}

完成以上代码后进行编译执行。注意要写一下socklib.h头文件,这里就不贴了。还要注意的是在dataserver中的端口号我直接定的13000,你也可以通过命令含参数进行自定义。
yang@yang-Inspiron-5437:~/桌面/study/csdn$ sudo gcc socklib.c dateser.c -o dateser

yang@yang-Inspiron-5437:~/桌面/study/csdn$ sudo gcc socklib.c dateclient.c -o dateclient

yang@yang-Inspiron-5437:~/桌面/study/csdn$ ./dateser &
[1] 17019
yang@yang-Inspiron-5437:~/桌面/study/csdn$ wait for connect

yang@yang-Inspiron-5437:~/桌面/study/csdn$ telnet 127.0.1.1 13000
Trying 127.0.1.1…
Connected to 127.0.1.1.
Escape character is ‘^]’.
connected
2020年 05月 21日 星期四 17:59:50 CST
over
wait for connect
Connection closed by foreign host.
yang@yang-Inspiron-5437:~/桌面/study/csdn$ ./dateclient
hostname:127.0.1.1
port:13000
hostname:127.0.1.1 port:13000
connect success
connected
2020年 05月 21日 星期四 18:00:05 CST
over
wait for connect
yang@yang-Inspiron-5437:~/桌面/study/csdn$
asdas
注意哦,我使用的hostname是直接用的ipv4地址,也可以是对应的主机名,可以在cat /etc/hosts查看。

最后

本文讲解的是给予本机的服务器搭建,即只能本机访问,别的电脑无法访问,原因是bind绑定的是主机的ip(127.0.0.1),而不是局域网内的ip(192.168.x.x),如果绑定是局域网,那么同局域网的其他电脑即可访问。这一部分将在ubuntu下搭建简易web服务器(二)中进行简述。

声明:作者主要通过《UNIX/LINUX编程实践教程》的学习完成本文。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值