网络入门---网络编程初步认识和实践(使用udp协议)

news2024/11/20 11:48:43

目录标题

  • 前言
  • 准备工作
  • udpserver.hpp
    • 成员变量
    • 构造函数
    • 初始化函数(socket,bind)
    • start函数(recvfrom)
  • udpServer.cc
  • udpClient.hpp
    • 构造函数
    • 初始化函数
    • run函数(sendto)
  • udpClient.cc
  • 测试

前言

在上一篇文章中我们初步的认识了端口号的作用,ip地址和MAC地址在网络通信时起到的作用,以及网络套接字的基本了解,那么这篇文章我们就对网络编程常见的接口进行讲解,并且使用这些接口实现一个简单udp通信功能,本篇文章采用边讲边实现的方式来带着大家理解这些函数,在目录里面我添加实现该功能时哪些函数会被讲解,大家可以看一下。

准备工作

我们下面要实现一个简单的通信功能,该功能分为客服端和服务端,客户端向服务端发送数据,服务端将发送过来的数据打印到屏幕上即可:
在这里插入图片描述
因为客户端和服务端要同时运行所以这里就创建两个含有main函数的文件udpclient.cc udpserver.cc
这里采用面向对象的方式进行实现,所以这里就创建两个类udpClient udpServer来分别描述这里的发送消息和接收消息的行为,并分别将两个类放到文件udpClient.hpp udpServer.hpp里面,最后再创建一个makefile文件:
在这里插入图片描述
我们要给每个类都添加对应的构造函数,析构函数,初始化函数,运行函数(开始执行功能的函数),所以要实现的框架就是下面这样:

//udpServer.hpp
class udpServer
{
public:
    udpServer()//构造函数
    {}
    void initServer()//初始化函数
    {}
    void start()//运行函数
    {}
    ~udpServer()//析构函数
    {}
private: 
};
//udpClient.hpp
class udpClient
{
  public:
  udpClient()
  {}
  void initClient()
  {}
  void run()
  {}
  ~udpClient()
  {}
  private:  
};

那么接下来的工作就是先实现udpserver类的具体内容。

udpserver.hpp

成员变量

udpserver用来描述服务端进程的类,所以他得有自己对应的ip地址和端口号,那么在类中就创建一个string类型的对象用来存储ip地址和一个16位的无符号整数来存储端口号:

class udpServer
{
public:
    udpServer(){}
    void initServer(){}
    void start(){}
    ~udpServer(){}
private:
    string  _ip;//存储ip地址
    uint16_t _port;//存储端口号
};

构造函数

构造函数就负责对这两个类内成员变量进行初始化,因为有两个变量所以构造函数就有两个参数,因为在函数里面不会对参数进行修改所以参数的类型就得是const &类型,那么这里的代码如下:

 udpServer(const uint16_t& port, const string & ip)//构造函数
    //构造函数就负责获取ip和端口号
    :_ip(ip)
    ,_port(port)
    {}

大家在后面学习的时候会发现ip地址是可以通过某种方式被设置的,所以这里我们就先创建一个全局变量defaultip将其内容初始化为全0,然后将缺省参数设置为defaultip,那么这里代码如下:

static const string defaultIp="0.0.0.0";
class udpServer
{
public:
    udpServer(const uint16_t& port, const string & ip=defaultIp)//构造函数
    //构造函数就负责获取ip和端口号
    :_ip(ip)
    ,_port(port)
    {}
    void initServer(){}
    void start(){}
    ~udpServer(){}
private:
    string  _ip;//存储ip地址
    uint16_t _port;//存储端口号
};

初始化函数(socket,bind)

