Web服务器端程序主要是两个部分,一部分是主函数,一部门是命令处理函数。命令处理函数比较好理解就是针对客户端不同的命令进行处理,与客户端进行通信。主函数也有两个主要的功能,第一是要对程序进行初始化,其中包括创建监听套接字并且绑定到地址和端口上,第二是创建子进程处理对应的连接请求。

1、主函数

Web服务器的主函数中第一是初始化程序,第二就是创建子进程。父进程一直监听,子进程进行连接处理,提高服务器端的处理能力,提高效率,流程图如下:

主函数中的代码如下所示:

#include "common.h"
int main(void)
{
struct sockaddr_in sin; /* 服务器端地址结构 */
struct sockaddr_in cin; /* 客户端地址结构 */
int lfd, cfd;
socklen_t len = sizeof(struct sockaddr_in);
/* 十进制点分格式地址的长度,包括字符串结束符 */
char buf[MAX_LINE]; /* 命令缓冲区,存储发送给客户端的信息 */
char str[ADDR_LEN]; /* 十进制点分地址缓冲区 */
int sock_opt = ; /* 套接字选项 */
int n;
pid_t pid;
if(init(&sin, &lfd, sock_opt) == -)
/* 初始化程序,得到地址结构和监听套接字描述符 */
exit();
printf("waiting connections ...\n"); /* 打印提示信息 */
while(){ /* 死循环,处理客户端的请求 */
if( (cfd = accept(listen_fd, (struct sockaddr *)&cin, &len)) == -){ /* 接收请求 */
perror("fail to accept");
exit();
}
if( (pid = fork()) < ){ /* 创建子进程*/
perror("fail to fork");
exit();
}else if(pid == ){ /* 子进程处理连接请求,父进程继续监听 */
close(lfd); /* 关闭监听套接字 */
while(){ /* 本程序的客户端是一个交互式程序,服务器端和客户端也是交互的 */
if(my_read(cfd, buf, MAX_LINE) == -) /* 读取客户端的命令 */
exit();
/* 在用户发送的命令串中寻找合法的命令,找到则处理 */
if(strstr(buf, "GET") == buf) /* get命令 */
if(do_put(cfd, &buf[]) == -) /* 调用do_put函数进行处理 */
printf("error occours while putting\n");
else if(strstr(buf, "PUT") == buf) /* put命令 */
if(do_cd(cfd, &buf[]) == -) /* 调用do_put函数进行处理 */
printf("error occours while getting\n");
else if(strstr(buf, "CD") == buf) /* cd命令 */
if(do_ls(cfd &buf[]) == -)
printf("error occours while changing directory\n");
else if(strstr(buf, "LS") == buf) /* ls命令 */
if(do_ls(&buf[]) == -)
printf("error occours while listing\n");
else if(strstr(buf, "BYE") == buf) /* bye命令 */
break; /* 不调用相关函数处理,直接跳出循环 */
else{ /* 未知命令,出错 */
printf("wrong command\n");
exit();
}
}
close(cfd); /* 跳出循环后关闭连接套接字描述符,通信结束 */
exit(); /* 子进程退出 */
}else
close(cfd); /* 父进程关闭连接套接字,继续监听 */
}
return ; /* 服务器程序很少有退出的时候 */
}

在主函数中也可以看出来父进程用来监听,子进程用来处理连接请求。一开始进行LinuxC学习的时候学到了进程的相关的知识,这是一个多进程的程序实例,原理很简单,但是需要注意的是子进程与父进程资源共享,因此要在父进程进行监听的时候要关闭连接套接字,在子进程中要关闭监听套接字,防止父子进程中相互干扰。主函数中进程初始化调用的init函数,函数代码如下:

int init(struct sockaddr_in *sin, int *lfd, int sock_opt)
{
bzero(sin, sizeof(struct sockaddr_in));
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
sin->sin_port = htons(PORT); if( (tfd = socket(AF_INET, SOCK_STREAM, )) == -) { /* 创建监听套接字 */
perror("fail to creat socket");
return -;
}
setsockopt(tfd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(int)); /* 设置套接字选项 */
/* 绑定客户端地址,具体地址没有限制 */
if( (bind(tfd, (struct sockaddr *)sin, sizeof(struct sockaddr_in))) == -){
perror("fail to bind");
return -;
}
if( (listen(tfd, )) == -){
perror("fail to listen");
return -;
}
*lfd = tfd;
return ;
}

