网络入门---网络编程初步认识和实践

目录标题

  • 前言
  • 准备工作
  • 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.mfbz.cn/a/211833.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HuggingFace学习笔记--Prompt-Tuning高效微调

1--Prompt-Tuning介绍 Prompt-Tuning 高效微调只会训练新增的Prompt的表示层&#xff0c;模型的其余参数全部固定&#xff1b; 新增的 Prompt 内容可以分为 Hard Prompt 和 Soft Prompt 两类&#xff1b; Soft prompt 通常指的是一种较为宽泛或模糊的提示&#xff0c;允许模型在…

规则引擎专题---3、Drools组成和入门

Drools概述 drools是一款由JBoss组织提供的基于Java语言开发的开源规则引擎&#xff0c;可以将复杂且多变的业务规则从硬编码中解放出来&#xff0c;以规则脚本的形式存放在文件或特定的存储介质中(例如存放在数据库中)&#xff0c;使得业务规则的变更不需要修改项目代码、重启…

初识RocketMQ

1、简介 RocketMQ 是阿里巴巴在 2012 年开源的消息队列产品&#xff0c;用 Java 语言实现&#xff0c;在设计时参考了 Kafka&#xff0c;并做出了自己的一些改进&#xff0c;后来捐赠给 Apache 软件基金会&#xff0c;2017 正式毕业&#xff0c;成为 Apache 的顶级项目。Rocket…

avue-crud中时间范围选择默认应该是0点却变成了12点

文章目录 一、问题二、解决三、最后 一、问题 在avue-crud中时间范围选择&#xff0c;正常默认应该是0点&#xff0c;但是不知道怎么的了&#xff0c;选完之后就是一直是12点。具体问题如下动图所示&#xff1a; <template><avue-crud :option"option" /&g…

YOLOv8改进 | 2023 | SCConv空间和通道重构卷积(精细化检测,又轻量又提点)

一、本文介绍 本文给大家带来的改进内容是SCConv&#xff0c;即空间和通道重构卷积&#xff0c;是一种发布于2023.9月份的一个新的改进机制。它的核心创新在于能够同时处理图像的空间&#xff08;形状、结构&#xff09;和通道&#xff08;色彩、深度&#xff09;信息&#xf…

计算机组成原理笔记——存储器(静态RAM和动态RAM的区别,动态RAM的刷新, ROM……)

■ 随机存取存储器 ■ 1.随机存取存储器&#xff1a;按存储信息的原理不同分为&#xff1a;静态RAM和动态RAM 2.静态RAM&#xff08;SRAM&#xff09;&#xff1a;用触发器工作原理存储信息&#xff0c;但电源掉电时&#xff0c;存储信息会丢失具有易失性。 3.存储器的基本单元…

代码随想录算法训练营第三十四天|62.不同路径,63. 不同路径 II

62. 不同路径 - 力扣&#xff08;LeetCode&#xff09; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#…

Kubernetes技术与架构-策略

Kubernetes集群提供系统支持的策略&#xff0c;也提供开放接口给第三方定义的策略&#xff0c;这些策略用于可定义的配置文件或者Kubernetes集群的运行时环境&#xff0c;其中包括进程ID数量的申请与限制策略&#xff0c;服务器节点Node内的进程ID的数量限制策略&#xff0c;Po…

PyQt6 QCheckBox复选框按钮控件

​锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计33条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话…

深入了解c语言中的结构体

介绍&#xff1a; 在C语言中&#xff0c;结构体是一种用户自定义的数据类型&#xff0c;它允许我们将不同类型的数据组合在一起&#xff0c;形成一个更为复杂的数据结构。结构体可以用来表示现实世界中的实体&#xff0c;如人员、学生、图书等。本篇博客将介绍结构体的基本概念…

iceoryx(冰羚)-进程间消息同步

