20145312《信息安全系统设计基础》实验五 网络通信
20145312《信息安全系统设计基础》实验五 网络通信
实验目的与要求
- 掌握在 ARM 开发板实现一个简单 WEB 服务器的过程。
- 学习在 ARM 开发板上的 SOCKET 网络编程。
- 学习 Linux 下的 signal()函数的使用。
实验内容
- 学习使用 socket 进行通讯编程的过程,了解一个实际的网络通讯应用程序整体设计,阅读 HTTP 协议的相关内容,学习几个重要的网络函数的使用方法。
- 读懂 HTTPD.C 源代码。在此基础上增加一些其他功能。在 PC 计算机上使用浏览器测试嵌入式 WEB 服务器的功能。
实验步骤
1. 搭建实验环境
- 连接arm开发板
- 建立超级终端
- 启动实验平台(redhat虚拟机)
- 配置同网段IP
- 安装arm编译器(bc共享文件夹)
- 配置环境变量(redhat虚拟机中)
2. 共享代码文件
- 将
\experiment\exp5\exp5\ws
目录下的07_httpd文件夹拷贝到共享文件夹bc中
3. 编译应用程序
- 运行make产生可执行文件httpd
4. 下载调试
- 使用 NFS 服务方式将 httpd 下载到开发板上,命令
mount -t nfs -o nolock 192.168.0.234:/home/bc /host
./httpd
运行可执行文件httpd
5. 本机测试
- 在台式机的浏览器中输入
http://192.168.0.121
(192.168.0.121为 UP-CUP S2410 实验板的 IP地址,可以使用ifconfig在arm中查看),观察在客户机的浏览器中的连接请求结果和在开发板上的服务器的打印信息。
- 此时超级终端显示连接成功
程序设计与分析
接口设计
int HandleConnect(int fd)
{
FILE *f;
char buf[160];
char buf1[160];
f = fdopen(fd,"a+");
if (!f) {
fprintf(stderr, "httpd: Unable to open httpd input fd, error %d\n", errno);
alarm(TIMEOUT);
close(fd);
alarm(0);
return 0;
}
setbuf(f, 0);
alarm(TIMEOUT);
if (!fgets(buf, 150, f)) {
fprintf(stderr, "httpd: Error reading connection, error %d\n", errno);
fclose(f);
alarm(0);
return 0;
}
#ifdef DEBUG
printf("buf = '%s'\n", buf);
#endif
alarm(0);
referrer[0] = '\0';
content_length = -1;
alarm(TIMEOUT);
//read other line to parse Rrferrer and content_length infomation
while (fgets(buf1, 150, f) && (strlen(buf1) > 2)) {
alarm(TIMEOUT);
#ifdef DEBUG
printf("Got buf1 '%s'\n", buf1);
#endif
if (!strncasecmp(buf1, "Referer:", 8)) {
char * c = buf1+8;
while (isspace(*c))
c++;
strcpy(referrer, c);
}
else if (!strncasecmp(buf1, "Referrer:", 9)) {
char * c = buf1+9;
while (isspace(*c))
c++;
strcpy(referrer, c);
}
else if (!strncasecmp(buf1, "Content-length:", 15)) {
content_length = atoi(buf1+15);
}
}
alarm(0);
if (ferror(f)) {
fprintf(stderr, "http: Error continuing reading connection, error %d\n", errno);
fclose(f);
return 0;
}
ParseReq(f, buf);
alarm(TIMEOUT);
fflush(f);
fclose(f);
alarm(0);
return 1;
}
- 客户连接处理
- 函数名:int HandleConnect(int fd)
- 参数:客户连接文件描述字
解析客户请求
int ParseReq(FILE *f, char *r)
{
char *bp;
struct stat stbuf;
char * arg;
char * c;
int e;
int raw;
#ifdef DEBUG
printf("req is '%s'\n", r);
#endif
while(*(++r) != ' '); /*skip non-white space*/
while(isspace(*r))
r++;
while (*r == '/')
r++;
bp = r;
while(*r && (*(r) != ' ') && (*(r) != '?'))
r++;
#ifdef DEBUG
printf("bp='%s' %x, r='%s' \n", bp, *bp,r);
#endif
if (*r == '?')
{
char * e;
*r = 0;
arg = r+1;
if (e = strchr(arg,' '))
{
*e = '\0';
}
} else
{
arg = 0;
*r = 0;
}
c = bp;
/*zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz*/
if (c[0] == 0x20){
c[0]='.';
c[1]='\0';
}
/*zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz*/
if(c[0] == '\0') strcat(c,".");
if (c && !stat(c, &stbuf))
{
if (S_ISDIR(stbuf.st_mode))
{
char * end = c + strlen(c);
strcat(c, "/index.html");
if (!stat(c, &stbuf))
{
DoHTML(f, c);
}
else
{
*end = '\0';
DoDir(f,c);
}
}
else if (!strcmp(r - 4, ".gif"))
DoGif(f,c);
else if (!strcmp(r - 4, ".jpg") || !strcmp(r - 5, ".jpeg"))
DoJpeg(f,c);
else if (!strcmp(r - 4, ".htm") || !strcmp(r - 5, ".html"))
DoHTML(f,c);
else
DoText(f,c);
}
else{
PrintHeader(f,'h');
alarm(TIMEOUT);
fprintf(f, "<html><head><title>404 File Not Found</title></head>\n");
fprintf(f, "<body>The requested URL was not found on this server</body></html>\n");
alarm(0);
}
return 0;
}
- 函数名:int ParseReq(FILE *f, char *r)
- 参数:
- 参数 1:文件流 FILE 结构指针,用于表示客户连接的文件流指针。
- 参数 2:字符串指针,待解析的字符串。
发送当前目录文件列表信息
- 函数名:int DoDir(FILE *f, char *name)
- 参数:
- 参数 1:文件流 FILE 结构指针,用于表示客户连接的文件流指针。用于写入目录文件信息数据。
- 参数 2:目录名,表示客户请求的目录信息。
发送 HTML 文件内容
int DoHTML(FILE *f, char *name)
{
char *buf;
FILE *infile;
int count;
char * dir = 0;
if (!(infile = fopen(name,"r"))) {
alarm(TIMEOUT);
fprintf(stderr, "Unable to open HTML file %s, %d\n", name, errno);
fflush(f);
alarm(0);
return -1;
}
PrintHeader(f,'h');
copy(infile,f); /* prints the page */
alarm(TIMEOUT);
fclose(infile);
alarm(0);
return 0;
}
- 函数名:int DoHTML(FILE *f, char *name)
- 参数:
- 参数 1:文件流 FILE 结构指针,用于表示客户连接的文件流指针。用于写入文件信息数据。
- 参数 2:客户请求的文件名。
发送纯文本(TXT)文件内容
int DoText(FILE *f, char *name)
{
char *buf;
FILE *infile;
int count;
if (!(infile = fopen(name,"r"))) {
alarm(TIMEOUT);
fprintf(stderr, "Unable to open text file %s, %d\n", name, errno);
fflush(f);
alarm(0);
return -1;
}
PrintHeader(f,'t');
copy(infile,f); /* prints the page */
alarm(TIMEOUT);
fclose(infile);
alarm(0);
return 0;
}
- 函数名:int DoText(FILE *f, char *name)
- 参数:
- 参数 1:文件流 FILE 结构指针,用于表示客户连接的文件流指针。用于写入文件信息数据。
- 参数 2:客户请求的文件名。
发送 JPEG 图像文件内容
int DoJpeg(FILE *f, char *name)
{
char *buf;
FILE * infile;
int count;
if (!(infile = fopen(name, "r"))) {
alarm(TIMEOUT);
fprintf(stderr, "Unable to open JPEG file %s, %d\n", name, errno);
fflush(f);
alarm(0);
return -1;
}
PrintHeader(f,'j');
copy(infile,f); /* prints the page */
alarm(TIMEOUT);
fclose(infile);
alarm(0);
return 0;
}
- 函数名:int DoJpeg(FILE *f, char *name)
- 参数:
- 参数 1:文件流 FILE 结构指针,用于表示客户连接的文件流指针。用于写入文件信息数据。
- 参数 2:客户请求的文件名。
发送 GIF 图像文件内容
int DoGif(FILE *f, char *name)
{
char *buf;
FILE * infile;
int count;
if (!(infile = fopen(name, "r"))) {
alarm(TIMEOUT);
fprintf(stderr, "Unable to open GIF file %s, %d\n", name, errno);
fflush(f);
alarm(0);
return -1;
}
PrintHeader(f,'g');
copy(infile,f); /* prints the page */
alarm(TIMEOUT);
fclose(infile);
alarm(0);
return 0;
}
- 函数名:int DoGif(FILE *f, char *name)
- 参数:
- 参数 1:文件流 FILE 结构指针,用于表示客户连接的文件流指针。用于写入文件信息数据。
- 参数 2:客户请求的文件名。
发送 HTTP 协议数据头模块设计
- 根据参数的不同,发送不同的 HTTP 协议头信息。
- 函数名:int PrintHeader(FILE *f, int content_type)
- 参数:
- 参数 1:文件流 FILE 结构指针,用于表示客户连接的文件流指针。用于写入 HTTP协议数据头信息。
- 参数 2:信息类型,用于确定发送的 HTTP 协议数据头信息。
- 算法
- 函数定义为:int PrintHeader(FILE *f, int content_type)
- 发送请求成功信息:HTTP/1.0 200 OK。
- 根据文档类型发送相应的信息:fprintf(),函数中的第一个参数 f 为客户连接文件流句柄。
int PrintHeader(FILE *f, int content_type)
{
alarm(TIMEOUT);
fprintf(f,"HTTP/1.0 200 OK\n");
switch (content_type)
{
case 't':
fprintf(f,"Content-type: text/plain\n");
break;
case 'g':
fprintf(f,"Content-type: image/gif\n");
break;
case 'j':
fprintf(f,"Content-type: image/jpeg\n");
break;
case 'h':
fprintf(f,"Content-type: text/html\n");
break;
}
fprintf(f,"Server: uClinux-httpd 0.2.2\n");
fprintf(f,"Expires: 0\n");
fprintf(f,"\n");
alarm(0);
return(0);
}
- 发送服务器信息:
fprintf(f,"Server: AMRLinux-httpd 0.2.4\n");
- 发送文件过期为永不过期:
fprintf(f,"Expires: 0\n");
主程序设计
int main(int argc, char *argv[])
{
int fd, s;
int len;
volatile int true = 1;
struct sockaddr_in ec;
struct sockaddr_in server_sockaddr;
pthread_t th_key;
void * retval;
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGALRM, sigalrm);
chroot(HTTPD_DOCUMENT_ROOT);
printf("starting httpd...\n");
printf("press q to quit.\n");
// chdir("/");
if (argc > 1 && !strcmp(argv[1], "-i")) {
/* I'm running from inetd, handle the request on stdin */
fclose(stderr);
HandleConnect(0);
exit(0);
}
if((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
perror("Unable to obtain network");
exit(1);
}
if((setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&true,
sizeof(true))) == -1) {
perror("setsockopt failed");
exit(1);
}
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(SERVER_PORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(s, (struct sockaddr *)&server_sockaddr,
sizeof(server_sockaddr)) == -1) {
perror("Unable to bind socket");
exit(1);
}
if(listen(s, 8*3) == -1) { /* Arbitrary, 8 files/page, 3 clients */
perror("Unable to listen");
exit(4);
}
pthread_create(&th_key, NULL, key, 0);
/* Wait until producer and consumer finish. */
printf("wait for connection.\n");
while (1) {
len = sizeof(ec);
if((fd = accept(s, (void *)&ec, &len)) == -1) {
exit(5);
close(s);
}
HandleConnect(fd);
}
pthread_join(th_key, &retval);
}
功能说明
- 系统的总入口,也是系统的主要控制函数。分别完成如下功能:
建立环境设置。
设置信号处理方式。
建立侦听 TCP 流方式 SOCKET 并绑定 80 端口。
建立连接侦听及客户连接处理调用主循环。
算法流程图
实验过程中遇到的问题
问题:
- 在按照实验指导书中使用make编译时,出现无法编译的问题。
解决:
- 找到了原因,是拷代码时没有将一个Rules.mak的MAK类型文件一起拷过来,导致make命令不能使用
问题:
- 按照实验步骤,先搭建ARM环境,统一ARM,主机和虚拟机三者保证它们在同一网段之后,就可以共享文件夹了,然后修改PATH变量,保证armv4l-unknown-linux-gcc工具的使用,然后进入测试代码的文件夹,make 一下后发现本应该出现下图的界面,但是最后一行自动编译指令变成了乱码
解决:
- 手动输入了最后一行:armv4l-unknown-linux-gcc -o http httpd.o copy.o -lpthread问题就解决了,ls一下,发现httpd存在于文件夹目录下了,然后将它使用NFS服务下载到开发板上,并运行它。最后在我们的本机浏览器上输入实验板的IP地址
实验心得
本次实验是基于实验1环境的搭建下进行得实验,在实验中我自以为熟络实验环境搭建,只想着同一网段实现文件的共享,却忘记将armv4l工具放入共享文件夹,也没有设置PATH变量,导致实验出现错误,本来补救是很简单的事,但是在紧迫的时间中对我和小伙伴增加了压力,怎么实验做一步错一步,老是得不到应该出现的反馈,这个时候团队合作精神就非常重要了,不要慌张也不要彼此责备,包括后面两个人实验报告的合作也一样,这次实验除了对WEB服务器的了解,更多的事我对团队合作的理解。