当前位置: 首页 > news >正文

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

目录标题

  • 前言
  • 准备工作
  • 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;
}

相关文章:

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

目录标题 前言准备工作udpserver.hpp成员变量构造函数初始化函数(socket,bind)start函数(recvfrom) udpServer.ccudpClient.hpp构造函数初始化函数run函数(sendto) udpClient.cc测试 前言 在上一篇文章中我们初步的认识了端口号的作用&#xff0c;ip地址和MAC地址在网络通信时…...

Linux系统安装Docker-根据官方教程教程(以Ubuntu为例)

Linux系统安装Docker-根据官方教程教程&#xff08;以Ubuntu为例&#xff09; 1. 背景介绍2. 环境配置2.1 软件环境要求2.2 软件下载2.3 文档地址2.3 必备命令工具下载 3. 安装Docker3.1 使用root用户操作后续命令3.2 卸载可能存在的旧版本 4. 安装Docker4.1 更新依赖包4.2 配置…...

2023-12-03 LeetCode每日一题(可获得的最大点数)

2023-12-03每日一题 一、题目编号 1423. 可获得的最大点数二、题目链接 点击跳转到题目位置 三、题目描述 几张卡牌 排成一行&#xff0c;每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。 每次行动&#xff0c;你可以从行的开头或者末尾拿一张卡牌&#x…...

【唐山海德教育】安全员b证的考试科目

安全员b证考试内容包括对安全生产知识和管理能力考核&#xff0c;采用书面或计算机闭卷考试方式&#xff0c;内容包括安全生产法律法规、安全管理和安全技术等内容。其中&#xff0c;法律法规占50%&#xff0c;安全管理占40%&#xff0c;土建综合安全技术占6%&#xff0c;机械设…...

吉他初学者学习网站搭建系列(4)——如何查询和弦图

文章目录 背景实现ChordDbvexchords 背景 作为吉他初学者&#xff0c;如何根据和弦名快速查到和弦图是一个必不可少的功能。以往也许你会去翻和弦的书籍查询&#xff0c;像查新华字典那样&#xff0c;但是有了互联网后我们不必那样&#xff0c;只需要在网页上输入和弦名&#…...

九章量子计算机:探索量子世界的革命性工具

九章量子计算机:探索量子世界的革命性工具 一、引言 九章量子计算机的推出,是近年来科技界最为引人瞩目的成就之一。这款基于量子力学的计算机,以其独特的计算方式和潜在的应用前景,引发了全球范围内的关注和讨论。本文将深入探讨九章量子计算机的原理、技术特点、应用前景…...

在 Linux 上修改 Oracle 控制文件、日志文件和数据文件的目录的脚本

以下是一个交互式的 Bash 脚本示例&#xff0c;用于在 Linux 上修改 Oracle 数据库控制文件、日志文件和数据文件的目录。脚本会要求您输入要修改的路径&#xff0c;并根据输入的路径执行相应的修改操作。 #!/bin/bash# 修改以下变量以匹配您的 Oracle 数据库设置 ORACLE_SID&…...

JavaScript 延迟加载的艺术:按需加载的最佳实践

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…...

HTML之实体和标签

HTML之实体和标签 实体标签meta标签语义化标签列表超链接 实体 如果我们需要在网页中书写一些特殊符号&#xff0c;则需要在html中使用【实体】&#xff08;转义符&#xff09; 实体语法&#xff1a; &实体的名字; <!DOCTYPE html> <html lang"en"> …...

【小布_ORACLE笔记】Part11-1--RMAN Backups

Oracle的数据备份于恢复RMAN Backups 学习第11章需要掌握&#xff1a; 一.RMAN的备份类型 二.使用backup命令创建备份集 三.创建备份文件 四.备份归档日志文件 五.使用RMAN的copy命令创建镜像拷贝 文章目录 Oracle的数据备份于恢复RMAN Backups1.RMAN Backup Concepts&#x…...

卷积神经网络-3D医疗影像识别

文章目录 一、前言二、前期工作1. 介绍2. 加载和预处理数据 二、构建训练和验证集三、数据增强四、数据可视化五、构建3D卷积神经网络模型六、训练模型七、可视化模型性能八、对单次 CT 扫描进行预测 一、前言 我的环境&#xff1a; 语言环境&#xff1a;Python3.6.5编译器&a…...

