HTTP协议

 一、URL:平时我们俗称的 "网址" 其实就是说的 URL

1、

1',http://   协议方案名(还可以是其他协议,改HTTP)
2',user:pass  登录信息认证
3',www.example.jp  服务器地址(域名,本质上是ip地址
4',80  服务器端口号(如果没有说明端口号,默认是80)(:加具体端口号就可以访问端口号)
5',dir/index.html  带层次的文件路径(如果没加任何路径,默认为/,表示根目录,不是操作系统的根目录,是服务器程序的根目录;把什么设置为根目录,取决于代码。可以把本地的任何目录作为根目录。
比如:
假如我们实现一个http服务器,然后把/home/test/http/作为http服务器的根目录,此时相当于通过浏览器所能访问的文件就局限在/home/test/http/目录之中,(把可以让服务器访问的目录统一放到同一个目录下,避免所有文件都能被访问到,不安全)。假设,/home/test/http/index.html我们的url就可以写成:http://[ip]:[port]/index.html(index.html是相对于根目录/home/test/http/取的相对路径的文件)
6',uid=1  查询字符串(参数)参数的组织形式是按照键值对的方式
7',ch 1  片段标识符(“回到顶部”,跳转到标识符对应的位置,把标识符对应的位置显示到屏幕上)

2、urlencode和urldecode(编码和解码)

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现. 比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义. 

转义的规则:将需要转码的字符转为16进制,然后从右到左,取4位(不⾜足4位直接处理),每2位做一位,前面加上%,编码 成%XY格式。"+" 被转义成了 "%2B"。urldecode就是urlencode的逆过程。

(1)urlencode和urldecode的转换:此处说的是在线工具:输入要编码的串,选择要转化的方式。(防止字符串中出现特殊的字符,出现后要进行转义)

二、Http协议

1、协议格式:结合抓包工具,记录请求的状态

请求:

A、get请求的包

只打开一次浏览器,但是浏览器存在多次交互(用抓包工具可以看到)

我们尝试抓一次包(百度),左半部分为访问百度的主页得到的内容,右边的上面为浏览器给客户端发送的请求(服务器端)的格式,查看row中的内容:

分成若干行: (主要描述是一个什么样的HTTP,是一个什么样的浏览器)

(1)GET行:GET表示http请求的方法(http协议中约定的客户端和服务器端交互方式有很多种)GET空格后面为请求的URL地址是什么样的;地址后面空格后的内容HTTP/1.1叫做http协议的版本号
(2)首行之后的每一行,一行一行的东西都叫做协议头Header每一行之间通过换行分割,Header中的每一部分都按照键值对的方式进行存储。
(3)User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134:可以通过版本号判断是由什么方式打开的页面。
Windows:操作系统的版本
 NT 10.0:内核的版本
Win64; x64:64位的平台底下
AppleWebKit:浏览器内核
Chrome:浏览器的名字
Safari:浏览器的名字

(4)Cookie字段:服务器给浏览器留下的本地的数据,cookie里面的数据保存了访问网站的临时信息,登路服务器后,会分配一个身份信息,是一个数据编号,下次再访问时,身份信息会根据cookie字段统计到服务器上,可以完成一个身份识别的过程。浏览器保存数据,返回浏览器。若是换成另一个网站,则此网站保存的身份信息则不适用了,cookie以域名为主,不同域名的cookie不同。还可以保存上一次访问的时间和访问的次数。cookie字段的长度也是有限的,不同浏览器对cookie长度的限制不同。

B、post请求的包:一般用于登录页面时抓到的包:body部分与Content_Length有关。

 第一行(HTTP首行):方法名(POST)+空格+后面跟要访问的RURL路径+空格+版本号

首行以下的每一行都是一个键值对,即Heder
若干个Header之后有一个空行:Header和body部分的分割符
空行下面为post请求的body部分:用户名和密码(明文密码)(&分隔若干键值对,每个键值对通过等号来分隔)

C:总结:

HTTP请求:

首行(方法 url 版本号)

若干行Header(User-Agent,Cookie)

空行(Header和body的分割)

body(这部分格式通常和URL中的查询字符串是类似的)

 

HTTP响应:fildder抓包后右边的下部分为响应,点击下半部分的row,显示响应部分的格式:会出现乱码,因为是有些是压缩过的,点一下解压缩即可。

 

