一.写一个发布静态资源的http服务器(静态资源就是已经存好的文件)
1.只用写服务端,客户端就是浏览器。解析浏览器发过来的数据,服务端组装一个应答报文进行回复。具体流程如下:
2.连接后(tcp连接),浏览器发送的请求报文:
一般的网站都会设置一个名叫Index.html文件,当只访问IP没加后缀(如:127.0.0.1 加后缀:127.0.0.1/a.jpg)就将这个页面返回。就是访问页面127.0.0.1与127.0.0.1/index.html等价。
3.myhttp组装一个应答报文发给浏览器(这里先只写前三个):
在接下来的代码中我将组装以下部分:
4.代码实现简单来说就是:你要访问哪个文件,我在解析文件名,拼接好路径,open在打开它,得到文件大小,组装好报文,再把报头发过去,最后把文件内容发过去。
这里我先将代码中出现的三个函数进行解释:
①off_t lseek(int fd, off_t offset, int whence);-->计算文件偏移量
fd :文件描述符,即你想要操作的文件的描述符。
offset :偏移量,以字节为单位。
whence :有以下三种
SEEK_SET :读写偏移量将指向 offset 字节位置处(从文件头部开始算);
SEEK_CUR :读写偏移量将指向当前位置偏移量 + offset 字节位置处, offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
SEEK_END :读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。
②int sprintf(char *str, const char *format, ...);-->将...所属的format格式插入到str中
str :指向目标字符串的指针, sprintf 将格式化后的字符串存储在这里。
format :格式字符串,定义了后续参数如何被格式化和插入到最终的字符串中。
... :可变参数列表,这些参数将按照 format 字符串中指定的格式被转换并插入到字符串中。
③char *strcat(char *dest, const char *src);-->j将一个字符串追加到另一个字符串的结尾
dest :指向目标字符串的指针, src 字符串的内容将被追加到这个字符串的末尾。
src :指向源字符串的指针,其内容将被复制到 dest 字符串的末尾。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <fcntl.h>//open
#define PATH "/home/stu/mycodes/day9"//这里是我自己的当前路径
int socket_init()//函数定义
char*get_filename(char buff[])
{
//用strtok_r实现(线程安全的函数)
char*ptr=NULL;
char*s=strtok_r(buff," ",&ptr);//接收recv的信息,接收GET
if(s==NULL)
{
return NULL;
}
printf("请求方法:%s\n",s);
s=strtok_r(NULL," ",&ptr);//从上次接收完的地方继续接收,这次接收的是文件名
return s;
}
void*fun(void*arg)
{
int *p=(int*)arg;
int c=*p;
free(p);
while(1)//长连接
{
char buff[1024]={0};
int n=recv(c,buff,1023,0);//接收浏览器发来的请求报文http
if(n<=0)
{
break;
}
printf("recv:%s\n",buff);
char*filename=get_filename(buff);//将提取请求报文的文件名交给函数实现
if(filename==NULL)
{
send(c,"404",3,0);//这里应该写个报文发送404,这里简化了直接发送404
break;
}
//提取文件名成功了,要访问这个文件,这里先将文件放在当前路径,也可以放在其他地方
char path[128]={PATH};//组装绝对路径
//将文件名拼接到PATH后面
if(strcmp(filename,"/")==0)//只写了IP地址,后面默认是index.html
{
strcat(path,"/inddex.html");//将/index.html拼接到path后面
}
else
{
strcat(path.filename);
}
printf("path=%s\n",path);//得到路径
int fd=open(path,O_RDONLY);//打开文件这里只读打开
if(fd==-1)
{
send(c,"404",3,0);//这里要改为报文
break;
}
int filesize=lseek(fd,0,SEEK_END);//获得文件大小,为下面组装报头准备,lseek函数根据起始位置和末尾位置的偏移量计算大小
lseek(fd,0,SEEK_SET);//计算完大小后,让文件回到起始位置
//要给浏览器发送数据,组装一个应答报文,发送报头
char head[128]={"HTTP/1.1 200 OK\r\n"};
strcat(head,"Server:myhttp\r\n");
//拼接文件的大小,将整形转为字符串拼接到一起。可以使用sprintf-->输出到一个字符数组里面,它会帮我们进行格式转换,输出格式写%d,后面给个整形,一输出到一个字符数组里面就自动转换为字符串。
sprintf(head+strlen(head),"Content-Length:%d\r\n",filesize);//head+strlen(head)是在head的结尾加上后面的%d
strcat(head,"\r\n");
send(c,head,strlen(head),0);//流式服务可以一部分一部分的发,发送http应答报文的报头给浏览器
//循环发送文件数据部分直到发完。
char data[1024];
int num=0;//统计每次读到了多少
while((num=read(fd,data,1024))>0)//至少读了一个字节
{
send(c,data,num,0);
}
close(fd);
}
close(c);
}
int main()
{
int sockfd=socket_init();
if(sockfd==-1)\
{
exit(1);
}
while(1)
{
struct sockaddr_in caddr;
int len=sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<=0)
{
continue;
}
printf("accept c=%d\n",c);
pthread_t id;
int *p=(int *)malloc(sizeof(int));
if(NULL==p)
{
close(c);
continue;
}
*p=c;
pthread_create(&id,NULL,fun,(void*)p);
}
}
int socket_init()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
exit(1);
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(80);//http默认80端口
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
printf("bind err\n");
exit(1);
}
res=listen(sockfd,10);
if(res==-1)
{
return -1;
}
return sockfd;
}
5.这里的favicon.ico就是请求图标(系统会默认请求)图标名称就是:favicon.ico
6.当连接html时系统会请求文档,当html文档中有图片,则还会请求连接图片,如:
7.wget www.baidu.com-->可以拿到百度的网址wget就是拿网址