在socket中使用域名
域名仅仅是IP地址的一个助记符,目的是方便记忆,通过域名并不能找到目标计算机,通信之前必须要将域名转换成IP地址。
gethostbyname() 函数可以完成这种转换,它的原型为:
struct hostent* gethostbyname(const char* hostname)
其中结构体hostent的定义:
struct hostent
{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
}
解析www.baidu.com
#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib")
typedef struct hostent host_t;
int main()
{
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");
struct in_addr* addr;
while (*host->h_addr_list)
{
addr = (struct in_addr*)(*host->h_addr_list);
printf("IP:%s\n", inet_ntoa(*addr));
host->h_addr_list++;
}
end:
WSACleanup();
return 0;
}
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个套接字。
发送数据函数
int sendto(SOCKET sock, const char* buf, int nbytes, int flags, const struct sockadr* to, int addrlen)
接收数据函数
int recvfrom(SOCKET sock, char* buf, int nbytes, int flags, const struct sockaddr* from, int* addrlen)
基于UDP的回声服务器端/客户端
服务端代码
#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib")
int main()
{
printf("这是服务端\n");
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;
servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
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);
WSACleanup();
return 0;
}
客户端代码
#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib")
int main()
{
printf("这是客户端\n");
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;
servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
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);
WSACleanup();
return 0;
}
|