(1)首行:包含版本号(HTTP/1.1 ),状态码(200):状态码:表示这次请求是成功还是失败,失败返回失败的原因。200是最常见的状态码,表示请求成功。  OK:表示对状态码的重复
a、常见状态码:404:这个页面没找到;504:服务器出现问题;302:跳转
b、常见状态码的规则:
2开头的:这次请求成功了;
3开头:进行重定向
4开头:访问的资源没法获得到,页面不存在
5开头:服务器存在问题
(2)首行之后的若干行,相当于Header。每行都是一个键值对,每行之间用换行分割,键值对内部由冒号分割
(3)Header后的空行作为Header结束的表示,分割响应的body,响应的body部分存放网站html的代码(html通过标记性的语言描述结构:右键,查看html),网站一般是通过html描述的
(4)Content-Length描述body部分应该有多长(应该明确指出)
(5)Set-Cookie请求中的Cookie通过Set-Cookie设置到浏览器中
(6)Content-Type:文本类型(charset字符集:一个汉字占几个字节取决于编码方式:UTF-8下为变长,具体占几个字节和具体是个什么样的字有关。    gbk约定一个汉字占两个字节(VS)。
总结:
http响应:
首行(版本号 状态码 状态码描述)
若干行Header
空行
body(往往就是网页的html)

3、http的应用:简单的http(http协议都是基于tcp实现的)

server.c

# include<stdio.h>
# include<string.h>
# include<stdlib.h>
# include<unistd.h>
# include<sys/socket.h>
# include<netinet/in.h>
# include<arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
int main(int argc,char *argv[]){
   if(argc!=3){
     printf("Usage ./server[ip][port]\n");
   }
   int fd=socket(AF_INET,SOCK_DGRAM,0);
   if(fd<0){
     perror("socket");
    return 1;
   }
   sockaddr_in addr;
   addr.sin_family=AF_INET;
   addr.sin_addr.s_addr=inet_addr(argv[1]);
   addr.sin_port=htons(atoi(argv[2]));
   //绑定端口号
   int ret=bind(fd,(sockaddr*)&addr,sizeof(addr));
   if(ret<0){
     perror("bind");
     return 1;
   }
   ret=listen(fd,5);
   if(ret<0){
      perror("listen");
      return 1;
   }
  //尝试读取数据
   while(1){
     sockaddr_in peer;
     socklen_t len=sizeof(peer);
     printf("wait connect\n");
     int new_fd=accept(fd,(sockaddr*)&peer,&len);
     if(new_fd<0){
     }
     printf("before read\n");
     char buf[1024*10]={0};
     const char *msg="hello world";
     read(new_fd,buf,sizeof(buf)-1);
     printf("%s\n",buf);
     char resp[1024*10]={0};
    //构造首行,首行包括版本号、状态码……
    size_t offset=sprintf(resp,"HTTP/1.1 200 OK\n");
    //第二行,header部分,需要长度
     offset+=sprintf(resp+offset,"Content-Length:%lu\n",strlen(msg));//size_t用%lu
    //空行
     offset+=sprintf(resp+offset,"\n");
   //body
     offset+=sprintf(resp+offset,"%s",msg);
     printf("before write\n");
     write(new_fd,resp,strlen(resp));//将构造好的http写到socket里面//需要将字符串构造成http格式的响应按
照http的格式
   }
}

前端开发三剑客:

HTML描述了一个网页的骨架

CSS描述了一个网页的详细样式(颜值担当)

JavaScript描述了一个网页如何和用户动态交互

改进:添加一个一级标题:

 # include<stdio.h>
# include<string.h>
# include<stdlib.h>
# include<unistd.h>
# include<sys/socket.h>
# include<netinet/in.h>
# include<arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
int main(int argc,char *argv[]){
if(argc!=3){
     printf("Usage ./server[ip][port]\n");
   }
   int fd=socket(AF_INET,SOCK_DGRAM,0);
   if(fd<0){
     perror("socket");
    return 1;
   }
   sockaddr_in addr;
   addr.sin_family=AF_INET;
   addr.sin_addr.s_addr=inet_addr(argv[1]);
   addr.sin_port=htons(atoi(argv[2]));
   //绑定端口号
   int ret=bind(fd,(sockaddr*)&addr,sizeof(addr));
   if(ret<0){
     perror("bind");
     return 1;
   }
   ret=listen(fd,5);
   if(ret<0){
      perror("listen");
      return 1;
   }
  //尝试读取数据
  while(1){
     sockaddr_in peer;
     socklen_t len=sizeof(peer);
     printf("wait connect\n");
     int new_fd=accept(fd,(sockaddr*)&peer,&len);
     if(new_fd<0){
     }
     printf("before read\n");
     char buf[1024*10]={0};
     read(new_fd,buf,sizeof(buf)-1);
     printf("%s\n",buf);
     char resp[1024*10]={0};
     const char*msg="<h1>hello world</h1>";//添加一个一级标题
    //构造首行,首行包括版本号、状态码……
    size_t offset=sprintf(resp,"HTTP/1.1 200 OK\n");
    //第二行,header部分,需要长度
    offset+=sprintf(resp+offset,"Content-Length:%lu\n",strlen(msg));//sizie_t用%lu
//空行
    offset+=sprintf(resp+offset,"\n");
   //body
    offset+=sprintf(resp+offset,"%s",msg);
    printf("before write\n");
    write(new_fd,resp,strlen(resp));//将构造好的http写到socket里面//需要将字符串构造成http格式的响应按
   //               照http的格式
      }
}
     const char*msg="<h1>hello world</h1>";//添加一个一级标题
    //构造首行,首行包括版本号、状态码……
    size_t offset=sprintf(resp,"HTTP/1.1 200 OK\n");
    //第二行,header部分,需要长度
    offset+=sprintf(resp+offset,"Content-Length:%lu\n",strlen(msg));//sizie_t用%lu
//空行
    offset+=sprintf(resp+offset,"\n");
   //body
    offset+=sprintf(resp+offset,"%s",msg);
    printf("before write\n");
    write(new_fd,resp,strlen(resp));//将构造好的http写到socket里面//需要将字符串构造成http格式的响应按
   //               照http的格式
      }
}

 

