winsock学习101

0.前言

首先你要有C/C++的相关知识,并且安装好了VS2017。

在VS2017中建立一个C++的空项目,就可以开始了。

参考自:资料参考


1.初始化winsock

直接复制下面的代码到VS中,应该可以看到类似输出:


注意:我在开头include了各种乱七八糟的东西,并不都是必要的,而且这不是很好的编程习惯,千万别学!(逃

源码讲解:

1. 最开头的ifndef部分,参见下面的链接:

ifndef...的含义

2. pragma warning的作用: VS有时会报错,我们需要屏蔽这些错误:

#pragma warning(disable:4996)

3.pragma comment的作用:

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)所做的。服务器要做的是接收连接并返回给它们想要的东西。

在我们浏览百度的时候,百度就是一个服务器,而我们的浏览器就是一个客户端。


现在,我们该开始干些服务器做的事情了。不过在那之前,我们先学点别的东西。


(吃饭去了,待续)

猜你喜欢

转载自blog.csdn.net/timotolkki1966/article/details/80447529
101