两个主机在进行通信的时候,是通过各自的IP地址来找到对方的。对于一个通信信息来讲,信息发出者的IP称为源IP,信息接受者的IP称为目的IP。
源IP和目的IP存放在信息的报头中,最大的意义在于:指导一个报文如何进行路径选择。
当数据从一台主机发送到另一台主机上后,就需要进行数据处理,在主机上有多个进程,如何知道是发送给哪一个进程呢?这就需要用到端口号来标识唯一的一个进程(用该进程绑定主机的一个端口号)。网络通信分为两步,先通过IP找到对应主机,再通过端口号找到对应进程。
IP+端口号就可以唯一的标识互联网中的一个进程。我们将IP+端口号称为一个套接字。
每一个进程都有自己的pid,为什么还需要端口号呢?因为这方便解耦。使用PID的话,一旦某一台主机的进程的PID发生了改变,那么整个网络都需要发生变化。为了导致整个网络系统的稳定性,使用端口号来标识进程。
一个进程可以关联多个端口号。
一个端口号只能关联一个进程。
目前我们只需要知道,TCP是可靠的,有链接;UDP是不可靠的,无链接。
注意,这里的可靠和不可靠是中性词,可靠意味着更多的资源消耗。
当两台主机进行通信的时候,它们可能是大端机或者小端机。但是为了方便通信,网络规定所有发送到网络中的数据必须是以大端的形式。如果是小端机需要自己将数据转化为大端再进行发送。
这四个头文件是必须被记住的。
#include
#include
#include
#include
int socket(int domain, int type, int protocol);
第一个参数,代表的是协议族,它的值可以是:
当我们使用Udp协议的时候,选择AF_INET
第二个参数代表的是套接字类型,它的值可以是:
在Udp中,套接字类型为SOCK_DGRAM。
第三个参数表示的是协议类型,当前两个参数确定之后,第三个参数会被自动识别,这里我们填0即可。
当创建套接字失败,则返回-1,如果成功则返回的是套接字的文件描述符,套接字是一个抽象层,用户可以将其像文件一样打开,读写操作。但是使用的接口和文件操作的完全不同。
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
bind函数将一个本地协议地址赋予一个套接字,它的第一个参数是套接字的文件描述符。
第二个参数表示一个指向特定协议地址结构的指针,第三个参数表示该地址结构的长度。
理解这个函数之前,我们要了解一下sockaddr这个结构体,它是一个类似多态的可以接收两种具体结构体类型的结构体:
不同的协议对应不同的协议地址结构,但是都可以作为实参传入sockaddr结构体中,不使用void的原因是,当时还没有提出void的概念。在使用的时候需要强转类型。
我们可以查一下该结构体的内容:
建立一个协议地址结构,需要对该结构体的内容进行初始化。
其中_SOCKADDR_COMMON是一个宏定义,使用family来替换它,传入的是协议族,这里我们可以传入AF_INET。
sin_port表示一个端口号,需要我们自己定义一个端口号并传入。端口号不能直接传入,而是需要转化成网络序列再进行传入,将端口号转化为网络序列的函数为:
uint16_t htons(uint16_t hostshort);
传入的内容就是16进制的端口号,返回该端口号的网络序列。
sin_addr是一个结构体,它里面有一个变量:
我们向该变量中传入本机的IP。但是传入本机IP的时候,需要将人可以识别的点分十进制转换成4字节整数IP。同时还要考虑大小端。
系统提供了inet_addr函数来帮助实现这一过程:
in_addr_t inet_addr(const char *cp);
向其中传入的就是点分十进制IP,将返回值传给s_addr即可。对于服务器来说,如果绑定的是确定的IP,那么只有发向该IP的数据才会被交给网络进程,但是一般服务器可能由多张网卡,配置了多个IP,我们需要不是发给某个IP的数据,而是发给所有IP的数据,因此在绑定服务器IP的时候,通常使用INADDR_ANY。而以上的结构通常是客户端来使用的。
bind的第三个参数表示的是该协议地址结构的大小。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
第一个参数表示套接字的文件描述符,第二个参数表示缓冲区的大小,第三个参数表示读的方式,通常默认为0。
第四第五个参数表示的是和服务端进行通信的客户端套接字的信息,表明是谁发的数据。
返回值表示读到多少字节。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
第一个参数表示套接字的文件描述符,第二个参数表示缓冲区的大小,第三个参数表示方式,第四个参数表示向谁发送数据,第五个参数表示发送数据的长度。
#include
#include
#include
#include
#include
#include
#include
const uint16_t port = 8080;
// udp_server,细节最后在慢慢完善
int main()
{
//1. 创建套接字,打开网络文件
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock < 0){
std::cerr << "socket create error: " << errno << std::endl;
return 1;
}
//2. 给该服务器绑定端口和ip(特殊处理)
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port); //此处的端口号,是我们计算机上的变量,是主机序列
local.sin_addr.s_addr = INADDR_ANY;
//给套接字绑定主机的IP和端口号
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
std::cerr << "bind error : " << errno << std::endl;
return 2;
}
//3. 提供服务
bool quit = false;
#define NUM 1024
char buffer[NUM];
while(!quit)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);//从peer这个协议地址结构接收数据
std::cout << "client# " << buffer << std::endl;
std::string echo_hello = "hello";
sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr*)&peer, len);//向peer地址结构发送数据
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
void Usage(std::string proc)
{
std::cout << "Usage: \n\t" << proc << " server_ip server_port" << std::endl;
}
// ./udp_client server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 0;
}
// 1. 创建套接字,打开网络文件
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "socket error : " << errno << std::endl;
return 1;
}
//客户端需要显示的bind的吗??
// a. 首先,客户端必须也要有ip和port
// b. 但是,客户端不需要显示的bind!一旦显示bind,就必须明确,client要和哪一个port关联
// client指明的端口号,在client端一定会有吗??有可能被占用,被占用导致client无法使用
// server要的是port必须明确,而且不变,但client只要有就行!一般是由OS自动给你bind()
// 就是client正常发送数据的时候,OS会自动给你bind,采用的是随机端口的方式!
// b. 你要给谁发??
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
// 2.使用服务
while (1)
{
// a. 你的数据从哪里来??
std::string message;
std::cout << "输入# ";
std::cin >> message;
sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
//此处tmp就是一个”占位符“
struct sockaddr_in tmp;
socklen_t len = sizeof(tmp);
char buffer[1024];
recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&tmp, &len);
std::cout << "server echo# " << buffer << std::endl;
}
return 0;
}
如果云服务器没有开放端口号,使用本地IP:
127.0.0.1
即可进行通信。