传输层的协议:
ip地址:
在网络中唯一标识一台主机
- IPV4:uint32_t DHCP NAT
- IPV6 : uint8_t addr[16] —向前并不兼容IPV4
每一条数据都必须包含源地址和目的地址:因为每条网络中的数据都必须确定是从那个主机来到那个主机去
端口号
- 在一台主机上唯一标识一个进程
- 在一台主机上,当网卡收到网络数据,操作系统能够通过端口分辨出,这个数据该交给拿个进程处理
- uint16_t:端口号范围:
0~65535,0~1023这些端口不推荐用户使用,因为0~1023这些端口已经被一些知名协议所占用,或作为预留
- 一个端口号只能被一个进程所占用,一个进程可以使用多个端口号
- 每一条数据都必须包含源端口和目的端口;因为每条网络中的数据都必须确定自己是哪个进程发送的,因该哪个进程进行处理
tcp协议;
传输控制协议—面向连接,可靠传输,面向字节流,实现数据可靠传输,传输灵活但是会有粘包问题
udp协议
用户数据报协议–无连接,不可靠,面向数据报,实现不可靠传输,传输不够灵活,但是不会存在粘包问题
网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分
磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分,
网络数据流同样有大端小端之分
那么如何定义网络数据流的地址呢?
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
字节序
cpu在内存中对数据的存取顺序
大段字节序:低地址存高位
小端字节序:低地址存高位
主机字节序的大小端取决于cpu架构:x86-little,mips-big
如何判断主机字节序:union联合体
网络字节序:为了避免两台不同字节序主机之间通信会造成数据二义性的情况,因此规定在网络中进行通信的时候,使用网络字节序;而网络字节序就是大端字节序
网络通信时,关注的数据字节序转换:存储大于一个字节类型的数据
socket编程
socket 常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器) int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听socket (TCP, 服务器) int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器) int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP, 客户端) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockaddr结构
- 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结构体指针做为 参数;
UDP编程
UDP编程接口
-
创建套接字----通过套接字使进程与网卡之间建立联系(内核中创建socket结构体)
int socket(int domain, int type, int protocol);
参数1:地址域。 AF_INET–使用IPv4网络协议地址域
参数2:套接字类型SOCK_STREAM 流式套接字-提供字节流服务,默认使用TCP协议 SOCK_DGRAM 数据报套接字---提供数据报传输服务,默认使用UDP协议
参数3:传输层协议
0 根据套接字类型使用默认协议 IPPROTO_TCP TCP协议 IPPROTO_UDP UDP协议
返回值:套接字描述符 失败返回-1
-
为套接字绑定地址信息
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
参数1:创建套接字返回的套接字描述符
参数2:地址信息
参数3:地址信息长度
返回值:成功:0 失败:-1 -
接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数1:套接字描述符
参数2:用于接受数据的缓冲区
参数3:想要接受的数据长度(限制buf越界)
参数4:选项标志(0表示阻塞接受数据)
参数5:发送端地址信息
参数6:地址信息长度(输入输出型参数,防止参数5越界。指定想要的长度,返回实际的长度)
返回值:返回实际接受的数据长度 失败返回-1 -
发送数据:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数1:套接字描述符
参数2:要发送的数据
参数3:要发送的数据长度
参数4:要发送的长度
参数5:0默认阻塞发送
参数6:目的端地址
返回值:实际的发送长度,失败返回-1 -
关闭套接字
int close(int fd);
fd :套接字描述符
返回值:0,失败返回-1
通信模型
c++封装UDP–socket类
udpsocket.hpp
/*
*实现一个UdpSocket类,向外提供方便的套接字操作接口
*
*bool Socket() 创建套接字
*bool Bind(std::string &ip,uint16_t port)
*bool Recv(std::string &buf,std::string &ip,uint16_t port)
*bool Send(std::string &buf,std::string &ip,unit16_t port)
*bool Close()
*
*/
#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define CHECK_RET(q) if((q)==false){return -1;}
class UdpSocket
{
public:
UdpSocket():_sockfd(-1){
}
~UdpSocket(){
close(_sockfd);
}
bool Socket()
{
//int socket(int domain, int type, int protocol);
_sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(_sockfd<0){
perror("socket error");
return false;
}
}
bool Bind(std::string &ip,uint16_t port){
struct sockaddr_in addr;//
addr.sin_family=AF_INET;//地址域
//因为端口和地址信息都是要在网络上传输,所以需要地址转换
//uint32_t htonl(uint32_t hostlong);
//将32的数据从主机字节序转换为网络字节序
//uint16_t htons(uint16_t hostshort);
//将16位的数据从主机字节序转换为网络字节序
//uint32_t ntohl(uint32_t netlong);
//将32位的数据从网络字节序转换位主机字节序
//uint16_t ntohs(uint16_t netshort);
//将16位的数据从网络字节序转换为主机字节序
addr.sin_port=htons(port);//端口信息
//in_addr_t inet_addr(const char *cp);
//将字符串点分十进制IP地址转换为网络字节序IP地址
// char *inet_ntoa(struct in_addr in);
// 将网络字节序IP地址转换成为字符串点分十进制IP地址
addr.sin_addr.s_addr=inet_addr(ip.c_str());
// int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
socklen_t len=sizeof(struct sockaddr_in);
int ret=bind(_sockfd,(sockaddr *)&addr,len);
if(ret<0){
perror("bind error");
return false;
}
return true;
}
bool Recv(std::string &buf,std::string &ip,uint16_t &port){
//ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
// struct sockaddr *src_addr, socklen_t *addrlen);
char tmp[4096]={0};
struct sockaddr_in addr;
socklen_t len=sizeof(struct sockaddr_in);
//不但接受数据,还接受谁给他发的数据
int ret=recvfrom(_sockfd,tmp,4096,0,(sockaddr*)&addr,&len);
if(ret<0){
perror("recv error");
return false;
}
//截取一段数据,放到buf里
buf.assign(tmp,ret);
ip=inet_ntoa(addr.sin_addr);
port=ntohs(addr.sin_port);
return true;
}
bool Send(std::string &buf,std::string &ip,uint16_t port){
//ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
// const struct sockaddr *dest_addr, socklen_t addrlen);
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,buf.c_str(),buf.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;
}
}
private:
int _sockfd;
};
服务端程序:
#include"udpsocket.hpp"
int main(int argc,char *argv[])
{
if(argc!=3){
printf("./udp_srv ip port\n");
return -1;
}
std::string srv_ip=argv[1];
uint16_t srv_port =atoi(argv[2]);
UdpSocket sock;
CHECK_RET(sock.Socket());//创建套接字
CHECK_RET(sock.Bind(srv_ip,srv_port));//绑定地址信息
while(1){
std::string cli_ip;
uint16_t cli_port;
std::string buf;
sock.Recv(buf,cli_ip,cli_port);
printf("client-[%s:%d]--say:%s\n",cli_ip.c_str(),cli_port,buf.c_str());
buf.clear();
printf("server say:");
fflush(stdout);
std::cin>>buf;
sock.Send(buf,cli_ip,cli_port);
}
sock.Close();
return 0;
}
客户端程序:
#include"udpsocket.hpp"
int main(int argc,char *argv[])
{
if(argc!=3){
std::cout<<"./udp_cli ip port";
return -1;
}
std::string srv_ip=argv[1];
uint16_t srv_port=atoi(argv[2]);
UdpSocket sock;
CHECK_RET(sock.Socket());//创建套接字
while(1){
std::string buf;
std::cout<<"client say:",
fflush(stdout);
std::cin>>buf;
sock.Send(buf,srv_ip,srv_port);
buf.clear();
sock.Recv(buf,srv_ip,srv_port);
std::cout<<"server say:"<<buf<<std::endl;
}
sock.Close();
return 0;
}