C++基础 -33- 单目运算符重载

单目运算符重载格式 a和a通过形参确定 data1 operator() {this->a;return *this; }data1 operator(int) {data1 temp*this;this->a;return temp; }举例使用单目运算符重载 #include "iostream"using namespace std;class data1 {public :int a;data1(int…...

[传智杯 #3 初赛] 课程报名

题目描述 传智播客推出了一款课程&#xff0c;并进行了一次促销活动。具体来说就是&#xff0c;课程的初始定价为 v 元&#xff1b;每报名 m 个学员&#xff0c;课程的定价就要提升 a 元。由于课程能够容纳的学生有限&#xff0c;因此报名到 n 人的时候就停止报名。 现在老师…...

华为OD机试 - 悄悄话(Java JS Python C)

题目描述 给定一个二叉树,每个节点上站一个人,节点数字表示父节点到该节点传递悄悄话需要花费的时间。 初始时,根节点所在位置的人有一个悄悄话想要传递给其他人,求二叉树所有节点上的人都接收到悄悄话花费的时间。 输入描述 给定二叉树 0 9 20 -1 -1 15 7 -1 -1 -1 -1 …...

LeetCode - 965. 单值二叉树(C语言,二叉树,配图)

二叉树每个节点都具有相同的值&#xff0c;我们就可以比较每个树的根节点与左右两个孩子节点的值是否相同&#xff0c;如果不同返回false&#xff0c;否则&#xff0c;返回true。 如果是叶子节点&#xff0c;不存在还孩子节点&#xff0c;则这个叶子节点为根的树是单值二叉树。…...

每日一题(LeetCode)----哈希表--三数之和

每日一题(LeetCode)----哈希表–三数之和 1.题目&#xff08;15. 三数之和&#xff09; 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所…...

DL中的GPU使用问题

写在前面 在使用GPU进行深度学习训练经常会遇到下面几个问题&#xff0c;这里做一个解决方法的汇总。 &#x1f415;Q1&#x1f415;&#xff1a;在一个多卡服务器上&#xff0c;指定了cuda:1&#xff0c;但是0号显卡显存还是会被占用一定量的显存。 这个问题很经典的出现场景就…...

Linux命令——watch

watch是周期性的执行下个程序&#xff0c;并全屏显示执行结果 用法&#xff1a; vmfedora:~$ watch --helpUsage:watch [options] commandOptions:-b, --beep beep if command has a non-zero exit-c, --color interpret ANSI color and style sequen…...

力扣题:字符的统计-12.2

力扣题-12.2 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;423. 从英文中重建数字 解题思想&#xff1a;有的单词通过一个字母就可以确定&#xff0c;依次确定即可 class Solution(object):def originalDigits(self, s):""":typ…...

Python----Pandas

目录 Series属性 DataFrame的属性 Pandas的CSV文件 Pandas数据处理 Pandas的主要数据结构是Series&#xff08;一维数据&#xff09;与DataFrame&#xff08;二维数据&#xff09; Series属性 Series的属性如下&#xff1a; 属性描述pandas.Series(data,index,dtype,nam…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态

前言 在人工智能技术飞速发展的今天&#xff0c;深度学习与大模型技术已成为推动行业变革的核心驱动力&#xff0c;而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心&#xff0c;系统性地呈现了两部深度技术著作的精华&#xff1a;…...

Android写一个捕获全局异常的工具类

项目开发和实际运行过程中难免会遇到异常发生&#xff0c;系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler&#xff0c;它是Thread的子类&#xff08;就是package java.lang;里线程的Thread&#xff09;。本文将利用它将设备信息、报错信息以及错误的发生时间都…...

Android屏幕刷新率与FPS(Frames Per Second) 120hz

Android屏幕刷新率与FPS(Frames Per Second) 120hz 屏幕刷新率是屏幕每秒钟刷新显示内容的次数&#xff0c;单位是赫兹&#xff08;Hz&#xff09;。 60Hz 屏幕&#xff1a;每秒刷新 60 次&#xff0c;每次刷新间隔约 16.67ms 90Hz 屏幕&#xff1a;每秒刷新 90 次&#xff0c;…...

[10-1]I2C通信协议 江协科技学习笔记(17个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17...