windows网络基础

网络基础与Tcp编程

windows中,要想进行socket网络操作,
必须包含一个名叫做WinSock2.h(或者WinSock.h),
如果包含的是WinSock2.h则必须在windows.h之前,
包含完头文件之后,还要链接一个库文件ws2_32.lib,
先来看服务端。
首先,要进行网络操作,
我们先要进行一下网络环境的初始化。
WSAStartup函数就是用来初始化网络环境的。
int WSAStartup(  
  WORD wVersionRequested,       //版本号,一般使用2.2版本
  LPWSADATA lpWSAData  //WSAData地址
);
函数的第二个参数,
接收一个WSAData结构的指针,
该结构呢,里边包含了版本号,
我们传递的版本号会对该结构里边的版本号进行初始化。
初始化完成之后,我们需要创建一个socket(套接字)
SOCKET socket(  
  int af,         //IP协议簇
  int type,       //套接字类型,TCP应该用SOCK_STREAM
  int protocol 	  //协议
);
其实,socket也是一个内核对象,但是它没有内核对象所拥有的明显标志,安全属性。
创建好套接字后呢,我们需要告诉操作系统需要在哪个地址和端口上进行网络操作,
相当于管道通信中绑定到标准输入输出口上。绑定的时候,需要有一个SOCKADDR_IN这个结构体,
struct sockaddr_in{
    short              sin_family;		//协议簇
    unsigned short     sin_port;		//端口
    struct   in_addr   sin_addr;		//ip地址
    char               sin_zero[8];		//为了设置和SOCKADDR结构等长的补充字节
};
初始化完端口,地址等信息后,需要调用bind函数,来完成绑定操作,
int bind(  
  SOCKET s,                          //我们创建的那个socket
  const struct sockaddr FAR *name,   //sockaddr结构指针
  int namelen                        //sockaddr长度
);
绑定之后,我们还需要调用listen函数来进行监听操作,这个操作呢,
就相当于门卫一样了,如果有人来,就告诉你一声,这就是监听。
int listen(  
  SOCKET s,      //我们创建的socket
  int backlog  	 //最大连接的队列长度
);
监听完成之后,我们就可以进行接收客户端的连接了,我们需要调用accept这个函数来进行接客。
SOCKET accept(  
  SOCKET s,   					//我们监听的那个socket
  struct sockaddr FAR *addr,    //我们需要传递一个sockaddr的地址,用于保存客户端的地址
  int FAR *addrlen  			//sockaddr的长度指针
);
接完客之后,我们就可以进行通信了,需要调用recv和send两个函数来进行收发数据,
int recv(  
  SOCKET s,        	 //客户端的socket
  char FAR *buf,     //接收的缓冲区
  int len,        	 //缓冲区的大小
  int flags       	//标志位,一般为0
);
 
int send(  
  SOCKET s,             //客户端的socket
  const char FAR *buf,  //发送数据的缓冲区
  int len,              //缓冲区的大小
  int flags             //标志位,一般为0
);
当我们传输完数据后,应该调用WSACleanup和closesocket来进行关闭网络环境和套接字。
int  WSACleanup (void);
int closesocket(  
  SOCKET s  		//要关闭的套接字
);
服务端的示例代码:
#include <stdio.h>  
#include <winsock2.h> // 必须包含windwos.h之前
#include <Windows.h>
 
#pragma comment(lib,"ws2_32.lib") 
 
#define  PORT 6000
// 传入的是服务端产生的已经连接套接字
DWORD WINAPI clientProc(LPARAM lparam)
{	
	SOCKET sockClient = (SOCKET)lparam;
	char buf[1024];
	while (TRUE)
	{
		memset(buf, 0, sizeof(buf));
		// 接收客户端的一条数据 
		int ret = recv(sockClient, buf, sizeof(buf), 0);
		//检查是否接收失败
		if (SOCKET_ERROR == ret) 
		{
			printf("socket recv failed\n");
			closesocket(sockClient);
			return -1;
		}
		
		// 0 代表客户端主动断开连接
		if (ret == 0) 
		{
			printf("client close connection\n");
			closesocket(sockClient);
			return -1;
		}		
		
		// 发送数据
		ret = send(sockClient, buf, strlen(buf), 0);
		//检查是否发送失败
		if (SOCKET_ERROR == ret)
		{
			printf("socket send failed\n");
			closesocket(sockClient);
			return -1;
		}
	}
	
	closesocket(sockClient);
	return 0;
}

// 网络编程初始化
bool InitNetEnv()
{
	// 进行网络环境的初始化操作
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) 
	{
		printf("WSAStartup failed\n");
		return false;
	}
	return true;
}