当前是使用udp协议来进行通信,所以在通信之前就得使用套接字socket来创建一套网络通信的文件机制,也就相当于在底层创建了一个网卡文件,然后将该文件与网卡设备关联起来,我们来看看这个函数的参数:
在这里插入图片描述
第一个参数是标记位表示当前你想进行的是网络通信还是本地通信,该参数的标记位有很多:
在这里插入图片描述
但是实际上经常使用的就只有前两个:AF_UNIX(本地通信)和AF_INET(网络通信)也就是上一篇文章中的这张图片图片:
在这里插入图片描述
第二个参数也是标记位表示当前套接字提供服务的类型也就是socket提供的能力类型,该标记位有下面这些:
在这里插入图片描述
比如说SOCK_STREAM就表示的是流式套接字,说人话就是该标记位在底层开了一套TCP策略来进行通信,SOCK_DGRAM表示的就是用户数据报套接字说人话就是在底层开了一套UDP策略来进行通信,SOCK_RAW表示的就是原始套接字等等等,所以下面我们在使用socket的时候第一个传输就传AF_INET,第二个参数就传递SOCK_DGRAM,第三个参数表示你想使用TCP_PROTOCOL还是UDP_PROTOCOL,但是前两个参数的确定就已经确定socket套接字所提供的功能,所以第三个参数就显得有点画蛇添足,所以在传递第三个参数的时候直接传递0即可,那么这就是socket参数的意义,socket函数执行完之后就会返回一个int类型的数据,该数据就是一个文件描述符也就是之前说的在底层创建一个和网卡相关联的文件的文件描述符,通过这个描述符就可以接受和发送消息, 如果创建套接字失败该函数就会返回-1,所以对网络的操作就和相当于之前对文件的操作,对网络的读写相当于对文件的读写那么,所以在初始化函数里面我们就可以创建一个变量来接收socket函数的返回值,然后对该值进行判断如果等于-1,我们就使用errno打印错误码和错误码对应的原因,并使用exit函数结束该进程,这里为了方便查看退出的原因就可以使用枚举来进行替换,那么这里的代码如下:

static const string defaultIp="0.0.0.0";
enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR};
class udpServer
{
public:
    udpServer(const uint16_t& port, const string & ip=defaultIp)//构造函数
    //构造函数就负责获取ip和端口号
    :_ip(ip)
    ,_port(port)
    {}
    void initServer()//初始化函数
	//初始化函数里面就创建对应的端口号,然后对端口号进行bind
	{
	   _sockfd=socket(AF_INET,SOCK_DGRAM,0);
	   if(_sockfd==-1)
	   {
	       //运行到这里说明创建端口失败
	       cout<<"socket error:"<< errno<<strerror(errno)<<endl;
	       exit(SOCKET_ERR);
	   }
	   cout<<"socket success"<<" : "<<_sockfd<<endl;
	}
    void start(){}
    ~udpServer(){}
private:
    string  _ip;//存储ip地址
    uint16_t _port;//存储端口号
};

套接字创建完成之后这里就存在一个问题,我们上篇文章说过套接字是要有自己的ip地址和端口的,那这个套接字知道自己所要服务的ip地址和端口吗?答案是不知道的,所以我们接下来就要将端口和ip地址绑定到这个套接字上面,那么这里就要用到函数bind
在这里插入图片描述
该函数的第一个参数表示要绑定哪个端口号也就是之前socket函数的返回值,第二个参数是一个sockaddr的结构体类型指针,在上一篇文章中我们说过因为套接字的类型有很多种并且当时创建接口的时候void*这种类型还没有创建出来,所以这里就得创建了一个新的结构体类型以掩盖底层套接字不同,这个结构体就是sockaddr,有了这个类型之后就可以用一个接口来服务多个套接字,该结构的构成如下:
在这里插入图片描述
虽然传递参数得是sockaddr的指针类型,但是实际在填写的内容的时候依然得按照sockaddr_in的类型来进行填写,该结构的构成如下:
在这里插入图片描述
最上面表示你想要通信的类型然后就是你要绑定的端口号和ip地址,所以我们得先创建一个结构体对象然后再以取地址加强制类型转换的形式进行参数传递,该函数的第三个参数是一个socklen_t的类型实际上就是一个整形用来表示你传递的结构体长度,因为每个套接字结构体的长度都不一样所以将长度传递给他之后他便知道了你之前填写的是哪种类型,他在函数内部再将第二个参数的类型转换回来,那么这就是bind函数的参数接收,该函数绑定成功之后就会返回0如果返回失败就返回-1,所以我们就可以根据该函数的返回值来进行判断是否成功,就接下来我们就先完成sockaddr_in结构体的填写,首先该结构体的内容如下:

struct sockaddr_in
{
    _SOCKADDR_COMMON(sin_);
    in_port_t sin_port;
    struct in_addr sin_addr;
    //....
};

