1. 霍霍哈嘿,这协议这协议!
一看到协议两个字,不要回顾上世纪和上上世纪的那段历史,也不要关注劳资纠纷和经济黑幕,虽然这些和协议有关,可不是今天的话题,今天动点真格的,谈技术层面的!
话说小甲一天遇到个外星人,外星人内急,对者小甲比手划脚打听W.C.的坐标,小甲不明白,以为要被绑架,撒丫子就跑,外星人一看,得,地球人有礼貌,还给带路,就紧跟小甲,这结果是@#$^&*(,...@@! 问题在哪,语言不通啊!
换个角度看这个问题,外星人叫Client,小甲外号叫Server,他们在Network上相遇,Client向Server发起请求,Server对Client作出响应。我们把这种第三类接触叫网络通讯,简称网通:-P
事后小甲和外星人在银河系法庭得到庭外和解,并且商定打听W.C,就摸鼻子,去餐厅就跺三下脚,去网吧就原地转两圈,去超市就卧倒10秒钟,诸如此类,等等等等,果然就再也没出过岔子,他们管这个叫协议。
HTTP就是在NetWork上碰面后约定的协议中的一种,大家通常管这种协议又叫做超文本传输协议。超文本是什么?网页呗。
这次外星人向小甲要网页,外星人说:
GET /demo.htm HTTP/1.0/r/n
Host: 192.168.1.1/r/n
Referer: http://192.168.1.1/demo.htm/r/n
User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)/r/n
Accept: */*/r/n
Connection: Keep-Alive/r/n
/r/n
小甲就给了外星人demo.htm, 他是这样说的:
HTTP/1.0 200 OK/r/n
Content-Type: text/plain/r/n
/r/n
THIS IS A DEMO PAGE!
2. 先要在Network上相遇
继续小甲和外星人的第三类接触。一个问题,外星人是怎么来地球的?
步骤:
a. 购买飞船。
b. 坐上飞船,发射,咻~~~
c. 来到地球。
动作分解:
a. 购买并准备了叫作SOCKET的飞船。
b. 将导航图SOCKADDR_IN输入到飞船。
c. connect到时空隧道,好了,咻~~~,来到地球。
看看地球人小甲是怎样迎接外星人的。
步骤:
a. 建造停机坪
b. 做好对接准备
c. 等到外星人到来
d. 对接成功,欢迎外星人光临地球!
动作分解:
a. 建造SOCKET停机坪,与外星人飞船用的一样材质,这叫与宇宙接轨。
b. 还记得SOCKADDR_IN吧,这里要准备对接端口了,与外星人时空隧道的端口一样。
c. 停机坪SOCKET绑定(bind)好对接端口SOCKADDR_IN后,就等着(listen)吧.
d. 外星人来了,欢迎(accept)来到地球!
整个过程不复杂吧,来看看具体实施过程,这回从Server小甲说起:
//1 .创建SOCKET
SOCKET _socket = ::socket( PF_INET, SOCK_STREAM, 0 );
if ( _socket == INVALID_SOCKET )
return E_ERROR ;
//2. 绑定端口
SOCKADDR_IN sa;
sa.sin_family = AF_INET;
sa.sin_addr.S_un.S_addr = INADDR_ANY;
sa.sin_port = htons( _port );
if ( ::bind( _socket, (sockaddr*)&sa, sizeof(sa) )
== INVALID_SOCKET ) {
_socket = INVALID_SOCKET;
return E_ERROR ;
}
//3. 监听
if ( SOCKET_ERROR == ::listen( _socket, 5 ) ) {
_socket = INVALID_SOCKET;
return E_ERROR ;
}
//4. 接受
SOCKET client = ::accept( _socket, NULL, NULL );
继续,再看看外星人Client的行动过程:
//1. 创建SOCKET
SOCKET _socket = ::socket( AF_INET, SOCK_STREAM, 0 );
if ( _socket == INVALID_SOCKET )
return E_ERROR ;
//2. 设置连接参数
SOCKADDR_IN sockAddr; // server
memset( &sockAddr, 0, sizeof(sockAddr) );
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = inet_addr( addr ); // addr是IP地址
if ( sockAddr.sin_addr.s_addr == INADDR_NONE ) {
struct hostent *ht = gethostbyname( addr );
if ( ht == NULL ) {
::closesocket( _socket );
return E_ERROR ;
}
sockAddr.sin_addr.s_addr = ((struct in_addr *)ht->h_addr)->addr;
}
sockAddr.sin_port = htons( (u_short)port ); //port是端口
//3. 非Block方式
//::ioctlsocket( _socket, FIONBIO, 1);
//4. 连接
::connect( _socket, (struct sockaddr*)&sockAddr, sizeof(sockAddr) );
3. 来谈谈吧
当Client和Server建立连接后,可以谈的话很多,通常根据谈话的内容类别分为HTTP、FTP、SMTP等众多协议,当然也可以根据需要规定自己的协议,比如我就规划过VSIP协议(Video Service Internet PROTOCOL)。
HTTP协议的谈话过程比较简单,属于问答式的,Client按照一定格式向Server提问,Server回答Client的提问,并按照格式给出结果。
a) 从最简单的谈话开始,获取页面
举个例子说明.当TCP Client连接上WEB服务器后,发送如下字符串来获取demo.htm页面
GET /demo.htm HTTP/1.0/r/n
Host: 192.168.1.1/r/n
Referer: http://192.168.1.1/demo.htm/r/n
User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)/r/n
Accept: */*/r/n
Connection: Keep-Alive/r/n
/r/n
解释一下吧。
-- GET:获取页面的动作方式;
--/demo.htm: 要获取的页面;
--HTTP/1.0:HTTP协议版本;
--Host:主机节点地址,也就是connect连接的地址;
--Referer:引用的页面全称,比如IE地址栏输入的URL;
--User-Agent:这个不用多解释了,HTML解析器的兼容类型,也就是html的接收者;
--Accept: 可以接收何种类型文档;
--Connection:网络连接后的行为方式,或Keep-Alive或Close;
b) 进阶. 要我输入用户名密码怎么办?
是否遇到过这种情况, 请求的页面后返回"401 Unauthorized",用IE访问弹出输入用户名和密码的对话框,对这种情况怎样发送请求呢?
举例之.
在IE地址栏输入http://192.168.1.1/demo.htm,访问时弹出输入用户名和密码的对话框,这时输入用户名"root"密码"pass"(当然我知道用户名密码),OK,看到页面了; 也可这样,在IE地址栏输入http://root:pass@192.168.1.1/demo.htm,OK,直接看到页面!
来看一下是怎么做到的! Client发送的内容有些变化!
GET /demo.htm HTTP/1.0/r/n
Host: 192.168.1.1/r/n
Referer: http://192.168.1.1/demo.htm/r/n
User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)/r/n
Accept: */*/r/n
Connection: Keep-Alive/r/n
Authorization: Basic cm9vdDpwYXNz/r/n
/r/n,
注意!关键有这么一项"Authorization", 那么用户名密码root:pass又在哪里呢? 注意到"Basic cm9vdDpwYXNz"了吗? 这里root:pass经过Base64编码,变成了"cm9vdDpwYXNz"!
c) 提交表单
不用说了,提交表单地球人都知道是什么意思,如果不知道,下面也不用看了。
提交表单有两种方式:Get和Post。用Get提交的表单相当于在url上加参数,如http://192.168.1.1/demo.asp?usr=root&pwd=pass 用Post提交的表单则从URL上
看不到,保密性相对比较好。
看看是怎样工作的吧:
用Get方式:
GET /demo.asp?usr=root&pwd=pass HTTP/1.0/r/n
Host: 192.168.1.1/r/n
Referer: http://192.168.1.1/demo.asp?usr=root&pwd=pass/r/n
User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)/r/n
Accept: */*/r/n
Connection: Keep-Alive/r/n
/r/n
相对的页面是:
<form action="http://192.168.1.1/demo.asp" method="GET">
<input type="text" size="10" value="root" name="usr">
<input type="password" size="10" value="pass" name="pwd">
</form>
ASP使用Request.QueryString的方式读取表单内容
再看一下Post:
POST /demo.asp HTTP/1.0/r/n
Host: 192.168.1.1/r/n
Referer: http://192.168.1.1/demo.asp/r/n
User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)/r/n
Accept: */*/r/n
Connection: Keep-Alive/r/n
Content-Type: application/x-www-form-urlencoded/r/n
Content-Length: 17/r/n
usr=root&pwd=pass/r/n
/r/n
相对的页面是:
<form action="http://192.168.1.1/demo.asp" method="POST">
<input type="text" size="10" value="root" name="usr">
<input type="password" size="10" value="pass" name="pwd">
</form>
ASP使用Request.Form的方式读取表单内容
另外,GET用于提交少量数据,而POST用于提交大量数据,如图片等,在接下来的编程系列中我来谈谈用C/C++代码HTTP上载图片到服务器。今天先到这里了,就到这里,休息,休息一下!
附base64编码算法:
void encode( char *in, char *out )
{
int ilen = strlen(in);
assert( ilen );
int zlen = ( 3 - ilen % 3 ) % 3;
int p1len = ilen + zlen;
char *p1 = new char[p1len];
memset( p1, 0, p1len );
strncpy( p1, in, ilen );
int p2len = p1len / 3 * 4;
char *p2 = new char[ p2len + 1 ];
// Loop 24 bit groups
int bit[24]; // 0 / 1
int j = 0; // index of p2
char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for ( int i = 0; i < p1len / 3; i ++ )
{
bit[ 0] = ( p1[i * 3 ] & 0x80 ) ? 1 : 0;
bit[ 1] = ( p1[i * 3 ] & 0x40 ) ? 1 : 0;
bit[ 2] = ( p1[i * 3 ] & 0x20 ) ? 1 : 0;
bit[ 3] = ( p1[i * 3 ] & 0x10 ) ? 1 : 0;
bit[ 4] = ( p1[i * 3 ] & 0x08 ) ? 1 : 0;
bit[ 5] = ( p1[i * 3 ] & 0x04 ) ? 1 : 0;
bit[ 6] = ( p1[i * 3 ] & 0x02 ) ? 1 : 0;
bit[ 7] = ( p1[i * 3 ] & 0x01 ) ? 1 : 0;
bit[ 8] = ( p1[i * 3 + 1] & 0x80 ) ? 1 : 0;
bit[ 9] = ( p1[i * 3 + 1] & 0x40 ) ? 1 : 0;
bit[10] = ( p1[i * 3 + 1] & 0x20 ) ? 1 : 0;
bit[11] = ( p1[i * 3 + 1] & 0x10 ) ? 1 : 0;
bit[12] = ( p1[i * 3 + 1] & 0x08 ) ? 1 : 0;
bit[13] = ( p1[i * 3 + 1] & 0x04 ) ? 1 : 0;
bit[14] = ( p1[i * 3 + 1] & 0x02 ) ? 1 : 0;
bit[15] = ( p1[i * 3 + 1] & 0x01 ) ? 1 : 0;
bit[16] = ( p1[i * 3 + 2] & 0x80 ) ? 1 : 0;
bit[17] = ( p1[i * 3 + 2] & 0x40 ) ? 1 : 0;
bit[18] = ( p1[i * 3 + 2] & 0x20 ) ? 1 : 0;
bit[19] = ( p1[i * 3 + 2] & 0x10 ) ? 1 : 0;
bit[20] = ( p1[i * 3 + 2] & 0x08 ) ? 1 : 0;
bit[21] = ( p1[i * 3 + 2] & 0x04 ) ? 1 : 0;
bit[22] = ( p1[i * 3 + 2] & 0x02 ) ? 1 : 0;
bit[23] = ( p1[i * 3 + 2] & 0x01 ) ? 1 : 0;
p2[j++] = base64[ bit[0] * 32 + bit[1] * 16 + bit[2] * 8
+ bit[3] * 4 + bit[4] * 2 + bit[5] ];
p2[j++] = base64[ bit[6] * 32 + bit[7] * 16 + bit[8] * 8
+ bit[9] * 4 + bit[10] * 2 + bit[11] ];
p2[j++] = base64[ bit[12] * 32 + bit[13] * 16 + bit[14] * 8
+ bit[15] * 4 + bit[16] * 2 + bit[17] ];
p2[j++] = base64[ bit[18] * 32 + bit[19] * 16 + bit[20] * 8
+ bit[21] * 4 + bit[22] * 2 + bit[23] ];
}
p2[j] = '/0';
for ( int n = 0; n < zlen; n ++ )
p2[--j] = '=';
strcpy(out, p2);
delete [] p1;
delete [] p2;
}