要想使网络中的计算机能够进行通信,必须为每台计算机指定一个标识号,通过这个标识号来指定接受数据的计算机或者发送数据的计算机。在TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机
。
目前,IP地址广泛使用的版本是IPv4,它是由4个字节大小的二进制数来表示,如:00001010000000000000000000000001。由于二进制形式表示的IP地址非常不便记忆和处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表示,数字间用符号“.”分开,如 “192.168.1.100”。
源ip地址
就是发出这个数据包的电脑的ip地址,它是数据的来源。
目标ip地址
就是数据最终要到达的那台电脑的ip地址。
路由器就是根据目标ip地址确定如何转发数据包,最终把数据以最佳路由发送到目标主机的。
访问目标计算机中的某个应用程序,还需要指定端口号。
在计算机中,不同的应用程序是通过端口号区分的。源端口
就是本机程序用来发送数据的端口。目的端口
就是对方主机用哪个端口接收。注意
:一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定。网络字节序定义
:收到的第一个字节被当作高位看待,这就要求发送端发送的第一个字节应当是高位。而在发送端发送数据时,发送的第一个字节是该数字在内存中起始地址对应的字节。可见多字节数值在发送前,在内存中数值应该以大端法存放。通讯字节序:大端
**。只有在多字节数据处理时才需要考虑字节序,运行在同一台计算机上的进程相互通信时,一般不用考虑字节序,异构计算机之间通讯,需要转换自己的字节序为网络字节。主机字节序是小端,所以才需要进行字节序转换。字节序是指多字节数据的存储顺序,在设计计算机系统的时候,有两种处理内存中数据的方法:大端格式、小端格式。
bool isLittleEndian()
{
union U
{
int i;
char c;
};
U u;
u.i = 0x12345678;
return u.c == 0x78;
}
#include
// 将 32位主机字节序数据转换成网络字节序数据
//(h:host, n:net,l:long)
uint32_t htonl(uint32_t hostint32);
// 将 16 位主机字节序数据转换成网络字节序数据
uint16_t htons(uint16_t hostint16);
// 将 32 位网络字节序数据转换成主机字节序数据
uint32_t ntohl(uint32_t netint32);
// 将 16 位网络字节序数据转换成主机字节序数据
uint16_t ntohs(uint16_t netint16);
struct sockaddr很多网络编程API诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是其他的,由地址族确定,然后函数内部再强制转化为所需的地址类型。
结构图
:
struct sockaddr {
unsigned short sa_family; // 2 bytes address family, AF_xxx unsiged short
char sa_data[14]; // 14 bytes of protocol address
};
struct sockaddr_in {
short sin_family; // 2 bytes e.g. AF_INET, AF_INET6
unsigned short sin_port; // 2 bytes e.g. htons(3490)
struct in_addr sin_addr; // 4 bytes see struct in_addr, below
char sin_zero[8]; // 8 bytes zero this if you want to
};
struct in_addr {
unsigned long s_addr; // 4 bytes load with inet_pton()
};
无需建立连接就可以发送封装的 IP 数据包的方法
。传输层
有两个主要协议,互为补充。无连接的是 UDP,它除了给应用程序发送数据包功能并允许它们在所需的层次上架构自己的协议之外,几乎没有做什么特别的事情。面向连接的是 TCP,该协议几乎做了所有的事情。传输层协议
无连接
不可靠传输
面向数据报(SOCK_DGRAM)
UDP的框架图
:#include
#include
int socket(int domain, int type, int protocol);
用例
:
AF_INET
IPv4 Internet protocols //用于IPV4AF_INET6
IPv6 Internet protocols //用于IPV6SOCK_STREAM
Provides sequenced, reliable, two-way, connection-based byte streams. //用于TCPSOCK_DGRAM
Supports datagrams (connectionless, unreliable messages ). //用于UDPSOCK_RAW
Provides raw network protocol access. //RAW类型,用于提供原始网络访问#include
#include
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
用例
:
#include
#include
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
用例
:
#include
#include
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
用例
:
#include"udpClient.hpp"
void Usage(std::string proc)
{
std::cout<<"Usage: "<<proc<<" svr_ip svr_port"<<std::endl;
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
udpClient uc(argv[1],atoi(argv[2]));
uc.initClient();
uc.start();
return 0;
}
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
class udpClient
{
public:
//Server ip , port;
udpClient(std::string _ip="127.0.0.1",int _port=8080)
:ip(_ip)
,port(_port)
{
}
void initClient()
{
sock = socket(AF_INET,SOCK_DGRAM,0);
std::cout<<"sock"<<sock<<std::endl;
// struct sockaddr_in local;
// local.sin_family = AF_INET;
// local.sin_port = htons(port);
// local.sin_addr.s_addr = inet_addr(ip.c_str());
//
//
// if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
// {
// std::cerr<<"bind error!\n"<
// exit(1);
// }
}
void start()
{
// char msg[64];
std::string msg;
struct sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
peer.sin_addr.s_addr = inet_addr(ip.c_str());
while(1)
{
std::cout<<"Please Enter: ";
std::cin>>msg;
if(msg == "quit")
{
break;
}
sendto(sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));
char echo[128];
ssize_t s=recvfrom(sock,echo,sizeof(echo)-1,0,nullptr,nullptr);
echo[s]='\0';
if(s>0)
{
std::cout<<" server: "<<echo<<std::endl;
}
// msg[0]='\0';
// struct sockaddr_in end_point;
// socklen_t len = sizeof(end_point);
// ssize_t s=recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);
// if(s>0)
// {
// msg[s]='\0';
// std::cout<<"Client :" << msg <
// std::string echo_string = msg;
// echo_string += " [server echo!]";
// sendto(sock,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&end_point,len);
// }
}
}
~udpClient()
{
close(sock);
}
private:
std::string ip;
int port;
int sock;
};
#include"udpServer.hpp"
void Usage(std::string proc)
{
std::cout<<"Usage: "<< proc <<"local_ip local_proc"<<std::endl;
}
int main(int argc,char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
udpServer *up = new udpServer(atoi(argv[1]));
up->initServer();
up->start();
delete up;
}
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class udpServer
{
public:
udpServer(int _port=8080)
// :ip(_ip)
:port(_port)
{
dict.insert(std::pair<std::string,std::string>("apple","苹果"));
dict.insert(std::pair<std::string,std::string>("banana","香蕉"));
dict.insert(std::pair<std::string,std::string>("string","字符串"));
dict.insert(std::pair<std::string,std::string>("int","整形"));
dict.insert(std::pair<std::string,std::string>("float","浮点型"));
}
void initServer()
{
sock = socket(AF_INET,SOCK_DGRAM,0);
std::cout<<"sock"<<sock<<std::endl;
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
std::cerr<<"bind error!\n"<<std::endl;
exit(1);
}
}
void start()
{
char msg[64];
while(1)
{
msg[0]='\0';
struct sockaddr_in end_point;
socklen_t len = sizeof(end_point);
ssize_t s=recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);
if(s>0)
{
char buff[16];
sprintf(buff,"%d",ntohs(end_point.sin_port));
std::string cli = inet_ntoa(end_point.sin_addr);
cli += ":";
cli += buff;
msg[s]='\0';
std::cout<<cli<< ":"<< msg <<std::endl;
//std::string echo_string = msg;
std::string echo = "unknow";
auto it = dict.find(msg);
if(it != dict.end())
{
echo = dict[msg];
}
//echo_string += " [server echo!]";
sendto(sock,echo.c_str(),echo.size(),0,(struct sockaddr*)&end_point,len);
}
}
}
~udpServer()
{
close(sock);
}
private:
// std::string ip;
int port;
int sock;
std::map<std::string,std::string> dict;
};
防止这样的永久阻塞的一般方法是给客户的recvfrom调用设置一个超时,大概有这么两种方法
:
数据报
走的路由并不一样,有的路由顺畅,有的却拥塞,这导致每个数据报到达目的地的顺序就不一样了。UDP协议并不保证数据报的按序接收。
滑动窗口进行流量控制和拥塞控制
,UDP因为其特点无法做到。UDP接收数据时直接将数据放进缓冲区内,如果用户没有及时将缓冲区的内容复制出来放好的话,后面的到来的数据会接着往缓冲区放,当缓冲区满时,后来的到的数据就会覆盖先来的数据而造成数据丢失(因为内核使用的UDP缓冲区是环形缓冲区)。因此,一旦发送方在某个时间点爆发性发送消息,接收方将因为来不及接收而发生信息丢失。增大UDP缓冲区
,使接收方的接收能力大于发送方的发送能力
。