_SOCKADDR_COMMON(sin_);是一个宏,该宏的定义如下:

#define  _SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family

这里的##就是用来形成新符号的,所以这里传递过来一个sin_最终这个参数就会形成sin_family,所以这个宏就会替换成为下面这样:

_SOCKADDR_COMMON(sin_);
sa_family_t sin_family;

而这个sa_family_t类型就是之前说的协议家族也就是AF_INET,AF_UNIX等等等,结构体的第二个参数就是对应的端口号虽然是in_port_t类型但是本质上还是一个16位的无符号整数

typedef uint16_t in_Port_t;

第三个参数虽然是一个结构体但是in_addr结构体内部就只有一个in_addr_t类型的变量:

typedef uint32_t in_addr_t;
struct in_addr
{
	in_addr_t s_addr;
};

所以通过上面的代码我们可以看到在操作系统中是用一个32位的整数来存储ip地址的,但是我们在类中却是使用string类型来存储,那为什么要这样呢?原因很简单采用点分十进制的字符串可读性特别的好符合我们人类的直觉,但是这种形式即需要转换又需要特别大的空间并且网络中的空间寸土寸金,所以在操作系统中就采用32位的无符号整数来进行存储,在用户层面中就是使用string类型来进行存储,既然两种类型完全不一样所以在赋值之前必须得做一些转换,那么这里的转换就不需要我们自己来转,操作系统中有对应的函数来实现,那么有了这些基础我们就可以继续完成初始化函数的实现,首先创建一个sockadd_in函数的对象,然后将其内容全部都初始化为0,那么这里我们可以使用bzero函数:
在这里插入图片描述
该函数就是将指定位置的值往后n个大小的空间全部都初始化为0,初始化完之后就将结构体内部的sin_family成员初始化为AF_INET,然后再将端口号填入到结构体的sin_port对象里面,这里大家要注意的一点就是在发送消息的时候也会将自己的端口号和ip地址发送过去,因为端口号的大小是两个字节所以在绑定的时候得将其从主机序列转换成为网络序列,那么这里就可以使用htons函数来进行转换:
在这里插入图片描述
然后再填写ip地址,因为ip地址在用户层和操作系统层存储的形式不一样所以这里就得做一些转换,那么这里就可以使用函数inet_addr来将其转换成为网络序列并且该函数在转换的时候还会对其大小端也进行转换,将主机端转化为网络端:
在这里插入图片描述
将内容填完之后便可以使用bind函数将该结构体和套接字绑定起来,并创建一个变量用来记录返回值以判断绑定是否成功,那么该函数完整的代码如下:

void initServer()//初始化函数
//初始化函数里面就创建对应的端口号,然后对端口号进行bind
{
    _sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(_sockfd==-1)
    {
        //运行到这里说明创建端口失败
        cout<<"socket error:"<< errno<<strerror(errno)<<endl;
        exit(SOCKET_ERR);
    }
    cout<<"socket success"<<" : "<<_sockfd<<endl;
    struct sockaddr_in local;
    bzero(&local,sizeof(sockaddr_in));
    local.sin_family=AF_INET;
    local.sin_port=htons(_port);
    local.sin_addr.s_addr=inet_addr(_ip.c_str());
    int res=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
    if(res==-1)
    {
        cout<<"bind error: "<<errno<< strerror(errno)<<endl;
        exit(BIND_ERR);
    }   
}

start函数(recvfrom)

服务器的本质就是死循环吧,所以start函数是一个死循环,在循环里面不停的接收别人发送过来的消息:

void start()//运行函数
{
   for(;;)
   {}
}

那么用来接收消息的函数就是recvfrom,该函数的参数如下:
在这里插入图片描述
第一个参数表示从哪个套接字中读取消息,第二个参数表示将读取的数据放到哪个缓冲区中,第三个参数就是一次性读取多少数据 ,第四个参数就表示以什么样的方式来进行读取这里我们就默认为0表示阻塞式的读取,因为在接收消息的时候我们得知道是谁将消息发送了过来,所以第五个参数是一个输出性参数该函数会将发送方的ip地址和端口号全部都填入第五个参数指向的结构体对象里面,第六个参数就表示传过来指针指向的对象的大小,该函数调用结束之后就会返回读取字符的个数,那么这就是该函数的使用形式用了这个函数我们就可以继续完善start函数,首先在for循环的外面创建一个缓冲区用来接收数据,在for循环里面首先创建一个sockaddr_in对象,然后调用recvfrom函数进行接收消息,接收完了之后就可以根据返回值进行判断如果返回值大于0我们就可以就读取的数据进行打印:

