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 " + "HOST: " + host + " " + "Connection: close "; //设置报文 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, '