本文已参与「新人创作礼」活动,一起开启掘金创作之路。
IP 地址和端口号
IP 地址可以确定网络中的一台主机,端口号可以确定一台主机中的进程。端口号和进程 PID 不同的是,并不是所有进程都要对外提供网络请求,端口号是网络级别的概念,PID 是系统概念。
网络字节序
发送主机通常将发送缓冲区的数据按照从低到高的顺序发出,而接收主机把从网络上接收到的字节依次保存在接收缓冲区中,也是按照从低到高的顺序保存。但是不同主机的字节序可能不同,这就造成数据读取错误。为了解决这个问题,网络协议对网络数据流的字节序做了统一规定。但是在发送 IP 地址和端口号时,仍需要手动转换。
套接字 API
创建套接字 int socket(int domain, int type, int protocol)
所需头文件:
- <sys/types.h>
- <sys/socket.h> 参数说明:
- domain 协议,如 IPV4,IPV6,本地通信
- type 传输层提供服务类型:TCP、UDP
- protocol 如果为 0,将自动推导
本质上,创建套接字和创建文件相同,因此还需要将文件和网络进行关联(绑定)。
绑定套接字 int bind(int fd, const sockaddr* addr, socklen_t len)
所需头文件:
- <sys/types.h>
- <sys/socket.h> 参数说明:
- fd 文件描述符
- addr 网络相关信息数据,包含 16 位端口号,32 位 IP 地址
- len a网络相关信息的长度 绑定套接字本质上就是将网络相关信息填充到套接字,将文件与网络 IP 和端口号相关联。
服务器程序绑定端口的原因:服务器程序是为了提供服务,需要其他人知道服务的 IP 和端口,并且 bind 之后,不能轻易改变。
发送数据 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen)
所需头文件:
- <sys/socket.h>
- <sys/types.h> 函数功能为往接收端发送大小为 len 的消息 buf,函数返回实际发送的数据大小。发送时,需要设置接收方的 IP 和端口。
接收数据 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
所需头文件:
- <sys/types.h>
- <sys/socket.h>
函数功能为接收大小为 len 的消息并存储到 buf,函数返回实际接收到数据的大小。接收时,无需设置发送方的 IP 和端口。
实现 UDP 通信
// Server
lass UdpServer
{
private:
std::string ip;
int port;
int sockfd;
public:
UdpServer(std::string _ip, int _port = DEFAULT):ip(_ip),port(_port),sockfd(-1)
{}
bool InitUdpServer()
{
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return false;
}
std::cout << "socket create success, sockfd: " << sockfd << std::endl;
struct sockaddr_in local;
memset(&local, '\0', sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
// local.sin_addr.s_addr = INADDR_ANY;
local.sin_addr.s_addr = inet_addr(ip.c_str());
if (bind(sockfd, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr << "bind error" << std::endl;
return false;
}
std::cout << "bind suceess" <<std::endl;
return true;
}
void Start()
{
for (;;)
{
# define SIZE 128
char buf[SIZE] = {0};
struct sockaddr_in peer;
socklen_t len;
ssize_t size = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer, &len);
if (size > 0)
{
buf[size] = 0;
int port = ntohs(peer.sin_port);
std::string _ip = inet_ntoa(peer.sin_addr);
std::cout <<_ip << ":" << port << "#" << buf << std::endl;
std::string echo_msg = "server get!->";
echo_msg += buf;
sendto(sockfd, echo_msg.c_str(), echo_msg.size(), 0, (struct sockaddr*)&peer, len);
}
else
{
std::cerr << "recvfrom error" << std::endl;
}
}
}
~UdpServer()
{
if (sockfd >= 0) close(sockfd);
}
};
// Client
class UdpClient
{
private:
int sockfd;
std::string server_ip;
int server_port;
public:
UdpClient(std::string _ip, int _port)
:server_ip(_ip), server_port(_port)
{}
bool InitClient()
{
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return false;
}
return true;
}
void Start()
{
std::string msg;
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
peer.sin_port = htons(server_port);
for(;;)
{
std::cout << "Please enter#";
std::cin >> msg;
sendto(sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peer, sizeof(peer));
char buf[128];
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t size = recvfrom(sockfd, buf, sizeof(buf)-1, 0, (struct sockaddr*)&temp, &len);
if (size > 0)
{
buf[size] = '\0';
std::cout << buf << std::endl;
}
}
}
~UdpClient()
{
if (sockfd >= 0) close(sockfd);
}
};
通过在本机进行 UDP 通信实验,实现结果如下。