可以通过改变size_t offset=sprintf(resp,"HTTP/1.1 200 OK\n");
中的数字改变最后的状态如:将200改为404,把body去掉(变为空字符串)

printf("%s\n",buf);
     char resp[1024*10]={0};
     const char*msg=" ";
    //构造首行,首行包括版本号、状态码……
    size_t offset=sprintf(resp,"HTTP/1.1 404 NOTFOUND\n");
    //第二行,header部分,需要长度

可以通过改变size_t offset=sprintf(resp,"HTTP/1.1 200 OK\n");

中的数字改变最后的状态如:将200改为302,在header中包含location字段

 char resp[1024*10]={0};
     const char*msg=" ";
    //构造首行,首行包括版本号、状态码……
    size_t offset=sprintf(resp,"HTTP/1.1 302 REDIRECT\n");
    //第二行,header部分,需要长度
    offset+=sprintf(resp+offset,"Content-Length:%lu\n",strlen(msg));//sizie_t用%lu
  offset+=sprintf(resp+offset,"Location:%s\n","http://www.baidu.com");//重定向到哪个位置上(通过302的方式跳转到百度)
//空行

其他应用层协议:

(1)HTTPS:基于HTTP做了加密(HTTP协议全是明文的,不安全),有浏览器进行解密,还原。使数据更安全。

(2)FTP:文件传输

(3)SSH:基于TCP的应用层协议

自定义协议:

(1)qq客户端和服务器端之间遵循qq的应用层协议

(2)微信客户端和服务器端之间遵循微信的应用层协议

(3)滴滴打车客户端和服务器端之间遵循滴滴打车的应用层协议