int main(int argc, char * argv[])
{
	if (!InitNetEnv())
	{
		return -1;
	}
	
	// 初始化完成,创建一个TCP的socket
	SOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	//检查是否创建失败
	if (sServer == INVALID_SOCKET)
	{
		printf("socket failed\n");
		return -1;
	}
	
	printf("Create socket OK\n");
	//进行绑定操作
	SOCKADDR_IN addrServ;
	addrServ.sin_family = AF_INET; // 协议簇为IPV4的
	addrServ.sin_port = htons(PORT); // 端口  因为本机是小端模式,网络是大端模式,调用htons把本机字节序转为网络字节序
	addrServ.sin_addr.S_un.S_addr = INADDR_ANY; // ip地址,INADDR_ANY表示绑定电脑上所有网卡IP
	
	//完成绑定操作
	int ret = bind(sServer, (sockaddr *)&addrServ, sizeof(sockaddr));
	//检查绑定是否成功
	if (SOCKET_ERROR == ret)
	{
		printf("socket bind failed\n");
		WSACleanup(); // 释放网络环境
		closesocket(sServer); // 关闭网络连接
		return -1;
	}
	
	printf("socket bind OK\n");
	// 绑定成功,进行监听
	ret = listen(sServer, 10);  
	//检查是否监听成功
	if (SOCKET_ERROR == ret)
	{
		printf("socket listen failed\n");
		WSACleanup();
		closesocket(sServer);
		return -1;
	}
	
	printf("socket listen OK\n");
	// 监听成功
	sockaddr_in addrClient; // 用于保存客户端的网络节点的信息
	int addrClientLen = sizeof(sockaddr_in);
	while (TRUE)
	{
		//新建一个socket,用于客户端
		SOCKET *sClient = new SOCKET; 
		//等待客户端的连接
		*sClient= accept(sServer, (sockaddr*)&addrClient, &addrClientLen);
		if (INVALID_SOCKET == *sClient)
		{
			printf("socket accept failed\n");
			WSACleanup();
			closesocket(sServer);
			delete sClient;
			return -1;
		}
	
		// 每个客户端单独用一个线程去处理
		// 监听描述符独占一个额外的线程
		//创建线程为客户端做数据收发
		CreateThread(0, 0, (LPTHREAD_START_ROUTINE)clientProc, (LPVOID)*sClient, 0, 0);
	}
	
	closesocket(sServer);
	WSACleanup();
	return 0;
}
接下来,看下客户端。
客户端比较简单,前面的部分和服务端都基本相同,在绑定操作上会有所差别。
客户端呢,需要调用connect函数
int connect(  
  SOCKET s,                          //要进行连接的socket
  const struct sockaddr FAR *name,   //SOCKADDR结构地址
  int namelen                        //SOKADDR大小
);
连接成功后,就可以和服务端进行通信了,调用recv和send来进行收发数据。
当通信完之后,就可以关闭连接了。
当客户端和服务端刚开始连接的时候呢,两者会先进行沟通,
这个沟通需要3个步骤来完成,我们称之为3次握手,
同样的关闭连接的时候,需要进行4个步骤来完成,我们称之为4次握手。
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
 
#pragma comment(lib,"ws2_32.lib")
#define  PORT 6000
 
int main(int argc, char * argv[])
{
	//初始化网络环境
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		printf("WSAStartup failed\n");
		return -1;
	}

	// 初始化完成,创建一个TCP的socket
	SOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sServer == INVALID_SOCKET)
	{
		printf("socket failed\n");
		return -1;
	}

	//指定连接的服务端信息
	SOCKADDR_IN addrServ;
	addrServ.sin_family = AF_INET;
	addrServ.sin_port = htons(PORT);
	//客户端只需要连接指定的服务器地址,127.0.0.1是本机的回环地址
	addrServ.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	
	// 服务器Bind 客户端是进行连接
	int ret = connect(sServer,(SOCKADDR*)&addrServ,sizeof(SOCKADDR));//开始连接
	if (SOCKET_ERROR == ret)
	{
		printf("socket connect failed\n");
		WSACleanup();
		closesocket(sServer);
		return -1;
	}
	
	//连接成功后,就可以进行通信了
	char szBuf[1024];
	memset(szBuf,0,sizeof(szBuf));
	sprintf_s(szBuf,sizeof(szBuf),"Hello server");
	
	//当服务端是recv的时候,客户端就需要send,
	//若两端同时进行收发则会卡在这里,因为recv和send是阻塞的
	ret = send(sServer, szBuf, strlen(szBuf), 0);
	if (SOCKET_ERROR == ret)
	{
		printf("socket send failed\n");
		closesocket(sServer);
		return -1;
	}
	
	ret = recv(sServer, szBuf, sizeof(szBuf), 0);
	if (SOCKET_ERROR == ret)
	{
		printf("socket recv failed\n");
		closesocket(sServer);
		return -1;
	}
	
	printf("%s\n",szBuf);
	closesocket(sServer);
	WSACleanup();
	return 0;
}

Udp

TCP创建一个socket调用socket函数时,
第二个参数为SOCK_STREAM,
而UDP则需要给定一个SOCK_DGRAM,
然后在第三个参数上给一个IPPROTO_UDP,
这样我们就创建好了一个UDP的socket。