iceoryx进程间消息同步 iceoryx进程间消息同步&#xff0c;是用socket或管道实现的,定义在iceoryx\iceoryx_posh\include\iceoryx_posh\internal\runtime\ipc_interface_base.hpp namespace platform { #if defined(_WIN32) using IoxIpcChannelType iox::posix::NamedPipe; …

Linux下为可执行文件添加图标

Ubuntu 18.04上使用Qt5.14.2创建一个简单的Qt Widgets项目test&#xff0c;添加2个Push Button按钮&#xff0c;点击分别获取github和csdn地址&#xff0c;在mainwindow.cpp中添加的代码如下: #include "mainwindow.h" #include "ui_mainwindow.h" #inclu…

Linux 互斥锁 读写锁 条件变量 信号量 (备查)

线程同步 1&#xff09;所谓的同步并不是多个线程同时对内存进行访问&#xff0c;而是按照先后顺序依次进行的。 2&#xff09;如没有对线程进行同步处理&#xff0c;会导致多个线程访问共享资源出现数据混乱的问题。 3&#xff09;所谓共享资源就是多个线程共同访问的变量&…

javaweb校车校园车辆管理系统springboot+jsp

结构设计&#xff1a;总体采用B/S结构设计模式 (1)用户登录模块&#xff1a;用户通过手动登录&#xff0c;检测是否是校内人员的车辆。 (2)用户车辆信息编辑、上传、模块&#xff1a;通过上传车辆入场信息的操作权限&#xff0c;以用户的名义发布资料上传至校园停车场系统中。…

可视化数据库管理客户端:Adminer

简介&#xff1a;Adminer&#xff08;前身为phpMinAdmin&#xff09;是一个用PHP编写的功能齐全的数据库管理工具。与phpMyAdmin相反&#xff0c;它由一个可以部署到目标服务器的文件组成。Adminer可用于MySQL、PostgreSQL、SQLite、MS SQL、Oracle、Firebird、SimpleDB、Elast…

Java+SSM+MySQL基于微信的在线协同办公小程序(附源码 调试 文档)

基于微信的在线协同办公小程序 一、引言二、系统设计三、技术架构四、管理员功能设计五、员工功能设计六、系统实现七、界面展示八、源码获取 一、引言 随着科技的飞速发展&#xff0c;移动互联网已经深入到我们生活的各个角落。在这个信息时代&#xff0c;微信作为全球最大的…

头歌JUnit单元测试相关实验入门

一、入门实验 1.1第一个Junit测试程序 任务描述 请学员写一个名为testSub()的测试函数&#xff0c;来测试给定的减法函数是否正确。 相关知识 Junit编写原则 1、简化测试的编写&#xff0c;这种简化包括测试框架的学习和实际测试单元的编写。 2、测试单元保持持久性。 3、利用…

【Python】Python给工作减负-读Excel文件生成xml文件

目录 ​前言 正文 1.Python基础学习 2.Python读取Excel表格 2.1安装xlrd模块 2.2使用介绍 2.2.1常用单元格中的数据类型 2.2.2 导入模块 2.2.3打开Excel文件读取数据 2.2.4常用函数 2.2.5代码测试 2.2.6 Python操作Excel官方网址 3.Python创建xml文件 3.1 xml语法…

计算机组成原理,硬件组成,存储器,控制器,控制器的任务, 运算器,中央处理器CPU,主存

计算机组成原理 课程需求 前导课程&#xff1a; 后继课程 汇编 操作系统 数逻 组成 系统结构 数电 微机原理 课程结构 计算机特性 1 从外部角度来看计算机的特性 快速 通用 准确 逻辑 2从外部特性与内部特性的关系 计算机组成 一 硬件组成 运算器 主要功能是进行算术…

强化学习(一)——基本概念及DQN

1 基本概念 智能体 agent &#xff0c;做动作的主体&#xff0c;&#xff08;大模型中的AI agent&#xff09; 环境 environment&#xff1a;与智能体交互的对象 状态 state &#xff1b;当前所处状态&#xff0c;如围棋棋局 动作 action&#xff1a;执行的动作&#xff0c;…
最新文章