2、命令处理模块

根据之前的客户端程序可以知道服务器端有4个函数,分别是处理GET、PUT、CD、LS这四个命令。

do_put函数处理GET命令,函数成功返回0,失败返回-1。客户端发过来的GET命令的格式为:GET arg1。

参数说明:

cfd : 连接套接字的描述符

file : 客户端请求的文件的路径

int do_put(int cfd, char *file)
{
struct stat statbuf; /* 文件状态缓冲区 */
int n, fd;
int res = -; /* 返回值 */
if( (fd = open(file, O_RDONLY)) == -){ /* 打开客户端请求的本地文件 */
/* 打开失败则向客户端输出出错信息并将应答码设置为ERR
* 出错信息格式为:应答码 出错信息
*/
my_write(cfd, "ERR open server file\n", strlen("ERR open server file\n"));
return res; /* 返回-1 */
}
if( (fstat(fd, &statbuf)) == -){ /* 得到文件状态 */
my_write(cfd, "ERR stat server file\n", strlen("ERR stat server file\n") /* 出错则发送错误信息 */
goto end;
}
if(!S_ISREG(statbuf.st_mode)){ /* 如果被请求文件不是普通文件,则出错 */
if(my_write(cfd, "ERR server path should be a regular file\n", strlen("ERR server path should be a regular file\n")) == -) /* 向客户端发送出错信息 */
goto end;
res = ; /* 成功发送后do_put函数返回0,虽然出现了错误,但还是返回0 */
goto end;
}
sprintf(buf, "OK %d", statbuf.st_size); /* 一切正常,发送应答信息,格式为:OK 发送文件的长度 */
if(my_write(cfd, buf, strlen(buf)) == -) /* 发送应答信息 */
goto end;
if ( (my_read(cfd, buf, MAX_LINE)) <= ) /* 等待客户端的应答信息,应答码是RDY */
goto end;
while(){ /* 开始传送文件内容 */
n = read(fd, buf, MAX_LINE); /* 循环读取文件内容,直到文件结束 */
if(n > )
if(my_write(cfd, buf, n) == -) /* 将读取的文件内容发送给客户端 */
goto end;
else if(n == ) { /* 文件已经到达结尾 */
printf("OK\n"); /* 输出提示信息 */
break;
}else { /* 如果读取的字节数小于0则说明出错 */
perror("fail to read");
goto end;
}
}
res = ; /* 执行至此一切正常 */
end:
close(fd); /* 关闭文件,注意不是关闭套接字 */
return res;
}

do_get函数处理PUT命令,成功返回0,失败返回-1。客户端发过来的 PUT命令的格式为:PUT arg1 arg2

该命令将客户端的文件传送至服务器,arg1为文件的大小,arg2为传送文件在服务器端的文件路径。

参数说明:

cfd : 连接套接字的描述符

file : 传送过来的文件在服务器端的存储路径

int do_get(int cfd, char *file)
{
struct stat statbuf; /* 文件状态缓冲区 */
int n, fd, len;
int res = -;
if( (fd = open(file, O_WRONLY|O_CREAT|O_TRUNC, )) == -){ /* 打开文件 */
/* 文件的打开方式是覆盖写,也就是说如果该文件已存在则覆盖。
* 但是如果有一个同名的目录则无法覆盖,下面分支处理这种情况
*/
if(errno == EISDIR){
if(my_write(cfd, "ERR server has a dir with the same name\n",
strlen("ERR server has a dir with the same name\n")) == -) /*
输出错误信息 */
goto end;
res = ; /* 这种错误属于用户的不正确请求 */
goto end;
}else{ /* 其他的原因不能打开文件则是服务器程序的错误 */
my_write(cfd, "ERR open server file\n", strlen("ERR open server file\n")); /* 输出错误信息 */
goto end;
}
}
if( (fstat(fd, &statbuf)) == -){ /* 得到文件状态 */
my_write(cfd, "ERR stat server file\n", strlen("ERR stat server file\n")); /* 输出错误信息 */
goto end;
}
len = statbuf.st_size;
if(!S_ISREG(statbuf.st_mode)){ /* 如果该文件不是普通文件则出错 */
if(my_write(cfd, "ERR server path should be a regular file\n", strlen("ERR server path should be a regular file\n")) == -) /* 输出错误信息 */
goto end;
res = ;
goto end;
}
if(my_write(cfd, "OK", ) == -) /* 发送正确的应答码 */
goto end;
while(){ /* 循环读传送过来的文件内容 */
n = my_read(cfd, buf, MAX_LINE);
if(n > ){
write(fd, buf, n); /* 写到指定的文件中 */
len -= n; /* 文件长度减少 */
}else if(len == ) {
/* 文件没有剩余,表示传输完毕 */
printf("OK\n");
break;
}else /* 读取的字节数小于0,出错 */
goto end;
}
res = ; /* 正常返回 */
end:
close(fd);
return res;
}

do_cd函数处理CD命令,成功返回0,失败返回-1,客户端发送过来的CD命令的格式为:PUT arg1

该命令进入指定的服务器端目录,arg1为指定目录的路径

参数说明:

path:指定目录的路径

int do_cd(char *path)
{
if(chdir(path) == -){ /* 进入指定的目录 */
perror("fail to change directory\n");
/* 出错则向客户端发送错误应答码和出错原因 */
my_write(cfd, "ERR can't change directory\n", strlen("ERR can't change directory\n"));
return -;
}
my_write(cfd, "OK\n");
return ;
}

do_ls函数处理LS命令,成功返回0,失败返回-1。客户端发送过来的LS命令的格式为:LS arg1。

该命令列出当前服务器端目录下的文件,arg1为指定目录的路径

参数说明:

path:指定目录的路径

int do_ls(char *path)
{
char cmd[];
char buf[NAME_LEN];
struct stat statbuf; /* 文件状态缓冲区 */
int n, fd;
int res = -; /* 返回值 */
/* 拼接命令“ls path > temp.txt”,将文件列表写在temp.txt文件中 */
sprintf(cmd, "ls %s > temp.txt ",path);
system(cmd); /* 执行该命令 */
if( (fd = open(“temp.txt”, O_RDONLY)) == -){ /* 打开客户端请求的本地文件 */
/* 打开失败则向客户端输出出错信息并将应答码设置为ERR。
* 出错信息格式为:应答码 出错信息
*/
my_write(cfd, "ERR ls server file\n", strlen("ERR ls server file\n"));
return res; /* 返回-1 */
}
if( (fstat(fd, &statbuf)) == -){ /* 得到文件状态 */
my_write(cfd, "ERR stat server file\n", strlen("ERR stat server file\n") /* 出错则发送错误信息 */
goto end;
}
if(!S_ISREG(statbuf.st_mode)){ /* 如果被请求文件不是普通文件,出错 */
if(my_write(cfd, "ERR server path should be a regular file\n", strlen("ERR server path should be a regular file\n")) == -) /* 向客户端发送出错信息 */
goto end;
res = ; /* 成功发送后do_put函数返回0,虽然出现了错误,但还是返回0 */
goto end;
}
sprintf(buf, "OK %d", statbuf.st_size); /* 一切正常发送应答信息,格式为: OK 发送文件的长度 */
if(my_write(cfd, buf, strlen(buf)) == -) /* 发送应答信息 */
goto end;
if ( (my_read(cfd, buf, MAX_LINE)) <= ) /* 等待客户端的应答信息,应答码是RDY */
goto end;
while(){ /* 开始传送文件内容 */
n = read(fd, buf, MAX_LINE); /* 循环读取文件内容,直到文件结束 */
if(n > )
if(my_write(cfd, buf, n) == -)/* 将读取的文件内容发送给客户端 */
goto end;
else if(n == ) { /* 文件已经到达结尾 */
printf("OK\n"); /* 输出提示信息 */
break;
}else { /* 如果读取的字节数小于0则说明出错 */
perror("fail to read");
goto end;
}
}
res = ; /* 执行至此一切正常 */
end:
close(fd); /* 关闭文件,注意不是关闭套接字 */
return res;
}

Web服务器端程序的实现的更多相关文章

  1. web应用程序

    1.web应用程序和网站的区别 应用程序有两种模式C/S.B/S.C/S是客户端/服务器端程序,也就是说这类程序一般独立运行.而B/S就是浏览器端/服务器端应用程序,这类应用程序一般借助IE等浏览器来 ...

  2. 黑客攻防技术宝典Web实战篇(一)Web应用程序技术基础

    在开展Web应用程序渗透测试之前请先了解下面列出的这些内容,如果不是很懂的话,请读David Gourley & Brian Totty的HTTP权威指南也叫HTTP:The Definiti ...

  3. Web标准中用于改善Web应用程序性能的各种方法总结

    提起Web应用程序中的性能改善,广大开发者们可能会想到JavaScript与DOM访问等基于各种既存技术的性能改善方法.最近,各种性能改善方法被汇总成为一个Web标准. 本文对Web标准中所包含的各种 ...

  4. 为什么移动Web应用程序很慢(译)

    前些日子,看到Herb Sutter在自己的博客中推荐了一篇文章<Why mobile web apps are slow>,在推荐里他这样写道: “I don’t often link ...

  5. Web应用程序简介

    1.HTTP通讯协议 根据联机方式与所使用的网络服务不同,会有不同的通信协议.例如,发送信件时会使用SMTP(Simple Mail Transfer Protocol,简单邮件传输协议),传输文件会 ...

  6. JAVA文件中获取路径及WEB应用程序获取路径方法

    JAVA文件中获取路径及WEB应用程序获取路径方法 1. 基本概念的理解 `绝对路径`:你应用上的文件或目录在硬盘上真正的路径,如:URL.物理路径 例如: c:/xyz/test.txt代表了tes ...

  7. 转:OWASP发布Web应用程序的十大安全风险

    Open Web Application Security Project(OWASP)是世界范围内的非盈利组织,关注于提高软件的安全性.它们的使命是使应用软件更加安全,使企业和组织能够对应用安全风险 ...

  8. [转]为什么移动Web 应用程序很慢

    原文出处: Herb Sutter   译文出处: tangzhnju 我写过不少文章来讨论为什么移动Web应用程序很慢,这也引起了不少的讨论.但是不幸的是,这些讨论没有像我喜欢的那样的基于事实. 所 ...

  9. NET5 Web应用程序

    ASP.NET5 Web应用程序结构 本文参考ASP.NET5 官方文档 Understanding ASP.NET 5 Web Apps,加入了一些个人理解,理解不对的地方希望大家能指出,互相学习. ...

随机推荐

  1. [深入理解Android卷一全文-第八章]深入理解Surface系统

    由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版.而知识的传播不应该由于纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容. ...

  2. 编程之美 set 1 不要被阶乘吓倒

    总结 1. 使用加法解决指数问题时, 可用背包问题的变形 2. 题目用到的公式和求解 1~N 中 1 出现的次数的公式类似 题目 1. 给定一个整数 N, 那么 N 的阶乘 N! 末尾有多少个 0 呢 ...

  3. GIS Cesium地图数据配置

    1.打开IIS,点击站点 2.跨域配置 配置方式: 在地图数据目录之中放置web.config文件,里面存放 <?xml version="1.0" encoding=&qu ...

  4. maven profile多环境动态配置文件使用

    pom.xml <profiles> <!-- =====开发环境====== --> <profile> <id>dev</id> < ...

  5. CH5402 选课【树形DP】【背包】

    5402 选课 0x50「动态规划」例题 描述 学校实行学分制.每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分.学校开设了 N(N≤300) 门的选修课程,每个学生可选课程的数量 M 是 ...

  6. js对用户信息加密传输 java后端解密

    1.加密采用服务端随机生成加密因子放入session中,传入登录或注册界面(每次进入都刷新) 2.页面中引入jquery.aes.js(这个js从网上下的坑比较多,引入先后顺序不一致都会报错,所以最后 ...

  7. window 如何枚举设备并禁用该设备和启用该设备?如何注册设备热拔插消息通知?

    目前实现的功能: 1.设备枚举 2.设置设备禁用和启用 3.注册设备热拔插消息通知 4.获取设备 vid pid 数值 需要链接的库 SetupAPI.lib DeviceManager 类如下: D ...

  8. 原!linux脚本 expect命令 完成 输入密码交互 进行scp远程文件拷贝

    1.安装expect yum install  expect expect相关知识--- https://blog.csdn.net/lufeisan/article/details/53488395 ...

  9. 持续集成之戏说Check-in Dance(转)

    add by zhj: 先说一下持续集成的定义,这是ThoughtWorks首席科学家Martin Fowler在<持续集成>第二版中给出的,“持续集成是一种软件开发实践.在持续集成中,团 ...

  10. Cache与主存之间的全相联映射,直接映射和组相联映射的区别

    2017-02-22 注:本文并非原创,来自百度文库,只是觉得写得较好,故分享之.若是某人的知识产权,望告知!谢谢 1.高速缓冲存储器的功能.结构与工作原理 高速缓冲存储器是存在于主存与CPU之间的一 ...