接下来,也和TCP一样,指定SOCKADDR_IN的地址信息(端口,ip),
指定完之后呢,若是客户端,则可以直接就进行通信了,
若是服务端,则还需要增加一步bind操作,
当我们调用bind函数,进行绑定后,服务端就可以和客户端进行通信了。
上篇提到TCP进行数据的收发是通过recv和send两个API来进行数据的收发的。
而UDP也需要两个函数,叫做recvform和sendto,这两个和TCP那两个有点不同,
int recvfrom(  
  SOCKET s,                     	//socket
  char FAR* buf, 					//接收数据的缓冲区             
  int len,                      	//缓冲区的大小
  int flags,                    	//标志位,调用操作方式
  struct sockaddr FAR *from,    	//sockaddr结构地址
  int FAR *fromlen              	//sockaddr结构大小地址
);

int sendto(  
  SOCKET s,                    		//socket      
  const char FAR *buf,         		//发送数据的缓冲区   
  int len,                     		//缓冲区大小      
  int flags,                   		//标志位,调用操作方式
  const struct sockaddr FAR *to,    //sockaddr结构地址
  int tolen                         //sockaddr结构大小地址
);
注意,这两个函数里边有一个sockaddr结构地址,它是用来保存该数据发送者的信息的。
UDP是面向数据包的,因此就好像寄快递一样,你必须在快递上写一张纸条,
上面填好姓名,地址等信息,填好之后,接收者才知道该东西是由谁寄过来的。
因此,上面两个函数提供了sockaddr结构的地址,用于保存从哪里发来的和发送到哪里的地址信息。
服务端:
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
 
#pragma comment(lib,"ws2_32.lib")
#define  PORT 6000
 
int main(int argc, char* argv[])
{
	//初始化网络环境
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		printf("WSAStartup failed\n");
		return -1;
	}
 
	//建立一个UDP的socket
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sock == SOCKET_ERROR)
	{
		printf("create socket failed\n");
		return -1;
	}
 
	//绑定地址信息
	sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(PORT);
	serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
 
	bind(sock, (sockaddr*)&serverAddr, sizeof(sockaddr));
	
	char buf[512];
	while (TRUE)
	{
		memset(buf, 0, 512);
		// 网络节点的信息,用来保存客户端的网络信息
		sockaddr_in clientAddr;
		memset(&clientAddr, 0, sizeof(sockaddr_in));
 
		int clientAddrLen = sizeof(sockaddr);
		//接收客户端发来的数据
		int ret = recvfrom(
			sock, 
			buf, 
			512, 
			0,
			(sockaddr*) &clientAddr,
			&clientAddrLen );
		
		printf(
			"Recv msg:%s from IP:[%s] Port:[%d]\n", 		
			buf,
			inet_ntoa(clientAddr.sin_addr),
			ntohs(clientAddr.sin_port));
		// 发一个数据包返回给客户
		sendto(
			sock, 
			"Hello World!", 
			strlen("Hello World!"), 
			0, 
			(sockaddr*)&clientAddr, 
			clientAddrLen);
		printf(
			"Send msg back to IP:[%s] Port:[%d]\n", 
			inet_ntoa(clientAddr.sin_addr), 
			ntohs(clientAddr.sin_port));
	}
	
	return 0;
}
客户端:
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
 
#pragma comment(lib,"ws2_32.lib")
#define  PORT 6000
int main(int argc, char* argv[])
{
	//初始化网络环境
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		printf("WSAStartup failed\n");
		return -1;
	}
	
	//建立一个UDP的socket
	SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sockClient == INVALID_SOCKET)
	{
		printf("create socket failed\n");
		return -1;
	}
	
	// 申明一个网络地址信息的结构体,保存服务器的地址信息
	sockaddr_in addr = { 0 };
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
 
	char buf[] = "client test!";
	//发送数据
	int dwSent = sendto(
		sockClient, 
		buf, 
		strlen(buf), 
		0, 
		(SOCKADDR *)&addr, 
		sizeof(SOCKADDR));
	if (dwSent == 0)
	{
		printf("send %s failed\n", buf);
		return -1;
	}
	
	printf("send msg:%s\n", buf);
 
	char recvBuf[512];
	memset(recvBuf, 0, 512);
 
	sockaddr_in addrSever = { 0 };
	int nServerAddrLen=sizeof(sockaddr_in);
	// 接收数据
	int dwRecv = recvfrom(
		sockClient, 
		recvBuf, 
		512, 
		0, 
		(SOCKADDR *)&addrSever,
		&nServerAddrLen);
	printf("Recv msg from server : %s\n", recvBuf);
	
	//关闭SOCKET连接
	closesocket(sockClient);
	//清理网络环境
	WSACleanup();
	system("pause");
	return 0;
}

参考

https://blog.csdn.net/Timmiy/article/details/51946093
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值