void start()//运行函数
{
    char buffer[gnum]={0};
    for(;;)
    {
        struct sockaddr_in peer; 
        socklen_t len = sizeof(peer); //必填
        ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
        if(s>0)
        {}
    }
}

打印数据的时候我们还要标注是那个ip地址和端口号发送过来的,所以在打印数据之前我们还得获取ip地址和端口号,那么这里获取就是从输出型参数sockaddr_in中进行获取,因为是从网络发到主机来的所以在获取端口号的时候得先进行转换所以这里得用到ntohs函数将大端数据转换成为主机端,因为网络中的ip地址和存储的形式不太一样所以在获取的时候得用到inet_ntoa函数来进行转换将网络形式转换成为客户形式并将大端数据转换成为主机端数据,那么该函数完整的代码如下:

void start()//运行函数
{
    char buffer[gnum]={0};
    for(;;)
    {
        struct sockaddr_in peer; 
        socklen_t len = sizeof(peer); //必填
        ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
        if(s>0)
        {
            buffer[s]=0;
            string clientip=inet_ntoa(peer.sin_addr);
            uint16_t clientport = ntohs(peer.sin_port);
            cout << clientip <<"[" << clientport << "]# " << buffer << endl; 
        }
    }
}

udpServer.cc

该文件装的就是main函数,在main函数里面就创建一个udpServer对象然后调用初始化函数和start函数执行任务,但是这里存在一个问题udpServer对象的构造函数需要传递ip地址和端口号那从哪来获取这两个东西呢?答案是在运行可执行程序的时候传递这两个参数就好比这样:./udpServer.cc ip地址 端口号,所以就得添加main函数的两个参数:

int main(int argc,char* argv[])
{
    unique_ptr<udpServer> usr();
    usr->initServer();
    usr->start();
    return 0;
}

但是这里存在一种情况就是使用者传递多了或者少了参数,那么这个时候就会出现问题,所以在执行之前我们得判断一下参数的个数,如果参数传递不对我们就执行一个函数用来告诉其正确的形式然后直接退出,然后将argv的第二个元素转化成为端口号,第三个元素赋值给一个string对象,那么这里的代码如下:

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << "local_ip local_port\n\n";
}

int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    string ip=argv[1];
    unique_ptr<udpServer> usr(new udpServer(port,ip));
    usr->initServer();
    usr->start();
    return 0;
}

那么这里我们可以单纯的运行一下不传递任何的ip地址和端口号:
在这里插入图片描述
可以看到这里直接退出并显示了正确的执行格式,然后我们可以使用ifconfig查看一下本地环回:
在这里插入图片描述
本地换回的地址就是127.0.0.1,我们网络通信的地址是分层,发送消息的时候从上到下分装的,发送到另外一台机器的时候又是从下到上进行解包分用的比如说下面的图片:
在这里插入图片描述
那么本地换回就是自己用来测试的,也就是在自己的主机上从上往下封装,然后再在自己的自己主机上从下往上进行解包分用:
在这里插入图片描述
所以本地换回就是用来进行测试的,先来自己的地址进行测试如果测试成功了再考虑其他机器的测试,那么这里我们就可以进行一下测试:

