源IP地址:指的就是发送数据包的那个电脑的IP地址。
目的IP地址:就是想要发送到的那个电脑的IP地址。
端口号(port)是传输层协议的内容。
系统编程的pid 表示唯一一个进程;此处我们的端口号也是唯一表示一个进程。
一个进程可以绑定多个端口号;
一个端口号不能被多个进程绑定。
传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述 “数据是谁发的,要发给谁”。
我们先对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识;后面再详细讨论。
此处我们也是对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识;后面再详细讨论。
内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分。
那么如何定义网络数据流的地址呢?
为使网络程序具有可移植性, 使同样的C代码在大端和小端计算机上编译后都能正常运行, 可以调用以下库函数做网络字节序和主机字节序的转换。
#include
uint32_t htonl(uint32_t hostlong); // 主机字节序转网络字节序,转化4字节
uint16_t htons(uint16_t hostshort); // 主机字节序转网络字节序,转化2字节
uint32_t ntohl(uint32_t netlong); // 网络字节序转主机字节序,转化4字节
uint16_t ntohs(uint16_t netshort); // 网络字节序转主机字节序,转化2字节
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
参数:
domain:地址域 —> 网络层使用什么协议
AF_INET: ipv4版本的ip协议
AF_INET6: ipv6版本的ip协议
AF_UNIX: 域套接字
type:套接字的类型
SOCK_DGRAM: 用户数据报套接字 —> 默认协议是UDP协议
SOCK_STREAM: 流式套接字 —> 默认的协议是TCP协议
protocol:协议
SOCK_DGRAM: IPPROTO_UDP(17)
SOCK_STREAM: IPPROTO_TCP(6)
也可以传递0,表示使用套接字的默认协议
返回值:套接字描述符,本质上还是一个文件描述符
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addr_len);
参数:
sockfd:套接字描述符
addr:地址信息
addr_len:传入结构体的真实字节数量
注意绑定ip的时候:
// 关闭套接字
close(int sockfd);
参数:
sockfd:套接字描述符
socket API是一层抽象的网络编程接口, 适用于各种底层网络协议, 如IPv4、IPv6。然而, 各种网络协议的地址格式并不相同。
//通用的结构体:
struct sockaddr
{
sa_family_t sa_family; //地址类型, AF_xxx, 2字节
char sa_data[14]; // 协议地址, 14字节
};
// AF_INET的结构体
struct sockaddr_in
{
sa_family_t sin_family; //地址类型, 2字节
in_port_t sin_port; // 端口号, 2字节
struct in_addr sin_addr; // IP地址, 4字节
unsigned char sin_zero[8]; // 8字节填充, 目的是与addr对齐
};
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in。
这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
in_addr用来表示一个IPv4的IP地址,其实就是一个32位的整数。
IPv4和IPv6的地址格式定义在netinet/in.h中, IPv4地址用sockaddr_in结构体表示, 包括16位地址类型, 16位端口号和32位IP地址。
IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样, 只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体, 就可以根据地址类型字段确定结构体中的内容。
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。
基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址,但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换。
// 点分十进制转网络二进制
#include
in_addr_t inet_addr(const char *cp);
inet addr()函数的作用是:将Internet主机地址从IPv4数字和点符号转换为按网络字节顺序的二进制数据。如果输入无效,则返回INADDR_NONE(通常为-1)。
#include
#include
#include // 套接字
#include // IP地址——> 网络字节序
#include // 主机 ——> 网络字节序
#include
int main()
{
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//地址信息
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(18989);
//私网IP:172.17.0.12
//1. 将点分十进制的IP地址转化成无符号的4字节的整数
//2. 将该整数转化成为网络字节序(二进制)
addr.sin_addr.s_addr = inet_addr("172.17.0.12");
// (struct sockaddr*)&addr 将addr_in强转为addr
int ret = bind(sockfd, (sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return -1;
}
while(1)
{
sleep(1);
}
return 0;
}
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
sockfd: 套接字描述符
buf: 要发送的内容
len: 发送数据的长度
flags: 0 表示阻塞发送
dest_addr: 要将数据发送到哪里去,对端的地址信息
addr_len: 地址信息的长度
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addr1en) ;
参数:
sockfd: 套接字描述符
buf: 接收数据准备的缓冲区
len: 缓冲区接收的最大能力
flags: 0 表示阻塞接收
src_addr: 消息发送端的地址信息,接收回来的消息是从哪里来的
addrlen: 输入输出型参数,返回的就是地址信息的真实长度
#include
#include
#include
#include
#include
#include
#include
int main()
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
perror("socket");
return -1;
}
while(1)
{
//2.发送数据
char buf[1024] = {
0 };
printf("please enter message: ");
std::cin >> buf;
// 对端(服务端)的地址信息
struct sockaddr_in svr_addr;
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(19999);
svr_addr.sin_addr.s_addr = inet_addr("172.17.0.12");
socklen_t svr_len = sizeof(svr_addr);
ssize_t send_size = sendto(sockfd, buf, strlen(buf), 0, (sockaddr*)&svr_addr, svr_len);
if(send_size < 0)
{
perror("sendto");
return -1;
}
//3.接收应答
memset(buf, '\0', sizeof(buf));
ssize_t recv_size = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, NULL, NULL);
if(recv_size < 0)
{
perror("recvfrom");
return -1;
}
//4.打印应答
printf("server say: %s\n", buf);
}
close(sockfd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
int main()
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//2.绑定端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(19999);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return -1;
}
while(1)
{
//3.接收
char buf[1024] = {
0 };
// 对端(客户端)的地址信息
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
ssize_t recv_size = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (sockaddr*)&cli_addr, &cli_len);
if(recv_size < 0)
{
perror("recvfrom");
return -1;
}
//4.处理
printf("client say: %s\n", buf);
printf("please enter respond: ");
//5.回复应答
fflush(stdout); // 强制刷新标准输出
std::cin >> buf;
ssize_t send_size = sendto(sockfd, buf, strlen(buf), 0, (sockaddr*)&cli_addr, cli_len);
if(send_size < 0)
{
perror("sendto");
return -1;
}
}
close(sockfd);
return 0;
}
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
class UdpApi
{
public:
UdpApi()
{
sockfd_ = -1;
}
// 创建套接字
int CreateSocket()
{
sockfd_ = socket(AF_INET, SOCK_DGRAM, 17);
if(sockfd_ < 0)
{
perror("socket");
return -1;
}
return 0;
}
// 绑定地址信息
int Bind(std::string ip, uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
int ret = bind(sockfd_, (sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return -1;
}
return 0;
}
// 发送数据
int SendData(std::string data, struct sockaddr_in* addr, socklen_t addrlen)
{
ssize_t send_size = sendto(sockfd_, data.c_str(), data.size(), 0, (sockaddr*)addr, addrlen);
if(send_size < 0)
{
perror("sendto");
return -1;
}
return send_size;
}
// 接收数据
int RecvData(std::string* data, struct sockaddr_in* addr, socklen_t* addrlen)
{
char buf[1024] = {
0 };
ssize_t recv_size = recvfrom(sockfd_, buf, sizeof(buf) - 1, 0, (sockaddr*)addr, addrlen);
if(recv_size < 0)
{
perror("recvfrom");
return -1;
}
data->assign(buf, strlen(buf));
return recv_size;
}
void Close()
{
close(sockfd_);
}
private:
int sockfd_;
};
#include "udp_socket.hpp"
#define CHECK_ERT(p) if(p < 0){return -1;}
int main()
{
UdpApi ua; // 服务端的ua
CHECK_ERT(ua.CreateSocket());
CHECK_ERT(ua.Bind("0.0.0.0", 19999));
while(1)
{
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
std::string data;
ua.RecvData(&data, &cli_addr, &cli_len);
printf("client say: %s\n", data.c_str());
data.clear();
printf("please enter respond: ");
fflush(stdout);
std::cin >> data;
ua.SendData(data, &cli_addr, cli_len);
}
ua.Close();
return 0;
}
#include "udp_socket.hpp"
#include
#define CHECK_ERT(p) if(p < 0){return -1;}
int main(int argc, char* argv[])
{
// ./udp_cli_package -ip [svr_ip] -port [svr_port]
if(argc != 5)
{
printf("./udp_cli_package -ip [svr_ip] -port [svr_port]\n");
return -1;
}
std::string svr_ip;
uint16_t svr_port;
for(int i = 0; i < argc; i++)
{
if(strcmp(argv[i], "-ip") == 0)
{
svr_ip = argv[i + 1];
}
else if(strcmp(argv[i], "-port") == 0)
{
svr_port = atoi(argv[i + 1]); //char* -> int
}
}
UdpApi ua; // 客户端的ua
CHECK_ERT(ua.CreateSocket());
while(1)
{
struct sockaddr_in svr_addr;
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(svr_port);
svr_addr.sin_addr.s_addr = inet_addr(svr_ip.c_str());
std::string data;
printf("please input message: ");
fflush(stdout);
std::cin >> data;
ua.SendData(data, &svr_addr, sizeof(svr_addr));
data.clear();
ua.RecvData(&data, NULL, NULL);
printf("svr say:%s\n", data.c_str());
}
ua.Close();
return 0;
}