标签:命名 精确 def dom winsock col 空间 开启 cto
2019年了,发现以前的很多教程都不能用了。
我自己写的socket发给服务器总是返回301错误——资源永久转移。很多教程都是这样,困扰了我很久。
终于我发现了一篇能用的爬虫代码,参考MSDN以及众多博主的博客,大概给这篇代码做了注解。
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <iostream> #include <vector> #include <list> #include <map> #include <queue> #include <string> #include <utility> #include <regex> #include <fstream> #include <WinSock2.h> #include <Windows.h> #pragma comment(lib, "ws2_32.lib") using namespace std; void startupWSA() //初始化socket { WSADATA wsadata; WSAStartup(MAKEWORD(2, 0), &wsadata); //参数1:指定wsa版本 //参数2:传输版本,套接字规范等信息到WSADATA,用于接收WSA套接字详细信息 } inline void cleanupWSA() //释放socket { WSACleanup(); //无参数,清理释放WSA资源 } inline pair<string, string> binaryString(const string &str, const string &dilme) { pair<string, string> result(str, ""); auto pos = str.find(dilme); if (pos != string::npos) { result.first = str.substr(0, pos); result.second = str.substr(pos + dilme.size()); } return result; } inline string getIpByHostName(const string &hostName) //从域名获得IP地址 { hostent* phost = gethostbyname(hostName.c_str()); //从域名得到IP地址(DNS) //hostent:该结构通过函数来存储关于一个给定的主机,如主机名,IPv4地址 return phost ? inet_ntoa(*(in_addr *)phost->h_addr_list[0]) : ""; //返回得到的点分十进制IP地址,如果转换失败返回"" //inet_ntoa:将一个32位网络字节序的二进制IP地址转换成相应的点分十进制的IP地址 } inline SOCKET connect(const string &hostName) // { auto ip = getIpByHostName(hostName); //获得host(IP) (上函数) if (ip.empty()) return 0; auto sock = socket(AF_INET, SOCK_STREAM, 0); //参数1(domain):协议域,又称协议族(family)。 //常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。 //协议族决定了socket的地址类型,在通信中必须采用对应的地址, //如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、 //AF_UNIX决定了要用一个绝对路径名作为地址。 //参数2(type):指定Socket类型。 //常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。 //流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。 //数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP服务应用。 //参数3(protocol):指定协议。 //常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等, //分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。 //注意:1.type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。 //当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。 if (sock == INVALID_SOCKET) return 0; //INVALID_SOCKET:该返回值代表创建套接字错误 SOCKADDR_IN addr; addr.sin_family = AF_INET; addr.sin_port = htons(80); addr.sin_addr.s_addr = inet_addr(ip.c_str()); if (connect(sock, (const sockaddr *)&addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) return 0; //参数1:套接字描述(之前创建的套接字) //参数2:指向结构sockaddr的指针(取地址) //参数3:结构的大小 //返回值(SOCKET_ERROR):表示连接失败 //SOCKADDR_IN:该结构主要使用三个变量(成员) //sin_family:指定协议族,可参考前面socket函数的第一个参数解释 //sin_port:网络字节序,指的是整数在内存中保存的顺序,即主机字节顺序 //(使用的函数htons: //将主机字节顺序转为网络字节顺序, 不同的CPU有不同的字节顺序类型, //这些字节顺序类型指的是整数在内存中保存的顺序,即主机字节顺序。 //将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为: //高位字节存放在内存的低地址处。 //例如 : 12345->0x3039(16进制)->0x930(字节翻转)--> 14640 ) //sin_addr:其中成员s_addr是IPv4地址结构,IN_ADDR结构 //(使用的inet_addr:该函数转换包含IPv4点分十进制地址转换成一个适当的地址的字符串 IN_ADDR结构。) return sock; } inline bool sendRequest(SOCKET sock, const string &host, const string &get) { string http = "GET " + get + " HTTP/1.1\r\n" + "HOST: " + host + "\r\n" + "Connection: close\r\n\r\n"; //设置报文 return http.size() == send(sock, &http[0], http.size(), 0); //发送请求 //参数1:socket,之前创建的套接字 //参数2:要发送的数据 //参数3:数据大小 //参数4:调用执行方式,默认写0即可 } inline string recvRequest(SOCKET sock) { static timeval wait = { 2, 0 }; static auto buffer = string(2048 * 100, ‘\0‘); //初始化string容量 auto len = 0, reclen = 0; do { fd_set fd = { 0 }; //fd_set:实际上是一个long型数组,是文件描述符的集合 //每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系, //建立联系的工作由程序员完成,当调用select()时, //由内核根据IO状态修改fd_set的内容, //由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。 //总之,这个结构维护一个或者多个socket(的状态) FD_SET(sock, &fd); //FD_SET:用于维护fd_set集合的宏 //参数1:socket套接字 //参数2:传入的fd_set reclen = 0; if (select(0, &fd, nullptr, nullptr, &wait) > 0) { reclen = recv(sock, &buffer[0] + len, 2048 * 100 - len, 0); if (reclen > 0) len += reclen; } //select:非阻塞式的函数,用于确定一个或者多个socket的状态 //对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息, //用fd_set结构来表示一组等待检查的套接口 //参数1(nfds):socket监视的文件句柄数,视进程中打开的文件数而定。 //参数2(readfds):socket监视的可读文件句柄集合 //参数3(writefds):socket监视的可写文件句柄集合 //参数4(exceptfds):socket监视的异常文件句柄集合 //参数5(timeout):传入参数,本次socket()超时结束时间(可精确到百万分之一秒) //recv:用于从服务器接收数据的函数 //参数1:socket套接字 //参数2:接收数据的缓冲区(buffer) //参数3:缓冲区长度 //参数4:指定调用方式,默认写0 //返回值:成功接收的字节长度 FD_ZERO(&fd); //FD_ZERO:用于清空fd_set集合的宏 //参数1:传入fd_set集合参数 //与fd_set配套的宏有: //FD_CLR(s, *set) //从集合中删除s这个元素 //FD_ISSET(s, *set) //判断s是否是集合成员,是返回非0,否则返回0 //FD_SET(s, *set) //将s作为成员加入集合 //FD_ZERO(*set) //将集合初始化(为空集合) } while (reclen > 0); return len > 11 ? buffer[9] == ‘2‘ && buffer[10] == ‘0‘ && buffer[11] == ‘0‘ ? buffer.substr(0, len) : "" : ""; //如果返回的字节长度大于11,那么... //...如果服务器发送的状态码为200 OK... //...那么返回发来的数据; //...如果不是200 OK... //...返回"" //如果不是大于11... //...返回"" } inline void extUrl(const string &buffer, queue<string> &urlQueue) { if (buffer.empty()) { return; } smatch result; auto curIter = buffer.begin(); auto endIter = buffer.end(); while (regex_search(curIter, endIter, result, regex("href=\"(https?:)?//\\S+\""))) { urlQueue.push(regex_replace( result[0].str(), regex("href=\"(https?:)?//(\\S+)\""), "$2")); curIter = result[0].second; } } void Go(const string &url, int count) //BFS { queue<string> urls; urls.push(url); for (auto i = 0; i != count; ++i) { if (!urls.empty()) { auto &url = urls.front(); auto pair = binaryString(url, "/"); auto sock = connect(pair.first); if (sock && sendRequest(sock, pair.first, "/" + pair.second)) { auto buffer = move(recvRequest(sock)); extUrl(buffer, urls); } closesocket(sock); //关闭socket cout << url << ": count=> " << urls.size() << endl; //统计该网页url数量 urls.pop(); } } } int main() { startupWSA(); //开启WSA Go("www.hao123.com", 200); //从www.hao123.com开始,计数200次 cleanupWSA(); //WSA释放 return 0; }
请尽量使用Visual Studio2017(或者VS系列)进行编译,避免IDE听不懂各自的方言。
注释已经非常详细了,接下来是引用的博客:
主要代码: https://www.cnblogs.com/mmc1206x/p/3932622.html
对于关键函数的参数说明: https://www.jianshu.com/p/e3c187da4420
对于fd_set以及select函数通俗易懂的解读: https://blog.csdn.net/rootusers/article/details/43604729
以上是主要思路以及部分函数参考的博客,我的注释中不足之处请看这些博客;
以下是MSDN官方文档以及维基/百度百科等参考的资料:
//对于MSDN以及英文资料的翻译: fanyi.baidu.com 和 translate.google.com
https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-select
https://zh.wikipedia.org/wiki/Select_(Unix)
https://baike.baidu.com/item/fd_set/6075513
https://www.ibm.com/support/knowledgecenter/en/SSB23S_1.1.0.15/gtpc2/cpp_fd_set.html
https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-fd_set
https://docs.microsoft.com/en-us/windows/desktop/api/wsipv6ok/nf-wsipv6ok-inet_addr
https://docs.microsoft.com/zh-cn/windows/desktop/api/winsock2/ns-winsock2-in_addr
https://docs.oracle.com/cd/E19620-01/805-4041/6j3r8iu2l/index.html
https://docs.microsoft.com/en-us/windows/desktop/api/winsock/ns-winsock-hostent
https://docs.microsoft.com/en-us/windows/desktop/api/winsock/ns-winsock-wsadata
https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-wsastartup
本注释讲解不足支持,或者想要获得更加详细的资料,请访问以上链接。
标签:命名 精确 def dom winsock col 空间 开启 cto
原文地址:https://www.cnblogs.com/dudujerry/p/10353876.html