在这里插入图片描述
在这里插入图片描述
可以看到当前有个进程确实是在运行,然后使用指令netstat -naup便可以查看所有的网络进程:
在这里插入图片描述
可以到当前确实是有一个端口号为8081的进程并且该网络进程的名字就是udpServer,所以这就证明当前是可以正常的执行本地换回的,那么平时我们是使用的公网ip链接的服务器,那么这里也是否能够绑定公网ip呢?
在这里插入图片描述
答案是不可以的(有些还是可以具体情况以实践为主)因为云服务器是虚拟化的服务器·,不能直接bind你的公网ip,但是如果你有一个虚拟机或者是一个真实的linux环境的话则可以bing公网ip,虽然公网ip我们不能直接bind但是还是可以直接绑定内网ip的:
在这里插入图片描述
但是虽然局域网ip或者私有ip可以直接绑定但是依然可能会出现华为云的机器不能和阿里云腾讯云的用户进行通信,那我们该如何保证我们的机器能够被其他的机器找到呢?实际上,一款网络服务器是不建议指明一个IP的,一个服务器可能会有多个网卡所以也就可能存在多个IP的,如果只绑定了一个ip的话就可能会出现有很多的数据发到了不同的ip上这些数据都属于这台机器上的某个进程,但是却只有这一个指定的ip能够正常的收到,因为你只指明的绑定了那一个ip,所以为了让所有的机器都能够找到我们,我们在绑定ip地址的时候就是采用这样的方法:

 //local.sin_addr.s_addr=inet_addr(_ip.c_str());
 local.sin_addr.s_addr=INADDR_ANY;

而INADDR_ANY也就是一个宏他的真实定义就是一个宏:

#define INADDR_ANY ((in_addr_t)0x00000000)

也就是我们之前给的缺省参数,ip地址修改成这样之后表示的意思就是未来发给这个机器的所有数据只要是发给8080端口的就会将数据发给8080对应的进程,所以这里就不会出现发给了一个ip而漏掉其他ip的现象,这是任意地址bind也是服务器的真实写法。所以未来在执行服务端进程的时候就不需要传递ip地址直接传递端口就可以了:

#include"udpServer.hpp"
#include<memory>
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<udpServer> usr(new udpServer(port));
    usr->initServer();
    usr->start();
    return 0;
}

udpClient.hpp

在这个文件夹里面装的是客户端所要执行的操作,这里要进行网络通信所以也得有套接字,因为通信的时候得知道服务端的端口号和ip地址所以还得有两个变量来存储这些值,那么这里的代码如下:

class udpClient
{
  public:
  udpClient(){}
  void initClient(){}
  void run(){}
  ~udpClient(){}
  private:  
  int _sockfd;
  string _serverip;
  uint16_t _serverport;
};

构造函数

构造函数需要两个参数用来初始化_serverip和_serverport的值,这里的逻辑和客户端差不多这里就不多说,直接上代码:

udpClient(const string serverip,const uint16_t port)
:_serverip(serverip)
,_serverport(port)
,_sockfd(-1)
{}

初始化函数

初始化函数也是同样的道理首先创建套接字然后对返回值进行判断看是否创建成功?

void initClient()
{
    //创建套接字
    _sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(_sockfd==-1)
    {
        cerr<<"socket error: "<<errno<<strerror(errno)<<endl;
        exit(SOCKET_ERR);
    }
    cout << "socket success: " << " : " << _sockfd << endl;
}

那接下来要对套接字进行绑定吗?答案是必须要绑定的但是这里不需要我们人为的绑定,我们把端口值的选择权交给了操作系统,那这是为什么呢?为什么服务端要人为的绑定而客户端确实操作系统来绑定呢?原因是未来客户端服务器是要明确的端口号不能随意的改变,他得让所有人都能够知道,而服务端的端口号却不需要明确他可以随意的改变他只需要保证唯一性就可以了,并且写服务器的一定是一家公司,但是客户端却可能是无数家公司,比如说抖音,今日头条,西瓜视频都属于字节跳动的所以在这个公司内部肯定有明确的规定,但是客户端在用户的手机上面,一个手机上可能会有各种各样的软件所以而且这些软件来自各种各样的公司所以一旦客户端也指定了端口号很可能就会出现两个软件争抢一个端口号的现象,所以为了避免这样的显现就把客户端的端口号选着的权利交给操作系统让操作系统来自行分配,那么这里就存在一个问题:操作系统又是什么时候来进行分配呢?这个问题我们后面再进行解答,那么这就是初始化函数的全部内容。

run函数(sendto)