总结:
一:socket API
A:只能连接一个
UDP:
服务器端
1、socket创建文件描述符,根据第二个参数决定是TCP还是UDP版本,创建socket也可能会失败,socket就是一个文件描述符,文件描述符就是文件描述符表的一个下标。文件描述符表是有上限的,可以通过ulimit -a来查询当前有多少个文件描述符,也可以通过ulimit来进行修改。
2、写一个udp版本的服务器,第二步需要绑定端口号,使用bind使一个文件描述符和ip地址端口号连接在一起,服务器端的ip地址和端口号就是服务器自身的ip地址和端口号。ip地址可以设置为全0,一台机子上可能涉及到多个网络接口,可能有多个ip地址,把ip地址设置为0。bind之后可能出现的问题:(1)bind可能失败(端口号已经被其他人占用了),一个端口号不能被两个进程绑定,多个进程可以绑定到一个端口号上(可以先绑定好,再fork)。一个进程可以绑定多个端口号(一个进程可以创建多个文件描述符,每个文件描述符都可以绑定在端口号上)。
3、循环读取客户端发送过来的数据,此时用到的接口号是recvfrom(在调用recvfrom的过程中,能把对端的ip地址和端口号通过参数返回回来;recvfrom应用于面向数据报的场景,专属于UDP)和sendto
客户端:
1、先创建一个socket(UDP版本的)
2、不需要绑定端口号(有可能与客户端上其他进程的端口号相冲突),由操作系统自动分配一个端口号
3、客户端发送数据给服务器,从服务器上读取返回结果
4、及时关闭用过的socket
TCP:
服务器端:
1、创建socket
2、绑定端口号(方法同UDP)
3、把socket转换成listen形式,把socket由默认的形式转换成被动形态(可以允许被对端连接)
4、进入循环:(1)accept:内核已经把连接建立好之后,把链接取到用户空间代码中去处理和链接(accept和建立链接没有关系,连接在listen调用完之后,服务器就已经允许建立连接了。在listen调用完之后,在accept调用完成之前,这个区间内任何时刻都有可能有客户端连接服务器端。只要客户端尝试连接,服务器端的内核会跟客户端进行三次握手,三次握手之后,连接建立完毕;连接建立完毕之后,服务器端的accept函数返回,返回后把建立好的连接拿到用户代码上,此时accept会返回一个新的文件描述符,这个新的文件描述符就是用来客户端和服务器端传输数据的依据。accept除了返回一个新的文件描述符之外,还会返回对端对应的ip地址和端口号。(所以服务器必须知道客户端是谁,不知道则无法返回【UDP采用recvfrom,TCP采用accept】)
send和recvfrom函数和write,read函数功能类似,只是send和recvfrom函数有更多的选项,功能更加丰富。
缺陷:同一时刻只能使用创建一个连接
B、改进方式:
1多进程版本的服务器:每次收到一个请求直接创造一个子进程,由子进程负责和客户端的交互过程:创建子进程,有了一个独立的执行流完成读写之外,父进程可以立即调用accept(能够很快的调用accept,连接处理就能很快)。
(1)缺点:处理僵尸进程。进程终止时,父进程不进行等待,就会产生僵尸进程。此时会产生矛盾:更快的调用accept和不产生僵尸进程。(忽略sigchild信号)(更复杂的方式:创建子进程,父进程等待子进程结束,子进程不直接和客户端交互,而是再创建一个孙子进程,子进程创建孙子进程之后会立刻结束,父进程会立即返回,即可调用accept,但是孙子进程还在,而且孙子进程变成了孤儿进程通过这种孤儿进程机制,在孙子进程完成调用之后,回收资源。)
(2)关闭进程的时候,父进程需要关闭,子进程也要关闭(父进程被子进程继承下来,所以同一个socket父进程打开着,子进程也打开着)。若是父进程关闭了,子进程没关闭,没问题,因为文件描述符本质上是TCP的一部分,子进程在连接客户端读写结束之后,子进程就退出了。对应的文件描述符也会被自动回收。若是子进程关闭了,父进程没关闭则不可以,因为父进程是服务器的本体,服务器一直在运行不及时关闭父进程就会出现问题。
(3)不用关闭文件描述符的情况:文件描述符使用完毕后,进程立即终止了。
2、多线程版本的服务器:每次收到一个连接后,直接创造一个线程负责交换过程:线程与线程之间地位平等,只有一个特殊的主线程(创建的第一个线程)主线程在main函数返回则会导致出错。要把对应的参数传到线程之中,把void*这样的参数线程入口函数,需要手动封装一个结构体,把结构体的指针强转成void*传递给线程入口函数,由线程入口函数把void*转给结构体,就能取到内容。定义的结构体必须放到堆上,放在栈上,主线程的循环会立即进入下一个循环,栈上的内存释放,导致非法访问内存。如果是全局或者静态变量,如果有多个客户连接即有多个线程,会导致相互之间影响。放在堆上,需要手动释放,所以处理函数在所有需要处理的都处理完成之后,应该释放。
需要关闭new_sock。
二、网络:
1、自定制协议:
(1)客户端按照何种方式发数据
(2)服务器端按照何种方式写数据
(3)序列化:把一个对象转化成字符串(发送与接收的都是字符串,字符串中会包含若干个格式化的消息,将若干个消息按照特定的格式组织成字符串。因为有多个字段,所以需要显式的指定分隔符,通过规则,把多个字段区分开,让接收端能够识别)
(4)反序列化:
2、HTTP协议:(我们可以在现有的优秀协议上进行二次开发)
(1)特点:
a、纯文本:通过肉眼可以很容易的观察
b、
c、格式很长

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xuruhua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值