域名仅仅是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类型数据从网络字节序转换为主机字节序。
其他函数链接
UDP注意:为 sockaddr_in 等成员赋值时需要显式地将主机字节序转换为网络字节序,而通过 send() 发送数据时TCP协议会自动转换为网络字节序,不需要再调用相应的函数。
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;
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)