前言
传输层协议的区别:
tcp:传输控制协议–面向连接,可靠传输,面向字节流
udp:用户数据报协议—无连接,不可靠,面向数据报
tcp保证可靠传输,但是传输速度没有udp快
tcp应用于安全性要求高的场景/udp应用于实时性要求高的场景
一、通信程序的编写
通信中总是两端主机进行通信,并且主动发起请求的一方称为客户端,被动首先接收请求的一方称为服务端
客户端:主动发起请求的一方----用户的一端
服务端:被动接收请求的一方----向用户提供服务的一端
网络通信程序的编写都使用的是套接字接口----socket接口
二、udp通信流程
1.udp流程图
2.套接字接口
1.创建套接字
int socket(int domain,int type,int protocol)
- domain:地址域–确定本次socket通信使用哪种协议版本的地址结构–不同的协议版本有不同的地址结构–AF_INET IPV4
- type:套接字类型(流式套接字-SOCK_STREAM–TCP所使用的/数据报套接字-SOCK_DGRAM—UDP所使用的)
- protocol:协议类型(通常就是传输层协议的选择 IPPROTO_TCP/IPPROTO__UDP),默认为0-流式默认tcp/数据报默认udp
- 返回值:文件描述符–非负整数–套接字所有其他接口的操作句柄;失败返回-1;
2.为套接字绑定地址信息
int bind(int sockfd,const struct sockaddr *addr,socklen_t len)
- sockfd:创建套接字返回的操作句柄
- addr:要绑定的地址信息
- len:实际地址结构长度
- ipv4通信使用的是struct scokaddr_in 地址信息
- 返回值:成功返回0,失败返回-1
3.接收数据
ssize_t recvfrom(int sockfd,char* buf, int len,int flag, struct sockaddr *peer_addr,socklen_t addlen);
- sockfd:socket操作句柄
- buf:一块缓冲区,用于接收从接收缓冲区中取出的数据
- len:想要接收的数据长度
- flag:操作选项标志,默认为0,表示阻塞操作
- peer_addr:发送方的地址信息
- addrlen:想要获取的地址信息长度以及返回实际长度
- 返回值:成功返回实际接收到的数据字节长度;失败返回-1;
4.发送数据
ssize_t sendto(int sockfd,char *data,int len ,int flag,struct sockaddr *peer_addr,socklen_t addlen);
- sockfd:socket操作句柄
- data:要发送的数据首地址
- len:想要发送的数据长度
- flag:操作选项标志,默认为0,表示阻塞操作
- peer_addr:接受方的地址信息
- addrlen:地址信息长度
- 返回值:成功返回实际发送的数据字节长度;失败返回-1;
5.关闭套接字
int close(fd);
3.字节序转换接口
- uint16_t htons(uint16_t data)/uint32_t htonl(uint32_t data);主机字节序到网络字节序的整型数据转换
- uint16_t ntohs(uint16_t data)/uint32_t ntohl(uint32_t data);网络字节序到主机字节序的整型数据转换
- in_addr_t inet_addr(char *ip);字符串IPV4的IP地址转换为网络字节序的整数IP地址
- char* inet_ntoa(struct in_addr nip);网络字节序整数IP地址转换为字符串的IPV4的IP地址
- const char *inet_ntop(int af,const void *src,char *dst,socklen_t size);将网络字节序的整数IP地址,转换为字符串IP地址—兼容ipv6
- int inet_pton(int af,const char* src,void *dst);将字符串的IP地址转换成网络字节序的整数IP地址
三.实现代码
udpsocket.hpp
#include <cstdio>
#include <string>
#include <unistd.h>
#include <netinet/in.h>//包含地址结构信息
#include <arpa/inet.h>//字节序转换接口
#include <sys/socket.h>//套接字接口信息
class UdpSocket {
public:
UdpSocket():_sockfd(-1){}
bool Socket() {//创建套接字
//socket(地址域, 套接字类型, 协议类型)
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (_sockfd < 0) {
perror("socket error");
return false;
}
return true;
}
// 为套接字绑定地址信息
bool Bind(const std::string &ip, uint16_t port) {
//定义IPV4地址结构 struct sockaddr_in
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);//htons将主机字节序短整型数据转换为网络字节序数据
addr.sin_addr.s_addr = inet_addr(ip.c_str());//将字符串IP地址转换为网络字节序
//bind(描述符, 地址信息, 地址信息长度)
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if (ret < 0) {
perror("bind error");
return false;
}
return true;
}
//接收数据,获取发送端地址信息
//Recv(&buf, &ip, &port)
//Recv(&buf);--不给予后两个参数,就会使用默认参数 = Recv(&buf, NULL, NULL);
bool Recv(std::string *buf, std::string *ip=NULL, uint16_t *port=NULL) {
//recvfrom(套接字句柄,接收缓冲区,数据长度,标志, 源端地址,地址长度)
struct sockaddr_in peer_addr;
socklen_t len = sizeof(struct sockaddr_in);
char tmp[4096] = {0};
int ret = recvfrom(_sockfd, tmp, 4096, 0, (struct sockaddr*)&peer_addr, &len);
if (ret < 0) {
perror("recvfrom error");
return false;
}
buf->assign(tmp, ret); // assign从指定字符串中截取指定长度的数据到buf中
if (port != NULL) {//根据地址判断用户是否想要获取地址信息
*port = ntohs(peer_addr.sin_port);//网络字节序到主机字节序的转换
}
if (ip != NULL) {
*ip = inet_ntoa(peer_addr.sin_addr);//网络字节序到字符串IP地址的转换
}
return true;
}
// 发送数据
bool Send(const std::string &data, const std::string &ip, const uint16_t port) {
//sendto(套接字句柄,数据首地址,数据长度,标志,对端地址信息,地址信息长度)
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = sendto(_sockfd, data.c_str(), data.size(), 0,
(struct sockaddr*)&addr, len);
if (ret < 0) {
perror("sendto error");
return false;
}
return true;
}
bool Close(){
if (_sockfd > 0) {
close(_sockfd);
_sockfd = -1;
}
return true;
}// 关闭套接字
private:
int _sockfd;
};
udp_cli.cpp
#include <iostream>
#include <string>
#include "udpsocket.hpp"
#define CHECK_RET(q) if((q)==false){return -1;}
int main (int argc, char *argv[])
{
//客户端参数获取的IP地址是服务端绑定的地址,也就是客户端发送数据的目标地址
//不是为了自己绑定的
if (argc != 3) {
std::cout << "Usage: ./udp_cli ip port\n";
return -1;
}
std::string srv_ip = argv[1];
uint16_t srv_port = std::stoi(argv[2]);
UdpSocket cli_sock;
//创建套接字
CHECK_RET(cli_sock.Socket());
//绑定地址(不推荐)
while(1) {
//发送数据
std::cout << "client say:";
std::string buf;
std::cin >> buf;
if (buf == "quit") {
break;
}
CHECK_RET(cli_sock.Send(buf, srv_ip, srv_port));
//接收数据
buf.clear();
CHECK_RET(cli_sock.Recv(&buf));//默认参数可以不用赋予
std::cout << "server say: " << buf << std::endl;
}
//关闭套接字
cli_sock.Close();
//...
return 0;
}
udp_src.cpp
#include <iostream>
#include <string>
#include "udpsocket.hpp"
#define CHECK_RET(q) if((q)==false){return false;}
int main (int argc, char *argv[])
{
//argc 表示程序运行参数的个数
//./udp_srv 192.168.2.2 9000
if (argc != 3) {
std::cout << "Usage: ./udp_srv ip port\n";
return -1;
}
uint16_t port = std::stoi(argv[2]);
std::string ip = argv[1];
UdpSocket srv_sock;
//创建套接字
CHECK_RET(srv_sock.Socket());
//绑定地址信息
CHECK_RET(srv_sock.Bind(ip, port));
while(1) {
//接收数据
std::string buf;
std::string peer_ip;
uint16_t peer_port;
CHECK_RET(srv_sock.Recv(&buf, &peer_ip, &peer_port));//接收对端数据以及地址信息
std::cout << "client["<<peer_ip<<":"<<peer_port<<"] say: "<<buf<<std::endl;
//发送数据
buf.clear();
std::cout << "server say: ";
std::cin >> buf;
CHECK_RET(srv_sock.Send(buf, peer_ip, peer_port));//谁发送了数据就给谁回复
}
//关闭套接字
srv_sock.Close();
return 0;
}