同样的道理run函数也得是一个死循环,在run函数里面我们就需要向客服端发送消息,那么这里就得使用sendto函数,该函数的参数如下:
在这里插入图片描述
第一个参数就是将数据放到哪个套接字上,第二个参数就是消息现在存放在哪里,第三个参数表示消息有多少,第四个参数表示当前的属性直接默认为0也就是阻塞式发送有数据就发没数据就不发,因为发送数据的时候我们得顺便告诉服务器是谁向你发送的数据,所以第五第六个参数就是用来填写目标主机的端口号和ip地址,这个跟前面的类似就不多说了,所以run函数的实现就是先创建一个sockaddr_in对象然后填写相应的信息,再创建一个string的对象和一个循环,在循环里面就往string对象里面填入信息然后将string对象的内容通过sendto函数发送到对应的主机里面,那么run函数的完整代码如下:

void run()
{
  struct sockaddr_in server;//用来记录服务端口的信息
  server.sin_family = AF_INET;
  server.sin_addr.s_addr = inet_addr(_serverip.c_str());
  server.sin_port = htons(_serverport);
  string tmp;
  while(1)
  {
    cout << "Please Enter# ";
    cin>>tmp;
    sendto(_sockfd,tmp.c_str(),tmp.size(),0,(struct sockaddr*)&server,sizeof(server));
  }
}

udpClient.cc

该文件的实现和udpServer.cc文件的实现相差不大,唯一的区别就是这里必须得指明你往哪个机器发送,那么这里就不多说了:

#include"udpClient.hpp"
#include<memory>
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << "local_ip local_port\n\n";
}
int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);
    unique_ptr<udpClient> ucil(new udpClient(serverip,serverport));
    ucil->initClient();
    ucil->run();
    return 0;
}

测试

下面就可以开始进行测试,首先运行客户端:
在这里插入图片描述
再运行服务端:

然后在服务端中输入信息就可以看到客户端中立马将信息显示了出来:
在这里插入图片描述
那么这就说明我们的代码实现的没有问题,那么完整的代码就如下,首先是文件udpServer.hpp

#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
static const string defaultIp="0.0.0.0";
static const int gnum =1024;
enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR};
class udpServer
{
public:
    udpServer(const uint16_t& port, const string & ip=defaultIp)//构造函数
    //构造函数就负责获取ip和端口号
    :_ip(ip)
    ,_port(port)
    {}
    void initServer()//初始化函数
    //初始化函数里面就创建对应的端口号,然后对端口号进行bind
    {
        _sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd==-1)
        {
            //运行到这里说明创建端口失败
            cout<<"socket error: "<< errno<<strerror(errno)<<endl;
            exit(SOCKET_ERR);
        }
        cout<<"socket success"<<" : "<<_sockfd<<endl;
        struct sockaddr_in local;
        bzero(&local,sizeof(sockaddr_in));
        local.sin_family=AF_INET;
        local.sin_port=htons(_port);
        //local.sin_addr.s_addr=inet_addr(_ip.c_str());
        local.sin_addr.s_addr=INADDR_ANY;
        int res=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(res==-1)
        {
            cout<<"bind error: "<<errno<< strerror(errno)<<endl;
            exit(BIND_ERR);
        }
        
    }
    void start()//运行函数
    {
        char buffer[gnum]={0};
        for(;;)
        {
            struct sockaddr_in peer; 
            socklen_t len = sizeof(peer); //必填
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
            if(s>0)
            {
                buffer[s]=0;
                string clientip=inet_ntoa(peer.sin_addr);
                uint16_t clientport = ntohs(peer.sin_port);
                cout << clientip <<"[" << clientport << "]# " << buffer << endl; 
            }
        }
    }
    ~udpServer()//析构函数
    {}

private:
    int _sockfd;
    string  _ip;
    uint16_t _port;   
};

udpServer.cc文件的内容如下:

#include"udpServer.hpp"
#include<memory>
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<udpServer> usr(new udpServer(port));
    usr->initServer();
    usr->start();
    return 0;
}

udpClient.hpp的内容如下:

