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

目录标题

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

前言

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

准备工作

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

//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来创建一套网络通信的文件机制,也就相当于在底层创建了一个网卡文件,然后将该文件与网卡设备关联起来,我们来看看这个函数的参数:
网络入门---网络编程初步认识和实践_第3张图片
第一个参数是标记位表示当前你想进行的是网络通信还是本地通信,该参数的标记位有很多:
网络入门---网络编程初步认识和实践_第4张图片
但是实际上经常使用的就只有前两个:AF_UNIX(本地通信)和AF_INET(网络通信)也就是上一篇文章中的这张图片图片:
网络入门---网络编程初步认识和实践_第5张图片
第二个参数也是标记位表示当前套接字提供服务的类型也就是socket提供的能力类型,该标记位有下面这些:
网络入门---网络编程初步认识和实践_第6张图片
比如说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的类型来进行填写,该结构的构成如下:
网络入门---网络编程初步认识和实践_第7张图片
最上面表示你想要通信的类型然后就是你要绑定的端口号和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函数来进行转换:
网络入门---网络编程初步认识和实践_第8张图片
然后再填写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,该函数的参数如下:
网络入门---网络编程初步认识和实践_第9张图片
第一个参数表示从哪个套接字中读取消息,第二个参数表示将读取的数据放到哪个缓冲区中,第三个参数就是一次性读取多少数据 ,第四个参数就表示以什么样的方式来进行读取这里我们就默认为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地址和端口号:
网络入门---网络编程初步认识和实践_第10张图片
可以看到这里直接退出并显示了正确的执行格式,然后我们可以使用ifconfig查看一下本地环回:
网络入门---网络编程初步认识和实践_第11张图片
本地换回的地址就是127.0.0.1,我们网络通信的地址是分层,发送消息的时候从上到下分装的,发送到另外一台机器的时候又是从下到上进行解包分用的比如说下面的图片:
网络入门---网络编程初步认识和实践_第12张图片
那么本地换回就是自己用来测试的,也就是在自己的主机上从上往下封装,然后再在自己的自己主机上从下往上进行解包分用:
网络入门---网络编程初步认识和实践_第13张图片
所以本地换回就是用来进行测试的,先来自己的地址进行测试如果测试成功了再考虑其他机器的测试,那么这里我们就可以进行一下测试:

在这里插入图片描述
在这里插入图片描述
可以看到当前有个进程确实是在运行,然后使用指令netstat -naup便可以查看所有的网络进程:
网络入门---网络编程初步认识和实践_第14张图片
可以到当前确实是有一个端口号为8081的进程并且该网络进程的名字就是udpServer,所以这就证明当前是可以正常的执行本地换回的,那么平时我们是使用的公网ip链接的服务器,那么这里也是否能够绑定公网ip呢?
网络入门---网络编程初步认识和实践_第15张图片
答案是不可以的(有些还是可以具体情况以实践为主)因为云服务器是虚拟化的服务器·,不能直接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
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函数,该函数的参数如下:
网络入门---网络编程初步认识和实践_第16张图片
第一个参数就是将数据放到哪个套接字上,第二个参数就是消息现在存放在哪里,第三个参数表示消息有多少,第四个参数表示当前的属性直接默认为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
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;
}

测试

下面就可以开始进行测试,首先运行客户端:
网络入门---网络编程初步认识和实践_第17张图片
再运行服务端:
网络入门---网络编程初步认识和实践_第18张图片
然后在服务端中输入信息就可以看到客户端中立马将信息显示了出来:
在这里插入图片描述
那么这就说明我们的代码实现的没有问题,那么完整的代码就如下,首先是文件udpServer.hpp

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

你可能感兴趣的:(网络,网络,开发语言)