网络套接字(Udp实现简单的网络通信)

目录

1.源IP和目的IP

2.端口号

(1)套接字

(2)PIDvs端口号

3.TCP和UDP

4.网络字节序

5.网络通信的接口

(1)头文件

(2)常用函数

创建套接字

套接字绑定协议地址

发送接受数据

6.通信的实现

(1)服务端

(2)客户端


1.源IP和目的IP

两个主机在进行通信的时候,是通过各自的IP地址来找到对方的。对于一个通信信息来讲,信息发出者的IP称为源IP,信息接收者的IP称为目的IP。

源IP和目的IP存放在信息的报头中,最大的意义在于:知道一个报文如何进行路径选择。

2.端口号

(1)套接字

当数据从一台主机发送到另一台主机上后,就需要进行数据处理,在主机上右多个进程,如何知道是发送到哪个进程的呢?这就需要用到端口号来标识2唯一的一个进程(用该进程绑定主机的一个端口号)。网络通信分为两步,先通过IP找到对应主机,再通过端口号找到对应进程。

IP+端口号就可以唯一的标识互联网中的一个进程。我们将IP+端口号称为一个套接字。

(2)PIDvs端口号

每一个进程都有自己的pid,为什么还需要端口号呢?因为这方便解耦。使用PID的话,一旦某台主机的进程的PID发生了改变,那么整个网络都需要发生变化。为了导致整个网络系统的稳定性,使用端口号来标识进程。

一个进程可以关联多个端口号

一个端口号只能关联一个进程

3.TCP和UDP

目前我们只需要知道,TCP是可靠的,有链接的;UDP是不可靠的,无链接。

注意,这里的可靠和不可靠是中性词,可靠意味着更多资源的消耗。

4.网络字节序

当两台主机进行通信的时候,它们可能是大端机或者小端机。但是为了方便通信,网络规定所有发送到网络中的数据必须是以大端(低地址处存放高字节)的形式。如果是小端机需要将小端转化为大端再进行发送。

5.网络通信的接口

(1)头文件

#include<sys/types.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

(2)常用函数

创建套接字

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_INFT。

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的第三个参数表示的是该协议地址结构的大小。

发送接受数据

接收:

size_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);

第一个参数表示套接字的文件描述符,第二个参数表示缓冲区的指针,第三个缓冲区的大小,第四个参数表示方式,第五个参数表示向谁发送数据,第六个参数表示发送数据的长度。

6.通信的实现

(1)服务端

#include <iostream>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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;
}

(2)客户端

#include <iostream>
#include <string>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<stdlib.h>

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值