0.前言
首先你要有C/C++的相关知识,并且安装好了VS2017。
在VS2017中建立一个C++的空项目,就可以开始了。
参考自:资料参考
1.初始化winsock
直接复制下面的代码到VS中,应该可以看到类似输出:
注意:我在开头include了各种乱七八糟的东西,并不都是必要的,而且这不是很好的编程习惯,千万别学!(逃
源码讲解:
1. 最开头的ifndef部分,参见下面的链接:
2. pragma warning的作用: VS有时会报错,我们需要屏蔽这些错误:
#pragma warning(disable:4996)
3.pragma comment的作用:
4. WSAstartup函数用来启动winsock库,其中第一个参数是使用的winsock版本,第二个参数是一个WASDATA结构体,用来存储winsock的额外信息。
5.WSAGetLastError函数用来捕获最近的一次错误代码。在以后的程序中我们会经常看到这个函数。
这个函数的存在是必须的,否则我们不知道在其中某步已经出错而继续进行下去,。
#define UNICODE #endif #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> #include <iphlpapi.h> #include <stdio.h> #include <cstdlib> #include <iostream> using namespace std; #pragma warning(disable:4996) #pragma comment(lib, "Ws2_32.lib") int main(int argc, char *argv[]) { WSADATA wsa; printf("\n初始化中Initialising Winsock..."); if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { printf("Failed. Error Code : %d", WSAGetLastError()); return 1; } printf("初始化成功Initialised."); return 0; }
2. 创建socket
运行以下代码,应该可以看到这样的输出:
socket函数创建一个新的socket并且返回它的描述。
本程序创建的socket类型如下所示:
Address Family | AF_INET | 代表IPV4 |
Type | SOCK_STREAM | socket类型,这里是TCP协议 |
Protoco | 0 | TCP协议 |
除了SOCK_STREAM,我们也可以使用 SOCK_DGRAM来调用UDP协议。
更多的参数参见这里:MSDN:socket()函数
#ifndef UNICODE #define UNICODE #endif #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> #include <iphlpapi.h> #include <stdio.h> #include <cstdlib> #include <iostream> using namespace std; #pragma warning(disable:4996) #pragma comment(lib, "Ws2_32.lib") #include<stdio.h> #include<winsock2.h> #pragma comment(lib,"ws2_32.lib") //Winsock Library int main(int argc , char *argv[]) { WSADATA wsa; SOCKET s; printf("\n初始化Initialising Winsock..."); if (WSAStartup(MAKEWORD(2,2),&wsa) != 0) { printf("失败Failed. Error Code : %d", WSAGetLastError()); return 1; } printf("Initialised.\n"); if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET) { printf("创建失败Could not create socket : %d" , WSAGetLastError()); } printf("成功Socket created.\n"); return 0; }
3. 连接到服务器并发送数据
想要连接到一个服务器,需要两个东西来定位它:IP地址和端口号。
首先,我们要创建一个 sockaddr_in结构体并填入适当的值。
我们不妨先看看sockaddr_in结构体的内容:
struct sockaddr_in { short sin_family; // e.g. AF_INET, AF_INET6 unsigned short sin_port; // e.g. htons(3490) struct in_addr sin_addr; // see struct in_addr, below char sin_zero[8]; // zero this if you want to };
第一行sin_family我们填入IPV4/IPV6。
第二行我们填入端口号。这个不同于我们常见的端口号(比如HTTP的端口号是80),这点稍后再讲。
第三行是in_addr,这个是什么呢?其实非常简单,不要被下面的代码吓到,其实极其简单:
这里用到了union。union是这样一种特殊的结构体:它含有许多变量,但是你同时只能使用一个——因为它们被保存在同一个地址开始的内存单元,所以你给union中的另一个变量赋值的时候,原本存储的另外一个就被清空了。
在这里,我们只需要注意被标记了///////////////////的一行。也就是
u_long S_addr;
只有这一行起作用,而别的两个结构体都没用作用,因为它们都被s_addr覆盖了。
其实,就干了一件事:用long型来存储s_addr!换句话说,这里就是用long的形式来存储了IP地址。
typedef struct in_addr { union { struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { u_short s_w1,s_w2; } S_un_w; u_long S_addr;////////////////////////// } S_un; } IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;
所以,如果希望把一个IP地址存入到sockaddr_in结构体中去,需要先把常见的IP地址形式转换为long形式。
winscok提供了一个非常好用的函数 inet_addr
来干这件事:
server.sin_addr.s_addr = inet_addr("74.125.235.20");
现在,我们只需要知道要连接到的服务器地址就万事俱备了。
我们把百度当作目标服务器。百度的IP地址可以ping一下,发现是61.135.169.125。我们就拿它当例子吧。
运行下面程序应该可以得到这样的输出:
#ifndef UNICODE #define UNICODE #endif #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> #include <iphlpapi.h> #include <stdio.h> #include <cstdlib> #include <iostream> using namespace std; #pragma warning(disable:4996) #pragma comment(lib, "Ws2_32.lib") int main(int argc, char *argv[]) { WSADATA wsa; SOCKET s; struct sockaddr_in server; char *message; printf("\nInitialising Winsock..."); if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { printf("Failed. Error Code : %d", WSAGetLastError()); return 1; } printf("Initialised.\n"); //Create a socket if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { printf("Could not create socket : %d", WSAGetLastError()); } printf("Socket created.\n"); server.sin_addr.s_addr = inet_addr("61.135.169.125"); server.sin_family = AF_INET; server.sin_port = htons(80);/////////////////////////////////////////// //Connect to remote server if (connect(s, (struct sockaddr *)&server, sizeof(server)) < 0) { puts("connect error"); return 1; } puts("Connected"); //Send some data message = "GET / HTTP/1.1\r\n\r\n"; if (send(s, message, strlen(message), 0) < 0) { puts("Send failed"); return 1; } puts("Data Send\n"); return 0; }
尝试将上面标记//////////////////////////的一行从80换成其他数值,比如100,我们将会连接失败,得到下面的失败输出:
注意:我们都知道TCP是面向连接的协议,而UDP是无连接的。所以connection(连接)这个术语只能用于TCP协议中。
除了连接到服务器,我们还注意到使用了send函数发送了一条指令给服务器。
message = "GET / HTTP/1.1\r\n\r\n";
这条指令的作用明显是取回主页。但是由于我们没有设置接收,所以也就到此为止了。
但在下一节,我们即将知道怎么接收数据。
4. 接收数据
注意:上一节中我们使用了百度的IP来尝试发送数据。但是在接收数据的时候并不能成功,推测是百度采用了某种手段防止抓取。
所以在这里,我换成了北京科技大学的课程中心的IP地址。如果还是不能获得理想的效果,我建议你换成你能想到的其它地址进行尝试。
成功的界面如下所示:
这看起来像是HTML,其实它就是。
#ifndef UNICODE #define UNICODE #endif #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> #include <iphlpapi.h> #include <stdio.h> #include <cstdlib> #include <iostream> using namespace std; #pragma warning(disable:4996) #pragma comment(lib, "Ws2_32.lib") int main(int argc, char *argv[]) { WSADATA wsa; SOCKET s; struct sockaddr_in server; char *message, server_reply[2000]; int recv_size; printf("\nInitialising Winsock..."); if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { printf("Failed. Error Code : %d", WSAGetLastError()); return 1; } printf("Initialised.\n"); //Create a socket if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { printf("Could not create socket : %d", WSAGetLastError()); } printf("Socket created.\n"); server.sin_addr.s_addr = inet_addr("202.204.49.169");//cc.ustb.edu.com server.sin_family = AF_INET; server.sin_port = htons(80); //Connect to remote server if (connect(s, (struct sockaddr *)&server, sizeof(server)) < 0) { puts("connect error"); return 1; } puts("Connected"); //Send some data发送请求 message = "GET / HTTP/1.1\r\n\r\n"; if (send(s, message, strlen(message), 0) < 0) { puts("Send failed"); return 1; } puts("Data Send\n"); //Receive a reply from the server接收html文档 if ((recv_size = recv(s, server_reply, 2000, 0)) == SOCKET_ERROR) { puts("recv failed"); } puts("Reply received\n"); //Add a NULL terminating character to make it a proper string before printing server_reply[recv_size] = '\0'; puts(server_reply); return 0; }
关闭socket:
我们接收到了想要的回复,但别忘了擦屁股。
使用下面两个函数可以关闭socket并进行清理。
closesocket(s); WSACleanup();
复习一下,到现在为止我们学到了这些东西:
1. 创建一个socket
2. 连接到一个服务器
3. 给这个服务器发送一些数据
4. 得到服务器返回的结果。
实际上,这就是你的浏览器在上网的时候所做的事情。
换句话说,到目前为止,我们所作的事情都是和浏览器一样的。我们的所作所为都是客户端(client)行为。
另外一种行为是服务器(server)所做的。服务器要做的是接收连接并返回给它们想要的东西。
在我们浏览百度的时候,百度就是一个服务器,而我们的浏览器就是一个客户端。
现在,我们该开始干些服务器做的事情了。不过在那之前,我们先学点别的东西。
(吃饭去了,待续)