|
文件: |
tobaidu.rar |
大小: |
7KB |
下载: |
下载 | |
新手之学习HTTP协议自力更生篇
工具:ethereal,vc6
文档:rfc1945(http1.0),rfc2614(http1.1)
平台:win2000 sp4/vc6 sp5
目的:
1 了解HTTTP协议
2 Windows Socket IO模式select应用
3 从理论到代码,得到一个网页.在本例中用的是的主页.
工具设置:
etheral菜单Capture->Options...->Capture Filter: 设为tcp and dst 220.181.27.5 or src 220.181.27.5 .还有Interface里要选择自己的网络接口.这样设的作用是只捕获含有指定IP的包.这里220.181.27.5是百度
关键代码如下:
int main()
{
WSADATA wsaData;
SOCKET clientSocket;
SOCKADDR_IN servAddr;
int port=80,ret,once,count;
fd_set fdwrite,fdread;
once=0; //控制只发送一次
char sended[]="GET / HTTP/1.1\r\n\
Accept: image/x=xbitmap,image/jpeg,image/pjpeg,application/x-shockwave-flash,\x2a\x2f\x2a\r\n\
Accept-Language: zh-cn\r\n\
Accept-Encoding: gzip,deflate\r\n\
User-Agent: Mozilla/4.0\r\n\
Host:
Connection: Keep-Alive\r\n\
\r\n";
static char recved[2048]; //会初始化为0
WSAStartup(MAKEWORD(2,2),&wsaData);
clientSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(port);
servAddr.sin_addr.s_addr = inet_addr("220.181.27.5"); //百度IP
ret=connect(clientSocket,(SOCKADDR *)&servAddr,sizeof(servAddr));
/*如果只收发一次好象不需要循环,不过发送之后马上收不太好,还是等有了数据再收较好.这就是循环的作用*/
while(TRUE)
{
FD_ZERO(&fdwrite);
FD_ZERO(&fdread);
FD_SET(clientSocket,&fdwrite);
FD_SET(clientSocket,&fdread);
ret=select(0,&fdread,&fdwrite,NULL,NULL);
if(ret==SOCKET_ERROR)
{
cout<<"SOCKET_ERROR\n"< }
if(ret>0)
{
if(FD_ISSET(clientSocket,&fdread))
{
//保证recved已初始化为0
count=recv(clientSocket,recved,sizeof(recved),0); cout<<"recved: "< break;
}
if(FD_ISSET(clientSocket,&fdwrite))
{
if(once>0) continue;
//为了方便,这儿只用一个send代替了一大片保证发送完的代码,如果数据多的话,不要用一个send, 因为可能发送不完.
int tmp=send(clientSocket,sended,sizeof(sended),0);
cout< once++;
}
}
}
shutdown(clientSocket,SD_BOTH);
closesocket(clientSocket);
return 0;
}
关于构造的http头
char sended[]="GET / HTTP/1.1\r\n\
Accept: image/x=xbitmap,image/jpeg,image/pjpeg,application/x-shockwave-flash,\x2a\x2f\x2a\r\n\
Accept-Language: zh-cn\r\n\
Accept-Encoding: gzip,deflate\r\n\
User-Agent: Mozilla/4.0\r\n\
Host:
Connection: Keep-Alive\r\n\
\r\n";
其实这个头有几点最容易出错的:
GET / HTTP/1.1\r\n\
相互间隔一个空格,捕获的包里空格显示为0x20.而这里的\r\r表示换行回车,数据包里显示为0x0d,0x0a,在协议里表示为CRLF.
再说一下这一行最后的'\'字符,是把下面一行的和前面的连起来.在包里面显示为:
47 45 54 20 2f 20 48 54 54 50 2f 31 23 31 0d 0a 41 63 63 65 70 74 3a
这和
GET / HTTP/1.1..Accept:
是对应的,因为\r\n不可显,用..代替.
如果不知道URL,用偶的这样构造的头最小的头是:
char sended[]="GET / HTTP/1.1\r\n\
HOST:
\r\n";
可能有人奇怪,为什么最后一行又加上了\r\n,HOST字段不是有了么?呵呵,除了实体外,每个请求标题域都得加上0d0a尾,在C++中就是\r\n了.最后一个\r\n表示请求标题域到此为止,结束了.如果下面有主体的话,就可以开始了,下次再写写这个BODY.
select模式
凡是send,rec如果遇到socket接口出现一些错误会阻塞线程,比如如果一直没有数据可以接收,rec就会一直等,这不是好的方法.最好有一种机制,如果可以读了,你给我说一声,我再来读,这保证没有数据时不会让你再recv.
select就起到这种作用.当然IO模式很多,偶用select因为它简单并且应用广泛么.Sockets lib工程都是用的select模式,可见一般的应用够用了.还是因为偶不会别的,呵呵.
windows网络编程一书中有这种模式的一种描述,按步做就是.偶就略了.......
偶实践了一个,发现其实只能把select用到一个新线程中,用到主线程中也会影响反应.不过在新线程中运行一点不影响主线程的界面,挺好.
偶这个循环是在例子上改的.运行正常.下面是运行结果:
HTTP/1.1 200 OK
Date: Mon, 03 Apr 2006 08:30:03 GMT
Server: Apache/1.3.27
Set-Cookie: BAIDUID=FE7EF2E2C77F616D12E25B44BCA44584; expires=Mon, 03-Apr-36 08:
30:03 GMT; path=/; domain=.baidu.com
Cache-Control: max-age=86400
Expires: Tue, 04 Apr 2006 08:30:03 GMT
Last-Modified: Thu, 16 Mar 2006 10:15:00 GMT
ETag: "2d800e-b53-44193aa4"
Accept-Ranges: bytes
Content-Length: 2899
Connection: close
Content-Type: text/html
百度——全球最大中文搜索引擎 t-Type content="text/html; charset=gb2312">