TCPIP协议学习笔记(四)Window下socket编程

TCPIP协议学习笔记(四)Window下socket编程,第1张

在socket中使用域名

域名仅仅是IP地址的一个助记符,目的是方便记忆,通过域名并不能找到目标计算机,通信之前必须要将域名转换成IP地址。

gethostbyname() 函数可以完成这种转换,它的原型为:

/** \brief 	解析域名
 *
 * \param   hostname 为主机名,也就是域名。
 * \return  指向 hostent 结构体的指针
 *
 */
struct hostent* gethostbyname(const char* hostname)

其中结构体hostent的定义:

struct hostent
{
    char *h_name;  //官方域名(Official domain name)。官方域名代表某一主页,但实际上一些著名公司的域名并未用官方域名注册。
    char **h_aliases;  //别名,可以通过多个域名访问同一主机。同一IP地址可以绑定多个域名,因此除了当前域名还可以指定其他域名。
    int  h_addrtype;  //gethostbyname() 不仅支持 IPv4,还支持 IPv6,可以通过此成员获取IP地址的地址族(地址类型)信息,IPv4 对应 AF_INET,IPv6 对应 AF_INET6。
    int  h_length;  //存IP地址长度。IPv4 的长度为4个字节,IPv6 的长度为16个字节。
    char **h_addr_list;  //这是最重要的成员。通过该成员以整数形式保存域名对应的IP地址。对于用户较多的服务器,可能会分配多个IP地址给同一域名,利用多个服务器进行均衡负载。
}


解析www.baidu.com

#include 
#include 
#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll


typedef struct hostent host_t;


int main()
{
	/* 初始化DLL */
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	/* 解析域名 */
	host_t* host = gethostbyname("www.baidu.com");

	if (!host)
	{
		printf("出错!\n");
		goto end;
	}

	/* 输出别名 */
	while (*host->h_aliases)
	{
		printf("别名:%s\n", *host->h_aliases++);
	}

	/* 地址类型 */
	printf("地址类型:%s\n", host->h_addrtype == AF_INET ? "IPv4" : "IPv6");


	/* IP地址 */
	struct in_addr* addr;
	while (*host->h_addr_list)
	{
		//printf("IP:%s\n", inet_ntoa(*(struct in_addr*)*host->h_addr_list++));
		addr = (struct in_addr*)(*host->h_addr_list);
		printf("IP:%s\n", inet_ntoa(*addr));//inet_ntoa用来将参数in 所指的网络二进制的数字转换成网络地址, 
											//然后将指向此网络地址字符串的指针返回.
		host->h_addr_list++;
	}

end:
	/* 终止 DLL 的使用 */
	WSACleanup();

	return 0;
}

/*
输出结果
别名:www.baidu.com
地址类型:IPv4
IP:36.152.44.95
IP:36.152.44.96
*/
socket网络字节序以及大端序小端序

CPU向内存保存数据的方式有两种:

  • 大端序(Big Endian):高位字节存放到低位地址(高位字节在前)。
  • 小端序(Little Endian):高位字节存放到高位地址(低位字节在前)。

仅凭描述很难解释清楚,不妨来看一个实例。假设在 0x20 号开始的地址中保存4字节 int 型数据 0x12345678,大端序CPU保存方式如下图所示:

小端序的保存方式如下图所示:

不同CPU保存和解析数据的方式不同(主流的Intel系列CPU为小端序),小端序系统和大端序系统通信时会发生数据解析错误。因此在发送数据前,要将数据转换为统一的格式——网络字节序(Network Byte Order)。网络字节序统一为大端序。

网络字节序转换函数

htons() 用来将当前主机字节序转换为网络字节序,其中h代表主机(host)字节序,n代表网络(network)字节序,s代表short,htons 是 h、to、n、s 的组合,可以理解为”将short型数据从当前主机字节序转换为网络字节序“。

常见的网络字节转换函数有:
htons():host to network short,将short类型数据从主机字节序转换为网络字节序。
ntohs():network to host short,将short类型数据从网络字节序转换为主机字节序。
htonl():host to network long,将long类型数据从主机字节序转换为网络字节序。
ntohl():network to host long,将long类型数据从网络字节序转换为主机字节序。

