客户端主动发起请求,服务器被动接收信息。
而客户端是发起链接,建立链接的过程叫做三次握手。
客户端询问服务器是否可以和你建立链接。
服务器回应客户端,可以建立链接,什么时候建立呢?
客户端回应服务器,就现在。
三次握手完成,然后链接被建立,操作系统需要维护链接,那么还要创建对应的数据结构。对应的接口是connect系统调用,也就是说connect触发了三次握手。
主动发起链接的客户端,但是客户端,服务器都可能主动断开链接。这个过程叫4次挥手。
例如,客户端主动断开。
向服务器发送FIN标志位报文,服务器向客户端发送ACK确认,至此客户端到服务器这条通到被关闭。服务器发送FIN标志位报文,客户端发送ACK确认,然后服务器到客户端这条通道被关闭。
四次挥手完成,首先关闭打开的链接,然后操作系统会清理刚才创建的数据结构。对应的接口是close,当谁调用close即就触发了四次挥手。
Tcp双方通信,地位对等。
TCP是全双工即服务器可以向客户端收发数据,服务器也可以收发数据。
管道是一种半双工,他只能单向传输。即一个发数据,一个接受数据。
在创建网络sock套接字时,以Tcp为例
操作系统会创建出struct socket结构体他是文件系统层面的,与进程相关连起来。
Tcp协议,同时还会有具体的tcp文件描述符。那么怎么将它和文件相关连起来呢。
通过sock_map_fd函数创建新文件,将他和sk与tcp_sock关联起来。
创建一个套接字文件描述符时,先创建了newfile,然后与socket关联起来,socket里面的file回指到newfile中,sk指向一个具体的tcp_sock。
所以在read/recv的时候,传入文件描述符,读取到socket中的sk,从他的接收队列里面读取到了数据。
应用层充满了协议,协议是一种 “约定”。 socket api的接口, 在读写数据时, 都是按一连串的比特位来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?比如说结构体数据。
网络发送采用的是序列化方式,可以简单的理解成将它们全部揉在一起。多变一,而接收的时候采用反序列化,也就是一变多。
我们所写的protocol也就是一种协议。我们是在应用层编写的,使用的是传输层tcp协议的接口。我只管用,并不关心它的底层细节是怎么样的。上层永远是使用下层的接口。
Protocol.hpp:
#ifndef __PROTOCOL_HPP__
#define __PROTOCOL_HPP__
#include
typedef struct request{
int x;
int y;
char op;
}request_t;
typedef struct reponse{
int code;
int result;
}reponse_t;
#endif
Server.hpp:
#ifndef _SERVER_HPP_
#define _SERVER_HPP_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"protocol.hpp"
using namespace std;
class server{
private:
int port;
int lsock;
public:
server(int _p)
:port(_p)
,lsock(-1)
{
}
void initServer()
{
lsock=socket(AF_INET,SOCK_STREAM,0);
if(lsock < 0)
{
cerr<<"socket error"<<endl;
exit(1);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
//创建套接字就是创建那批数据结构,而bind就是往结构里面填充ip与端口号
if(bind(lsock,(struct sockaddr*)&local,sizeof(local))<0)
{
cerr<<"bind err"<<endl;
exit(2);
}
if(listen(lsock,5)<0)
{
cerr<<"listen error"<<endl;
}
}
void cal(int sock)
{
//短链接来完成计算,执行一来一回,断开。
//计算结果返还回去
reponse_t rp{
4,0};
//收到请求开始计算
request_t rq;
ssize_t s=recv(sock,&rq,sizeof(rq),0);
if(s > 0)
{
rp.code=0;
switch( rq.op )
{
case '+':
rp.result=rq.x + rq.y;
break;
case '-':
rp.result=rq.x - rq.y;
break;
case '*':
rp.result=rq.x * rq.y;
break;
case '/':
if(rq.y!=0)
{
rp.result=rq.x/rq.y;
}
else
{
//除0
rp.code=1;
}
break;
case '%':
if(rq.y!=0)
{
rp.result=rq.x % rq.y;
}
else{
//模0
rp.code=2;
}
break;
default:
rp.code=3;
break;
}
}
send(sock,&rp,sizeof(rp),0);
close(sock);
}
void start()
{
struct sockaddr_in peer;
for(;;)
{
socklen_t len=sizeof(peer);
int sock=accept(lsock,(struct sockaddr*)&peer,&len);
if(sock < 0)
{
cerr<<"accept error"<<endl;
continue;
}
if(fork()==0)
{
if(fork() > 0)
{
//子进程直接退出
exit(0);
}
//孙子执行任务,虽然他成为孤儿进程,但是会被1号进程所领养
close(lsock);
cal(sock);
exit(0);
}
close(sock);
//子进程直接退出,我就等待他
waitpid(-1,nullptr,0);
}
}
~server()
{
close(lsock);
}
};
#endif
Server.cc
#include"Server.hpp"
void Menu(string str)
{
cout<<"Usage: "<<endl;
cout<<str<<"server port"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Menu(argv[0]);
exit(1);
}
server *cp=new server(atoi(argv[1]));
cp->initServer();
cp->start();
delete cp;
return 0;
}
Client.hpp
#ifndef _CLIENT_HPP_
#define _CLIENT_HPP_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"protocol.hpp"
using namespace std;
class client{
private:
string ip;
int port;
int sock;
public:
client(string _ip,int _p)
:ip(_ip)
,port(_p)
,sock(-1)
{
}
void initClient()
{
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
cerr<<"socket errot"<<endl;
exit(0);
}
}
void start()
{
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(port);
server.sin_addr.s_addr=inet_addr(ip.c_str());
if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0)
{
cerr<<"connect error"<<endl;
exit(2);
}
reponse_t rp;
request_t rq;
cout<<"please enter data1:";
cin>>rq.x;
cout<<"please enter data2:";
cin>>rq.y;
cout<<"op:";
cin>>rq.op;
send(sock,&rq,sizeof(rq),0);
recv(sock,&rp,sizeof(rp),0);
cout<<"code:"<<rp.code<<endl;
cout<<"result:"<<rp.result<<endl;
}
~client()
{
close(sock);
}
};
#endif
Client.cc
#include"Client.hpp"
void Menu(string str)
{
cout<<"Usage: "<<endl;
cout<<str<<"server ip and port"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Menu(argv[0]);
exit(1);
}
client *cp=new client(argv[1],atoi(argv[2]));
cp->initClient();
cp->start();
delete cp;
return 0;
}
Client端由于是短链接,所以他发出信息,接收到回来的消息就退出了。
tcpdump,可以抓取tcp,udp,可以抓icmp,基本传输层的时候都可以抓,
先启动服务器,在启动客户端,开始三次握手,准备连接(未发数据)
客服端发数据,服务器接收并处理数据,然后服务器先断开。
像刚才的protocol.hpp中就可以认为我们做的某种协议规定,基于应用层的自定义协议。一个个结构体,send将他们看做一个个二进制流,recv将他们有看做一个个结构体,所以也完成了序列化与反序列化。但是结构体有一个隐患,由于客户端和服务器的主机不同,结构体内存对齐数,主机位数,指针大小,都不一样。
他是应用层的超文本传输协议
url就是俗称的网址。
服务器资源里,第一个’/’,他叫做web根目录,但不能等同于根目录,他是任意文件夹的根目录。即就是把这个文件(数据)从服务器拿到本地,让浏览器解释。
像’?’ ’ / ',url中的符号,像这样的字符,他被url采用特殊含义去理解,因此这些字符是不能随意出现的,假如哪个参数中不得已要带,我们要对他进行转义操作。
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。
还有专门的urlencode工具。可以进行在线编码。
urlencode就是编码。
urldecode就是解码。
Tcp已经在传输层成功的建立了链接,不需要http多此一举。
Tcp有状态,三次握手后,建立链接,进入establish。断开链接,四次挥手时time_wait,close_wait等。也不需要http多此一举。
也就是说,http只管,客户端发送一个request,服务器收到然后返回一个response,客户端进行解释,就完成了他的工作。但是这时又有个疑惑,他只管这些,那我们登录某个网站之后,在一段时间内,是不需要再次重新登录的,这又是怎么回事呢?其实是两个东西在起作用cookie和session。
tcp是面向字节流的,那么上层怎么知道他把request数据发送完,或者将response数据解析完了呢?
也就是说,当读到空行,就说明我们将http协议属性读取完毕,接下来就是他的正文。从空行开始,由于报头部分已经读取到正文的长度,所以直接读取对应的val就完成了。
所使用的工具叫Fiddler,其原理比较简单。通过这张图即可表明
抓取的request
抓取的response
他们都可以和刚才画的两张简图对应起来。
之前提到过这个工具,百度服务器的80端口是对外提供服务的,所以直接连接.
写一行请求行,回车发出去。
不写报头,在回车一次。
就发出去了空行。由于是get方法就不发正文了。
其他不必多说,和简图可以对应,最主要的是这个正文长度。底下很多很多,Content就记录了他的字节
在浏览器查看后
html被浏览器解释过后,就成了看到的这样。
当在任意登陆窗口,登录账号和密码。post传参数据在正文中,get方法传参数据在url中。
使用fiddler抓包,竟然可以看到正文中的账号密码,没有加密
request:
response立刻返回:
所以get,post方法的区别:
get通过url传参,post通过正文传参。get方法不安全,post方法也不是很安全,但是post相对于get更加私密一些。
url是有长度的,正文部分理论是没有长度限制的,因为有个Content——length标识,但是电脑资源有限,那么他也肯定是有限的。
HTTP状态码
4xx,其实是客户端出错,因为他是“不合理的请求”。
使用wget工具
服务器会有反爬机制,判断user-agent是否存在,有说明你是浏览器用户,没有就说明机器在访问。即使伪装user-agent也会被发现规律性来防止。虽然永远是进攻的人占取主动权,但千万不要乱爬,否则会被带走的。
http虽然是无状态,但是有cookie保存信息。
比如看视频看了第一个视频,继续看第二个,不需要登录,就是因为cookie保存了信息,提升用户体验。
原理
那么他是内存级文件,还是硬盘级文件呢?
内存级就是浏览器关闭,重新打开就需要再次输入。
硬盘级就是即使关机,重新打开依旧不需要输入。
显然是硬盘级。
但是很不安全,假如电脑中毒,直接copy我的cookie文件,就可以不用登陆,访问我曾经的资源。
所以他又引出了一个概念,session。
虽然这时依旧有可能泄漏,但他最多可以和我访问一个资源,他是看不见我的用户名,密码等敏感信息,因为此时我的一切都在服务器保存着!存在本地客户端的只有一个sid。相对比较安全。
假如此时他想要更改你的qq账号密码,需要窃取腾讯服务器,几率机会为零,除非你的账号密码过于简单,就算由于简单被盗取更改,还可以查看ip来查看你的常驻ip。常驻ip还一样,那就只能申诉了。