云计算百科
云计算领域专业知识百科平台

网络编程:UDP网络套接字简单使用

认识端口号

端口号是用于在网络传输过程中标识进程的一个数值。

  • 端口号(port)是传输层协议的内容.
  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用

        即使我们的进程pid也可以标识唯一的进程,但是我们仍然需要一个端口号,这样的目的是由于端口号运用于网络编程,而我们进程pid主要在于我们OS内部,可以将我们的网络编程和操作系统进行解耦。

        在TCP协议中,我们的是利用一个hash表存储的我们的进程PCB,将端口号经过hash函数找到确定的位置。

一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定

网络字节序

        我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
  • 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  • 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换

  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回
  • 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

UDP_socket编程

sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX DomainSocket. 然而, 各种网络协议的地址格式并不相同

  • 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结构体指针做为参数;

sockaddr结构

sockaddr_in 结构

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址

in_addr结构

使用API介绍

socket

#include <sys/types.h>    
#include <sys/socket.h>

int socket(int domain, int type, int protocol)

功能:创建socket文件描述符

参数:

        domain:AF_INET             IPv4 Internet protocols 

        type: SOCK_DGRAM      Supports datagrams  (connectionless,  unreliable                                                                                     messages of a fixed maximum length).

        protocol:0

bind

#include <sys/types.h>        

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:绑定端口号

参数:

        sockfd:套接字文件描述符
        addr:当前主机信息

        addlen:addr的字节长度

recvfrom

#include <sys/types.h>

#include <sys/socket.h>

 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                             struct sockaddr *src_addr, socklen_t *addrlen);

功能:接收发送到当前主机的信息

参数:

        sockfd:套接字文件描述符

        buf:数据缓冲区

        len:缓冲区长度

        flags:设为0,阻塞等待

        src_addr:发送数据端口的主机信息

        addrlen:src_addr的字节长度

        

sendto

 #include <sys/types.h>

#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                        const struct sockaddr *dest_addr, socklen_t addrlen);

功能:发送消息

参数:

        sockfd:套接字文件描述符

        buf:缓冲区

        len:缓冲区长度

        flags:设为0

        dest_addr:发送目标的主机信息

        addr_len:dest_addr的字节长度

简单的UDP网络程序

服务端

变量设计:

    int sockfd_:记录套接字fd

    uint16_t port_:记录端口号

    string addr_:记录ip地址

    bool isrunning:是否正在运行

函数设计:

构造函数

        初始化我们的变量即可

  Udpserver(const uint16_t& port = defaultprot, const string & addr = defaultip) : sockfd_(0), port_(port),addr_(addr), isrunning(false)

    {

    }

Init 初始化函数

        在该函数中,我们需要创建我们的网络套接字,然后利用sockaddr_in结构传入我们的服务端的ip和端口等信息。然后利用bind绑定我们的端口号。即可完成初始化函数

void Init()

    {

        // 1、网络套接字

         sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);

        if (sockfd_ < 0)

        {

            lg(Fatal, "socket create error, sockfd: %d", sockfd_);

            exit(SOCKET_ERR);

        }

        lg(Info, "socket create success, sockfd: %d", sockfd_);

        // 2、bind绑定

        struct sockaddr_in local;

        bzero(&local, sizeof(local));//将local清空一下

        local.sin_family = AF_INET;                       // 域

        local.sin_addr.s_addr = inet_addr(addr_.c_str()); // 非零标识可以接收该主机任意IP接收的数据报,指定则只能接收对应IP

        local.sin_port = htons(port_);                    // 端口转为网络字节序

        if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)

        {

            lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));

            exit(BIND_ERR);

        }

        lg(Info, "bind sucess, errno: %d, err string: %s", errno, strerror(errno));

    }

Run 运行函数

        完成初始化后,我们进行运行我们的服务器,首先需要接收来自客户端的消息和客户端的主机信息(ip,端口号等),需要发送给客户端时只需要利用我们接收时的sockadd_in结构即可

void Run()

    {

        isrunning = true;

        char inbuffer[size];

        while (isrunning)

        {

            //接收客户端消息

            struct sockaddr_in client;//接收来自客户端的主机信息

            socklen_t len = sizeof(client);

            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer)-1, 0, (struct sockaddr *)&client, &len);

            if (n < 0)

            {

                lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));

                continue;

            

            inbuffer[n]= 0;

            cout<<inbuffer<<endl;

            //返回消息给客户端

            string info = inbuffer;

            string retstr = "Server say@: "+info;

            sendto(sockfd_,retstr.c_str(),retstr.size(),0,(struct sockaddr*)&client,len);

        }

    }

赞(0)
未经允许不得转载:网硕互联帮助中心 » 网络编程:UDP网络套接字简单使用
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!