#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR};
class udpClient
{
  public:
  udpClient(const string serverip,const uint16_t port)
  :_serverip(serverip)
  ,_serverport(port)
  ,_sockfd(-1)
  {}
  void initClient()
  {
      //创建套接字
      _sockfd=socket(AF_INET,SOCK_DGRAM,0);
      if(_sockfd==-1)
      {
          cerr<<"socket error: "<<errno<<strerror(errno)<<endl;
          exit(SOCKET_ERR);
      }
      //无需绑定
       cout << "socket success: " << " : " << _sockfd << endl;
  }
  void run()
  {
    struct sockaddr_in server;//用来记录服务端口的信息
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(_serverip.c_str());
    server.sin_port = htons(_serverport);
    string tmp;
    while(1)
    {
      cout << "Please Enter# ";
      cin>>tmp;
      sendto(_sockfd,tmp.c_str(),tmp.size(),0,(struct sockaddr*)&server,sizeof(server));
    }
  }
  ~udpClient()
  {}
  private:  
  int _sockfd;
  string _serverip;
  uint16_t _serverport;
};

udpClient.cc文件的内容如下:

#include"udpClient.hpp"
#include<memory>
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << "local_ip local_port\n\n";
}
int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);
    unique_ptr<udpClient> ucil(new udpClient(serverip,serverport));
    ucil->initClient();
    ucil->run();
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1297933.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Centos7、Mysql8.0 load_file函数返回为空的终极解决方法--暨selinux的深入理解

零、问题背景 最近想换房&#xff0c;为了方便自己对比感兴趣的房子&#xff0c;因此决定将目标房源的基本信息放在表里&#xff0c;特别是要一目了然的看到众多房子的各种图纸和照片&#xff0c;因此决定要在Mysql8.0.34数据库中以二进制形式保存图片&#xff08;抛开合理性和…

VSCode 报错 gopls was not able to find modules in your workspace.

由于在VSCode中打开一个项目时出现如下提示&#xff1a; 于是检索其解决办法 根据提示我们可以看到这是由于gopls所出的问题,在工作空间找不到相关module&#xff0c;这是VSCode中go插件所依赖附带的&#xff0c;该语言服务器提供诸如自动完成&#xff0c;转到定义&#xff0c;…

《python每天一小段》--12 数据可视化《1》

欢迎阅读《Python每天一小段》系列&#xff01;在本篇中&#xff0c;将使用Python Matplotlib实现数据可视化的简单图形。 一、概念 Matplotlib是一个流行的Python数据可视化库&#xff0c;它提供了丰富的绘图功能&#xff0c;可以创建各种类型的图表&#xff0c;包括折线图、…

echarts绘制一个环形图

其他echarts&#xff1a; echarts绘制一个柱状图&#xff0c;柱状折线图 echarts绘制一个饼图 echarts绘制一个环形图2 效果图&#xff1a; 代码&#xff1a; <template><div class"wrapper"><!-- 环形图 --><div ref"doughnutChart…

[面试题~k8s] 云原生必问基础篇

文章目录 概念篇1. k8s 是什么2. Container3. Pod4. Node5. Namespace6. Service7. Label8. Replica Set&#xff08;副本集)9. Deployment10. Volume11. Namespace12. PV 和 PVC 基础篇1. 删除 pod 流程2. k8s 有哪些组件master nodeworker node 3. k8s 和 docker 的关系4. 简…

mysql原理--InnoDB数据页结构

1.不同类型的页 页是 InnoDB 管理存储空间的基本单位&#xff0c;一个页的大小一般是 16KB 。InnoDB 为了不同的目的而设计了许多种不同类型的页。下面讨论存放我们表中记录的那种类型的页&#xff0c;官方称这种存放记录的页为索引&#xff08; INDEX &#xff09;页&#xff…

【开源】基于Vue+SpringBoot的河南软件客服系统

文末获取源码&#xff0c;项目编号&#xff1a; S 067 。 \color{red}{文末获取源码&#xff0c;项目编号&#xff1a;S067。} 文末获取源码&#xff0c;项目编号&#xff1a;S067。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统管理人员2.2 业务操作人员 三、…

【STM32】TIM定时器编码器

1 编码器接口简介 Encoder Interface 编码器接口 编码器接口可接收增量&#xff08;正交&#xff09;编码器的信号&#xff0c;根据编码器旋转产生的正交信号脉冲&#xff0c;自动控制CNT自增或自减&#xff0c;从而指示编码器的位置、旋转方向和旋转速度 接收正交信号&#…

视频剪辑技巧:批量合并视频,几个步骤轻松完成