其他函数链接

注意:为 sockaddr_in 等成员赋值时需要显式地将主机字节序转换为网络字节序,而通过 send() 发送数据时TCP协议会自动转换为网络字节序,不需要再调用相应的函数。

UDP

TCP中,套接字是一对一的关系。如要向10个客户端提供服务,那么除了负责监听的套接字外,还需要创建10套接字。但在UDP中,不管是服务器端还是客户端都只需要1个套接字。

发送数据函数

/** \brief 	经socket传送数据
 *
 * \param   sock 用于传输UDP数据的套接字
 * \param   buf	 保存待传输数据的缓冲区地址
 * \param   nbytes 带传输数据的长度(以字节计)
 * \param   flags 可选项参数,若没有可传递0
 * \param   to 存有目标地址信息的 sockaddr 结构体变量的地址
 * \param   addrlen 传递给参数 to 的地址值结构体变量的长度
 * \return  成功返回写入的字节数,否则,它将返回SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。
 * 
 */
int sendto(SOCKET sock, const char* buf, int nbytes, int flags, const struct sockadr* to, int addrlen)

接收数据函数

/** \brief 	经socket接收数据
 *
 * \param   sock 用于接收UDP数据的套接字
 * \param   buf	 保存接收数据的缓冲区地址
 * \param   nbytes 可接收的最大字节数(不能超过buf缓冲区的大小)
 * \param   flags 可选项参数,若没有可传递0
 * \param   to 存有发送端地址信息的sockaddr结构体变量的地址
 * \param   addrlen 保存参数 from 的结构体变量长度的变量地址值
 * \return  成功执行时,返回接收到的字节数
 *				另一端已关闭则返回0,否则,它将返回SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。WSAGetLastError来检索特定的错误代码。
 *
 */
int recvfrom(SOCKET sock, char* buf, int nbytes, int flags, const struct sockaddr* from, int* addrlen)
基于UDP的回声服务器端/客户端

服务端代码

#include 
#include 
#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll


int main()
{
	printf("这是服务端\n");

	/* 初始化DLL */
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	/* 创建套接字 */
	SOCKET servSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

	/* 绑定套接字 */
	SOCKADDR_IN servAddr;
	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;//IPv4
	servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//IP地址
	servAddr.sin_port = htons(1234);//端口
	bind(servSock, (SOCKADDR*)&servAddr, sizeof(servAddr));

	/* 接收客户端数据 */
	SOCKADDR clientSockAddr;
	SIZE_T size = sizeof(clientSockAddr);
	char buff[100] = { 0 };
	while (1)
	{
		int receiveLength = recvfrom(servSock, buff, 100, 0, (SOCKADDR*)&clientSockAddr, &size);
		printf("服务端收到的数据为:%s\n", buff);
		sendto(servSock, buff, receiveLength, 0, (SOCKADDR*)&clientSockAddr, size);
	}

	/* 断开连接 */
	closesocket(servSock);

	/* 终止 DLL 的使用 */
	WSACleanup();

	return 0;
}

客户端代码

#include 
#include 
#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll


int main()
{
	printf("这是客户端\n");

	/* 初始化DLL */
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	/* 创建套接字 */
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

	/* 服务器信息 */
	SOCKADDR_IN servAddr;
	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;//IPv4
	servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//IP地址
	servAddr.sin_port = htons(1234);//端口

	/* 获取用户输入并输出到服务端 */
	SOCKADDR fromAddr;
	SIZE_T size = sizeof(fromAddr);
	char buff1[100] = { 0 };
	char buff2[100] = { 0 };
	while (1)
	{
		printf("请输入数据:\n");
		gets(buff1);
		sendto(sock, buff1, strlen(buff1), 0, (SOCKADDR*)&servAddr, sizeof(servAddr));
		recvfrom(sock, buff2, 100, 0, (SOCKADDR*)&fromAddr, &size);
		printf("收到的数据为:%s\n", buff2);
	}

	/* 断开连接 */
	closesocket(sock);

	/* 终止 DLL 的使用 */
	WSACleanup();

	return 0;
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/801090.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-07
下一篇 2022-05-07

发表评论

登录后才能评论

评论列表(0条)

保存