在视频剪辑过程中&#xff0c;批量合并视频是一个常见的需求。通过合并多个视频文件&#xff0c;可以减少剪辑时间和提高工作效率。在开始合并视频之前&#xff0c;首先要明确需求和目的。例如&#xff0c;是想要将多个短视频片段随机合并成一个新的视频&#xff0c;还是想要将…

5分钟了解自动化测试,自动化优势、劣势、工具和框架选择全剖析

本文共有3963字&#xff0c;快速阅读需要大约5分钟&#xff0c;赏味期限持久。 随着软件测试技术的发展&#xff0c;人们已经从最初的纯粹的手工测试转变为手工与自动化测试技术相结合的测试方法。近年来&#xff0c;自动化测试越来越受到人们的重视&#xff0c;对于自动化测试…

mysql面试题——MVCC

一&#xff1a;什么是MVCC&#xff1f; 多版本并发控制&#xff0c;更好的方式去处理读-写冲突&#xff0c;就是为了查询一些正在被另一个事务更新的行&#xff0c;并且可以看到它们被更新之前的值&#xff0c;这样在做查询的时候就不用等待另一个事务释放锁。 二&#xff1a…

【投稿优惠|检索稳定】2024年信息系统、工程与数字化经济国际会议(ICISEDE 2024)

2024年信息系统、工程与数字化经济国际会议(ICISEDE 2024) 2024 International Conference on Information Systems and Engineering and the Digital Economy(ICISEDE 2024) [会议简介] 2024 年信息系统、工程与数字化经济国际会议(ICISEDE 2024)将于 2024 年 1 月 20 日在厦门…

研习代码 day52 | 单调栈问题——柱状图中最大的矩形

一、柱状图中最大的矩形 1.1 题目 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,2,3] 输出&#xff1a;…

TruLens RAG Triad 学习

TruLens RAG Triad 学习 0. 背景1. RAG 三元组2. TruLens 快速入门2-1. 安装依赖2-2. 初始化 OpenAI 认证信息2-3. 获取数据2-4. 创建向量存储2-5. 从头构建自定义 RAG2-6. 设置反馈函数2-7. 构建应用程序2-8. 运行应用程序0. 背景 近年来,RAG 架构已成为为大型语言模型 (LLM…

第21章总结 网络通信

21.1网络程序设计基础 21.1.1 局域网与互联网 为了实现两台计算机的通信&#xff0c;必须用一个网络线路连接两台计算机。如下图所示 21.1.2 网络协议 1.IP协议 IP是Internet Protocol的简称&#xff0c;是一种网络协议。Internet 网络采用的协议是TCP/IP协议&#x…

如何利用人工智能+物联网技术实现自动化设备生产

随着科技的发展与行业竞争的日益激烈&#xff0c;制造业也逐渐走向智能化发展。制造业的改革是利用物联网技术和自动化设备&#xff0c;实现生产线的智能化和自适应生产&#xff0c;优化生产流程&#xff0c;提高生产效率和质量&#xff0c;为企业创造更大的价值。 方案概述 智…

计算目标检测和语义分割的PR

需求描述 实际工作中&#xff0c;相比于mAP项目更加关心的是特定阈值下的precision和recall结果&#xff1b;由于本次的GT中除了目标框之外还存在多边形标注&#xff0c;为此&#xff0c;计算IoU的方式从框与框之间变成了mask之间&#xff1b; 本文的代码适用于MMDetection下的…

wireshark过滤包小技巧

1、过滤包含某个字符串的数据包&#xff1a; 或者&#xff1a; 2、过滤包含某一连续十六进制的数据包&#xff1a; 或者&#xff1a; 3、过滤精确到位数位置 或者&#xff1a;

1-Tornado的介绍

1 tornado的介绍 **Tornado**是一个用Python编写的可扩展的、无阻塞的**Web应用程序框架**和**Web服务器**。 它是由FriendFeed开发使用的&#xff1b;该公司于2009年被Facebook收购&#xff0c;而Tornado很快就开源了龙卷风以其高性能着称。它的设计允许处理大量并发连接&…

实时动作识别学习笔记

目录 yowo v2 yowof 判断是在干什么,不能获取细节信息 yowo v2 https://github.com/yjh0410/YOWOv2/blob/master/README_CN.md ModelClipmAPFPSweightYOWOv2-Nano1612.640ckptYOWOv2-Tiny