目录
一、服务端UdpServer
1、udp_server.hpp
1、服务器的初始化
2、服务器的运行
2、udp_server.cc
二、客户端UdpClient
udp_client.cc
三、完整代码
一、服务端UdpServer
1、udp_server.hpp
首先,我们在该文件中,将服务器封装成一个类,而作为一款服务器,必须要有自己的端口号,同时网络服务器需要有对应的IP地址,文件描述符sock_:进行各种各样的数据通信,在类内进行读写操作。然后对外提供初始化和运行的接口。
1、服务器的初始化
我们最开始需要先将它进行初始化。初始化的第一步就是创建套接字,而创建套接字我们需要用到下面的函数。
socket:其作用就是创建套接字。
NAME socket - create an endpoint for communication SYNOPSIS #include /* See NOTES */ #include int socket(int domain, int type, int protocol);
参数说明:
~ domain:域,用来表明套接字是进行网络通信还是本地通信,主要使用下面这两种 :AF_UNIX(本地通信) AF_INET(网络通信)。
~ type:创建套接字时所需的服务类型。其中最常使用的是SOCK_STREAM和SOCK_DGRAM。如:UDP是数据报的网络通信形式,我们采用的就是SOCK_DGRAM(用户数据报服务),TCP是面向字节流式的网络通信,我们采用的就是SOCK_STREAM(叫做流式套接字,提供的是流式服务)。
~ protocol:创建套接字的协议类别。该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。
~ 返回值:套接字创建成功返回一个文件描述符,创建失败返回-1,同时错误码会被设置。
初始化的第二步就是绑定端口号和IP,我们需要用到bind函数
NAME bind - bind a name to a socket SYNOPSIS #include /* See NOTES */ #include int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
~ sockfd:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。
~ addr:网络相关的属性信息,包括协议家族、IP地址、端口号等。
~ addrlen:传入的addr结构体的长度。
~ 返回值:绑定成功返回0,绑定失败返回-1,同时错误码会被设置。
在绑定时需要将服务器网络相关的属性信息填充到结构体struct addr_in当中,其结构如下:
我们一般需要填充下面三个成员:
sin_family:填充AF_INET。
sin_port:表示服务器端口号,是一个16位的整数。需要注意主机序列和网络序列的转化。
sin_addr:表示服务器IP地址,是一个32位的整数。我们发现这个结构是一个结构体,所以一般是填充其中的成员:是一个32位的整数。
我们所看到的IP地址是点分十进制的,但是真正的IP地址是整数,所以我们在使用是需要将IP地址转换成系统能识别32位的整数,也需要注意主机序列和网络序列的转化,这些操作我们使用函数inet_addr可以一并实现。
#include #include #include in_addr_t inet_addr(const char *cp); char *inet_ntoa(struct in_addr in);
注:实际上,一款网络服务器不建议指明一个IP,也就是不要显示地绑定IP,因为一个服务器上可能会有多张网卡,所以IP可能不止一个,如果只绑定一个明确的IP,最终的数据可能用别的IP来访问端口号,这就无法访问,所以真实的服务器IP一般采用INADDR_ANY(全0,任意地址)代表任意地址bind 。
所以我们在填充IP地址时最好使用INADDR_ANY。
2、服务器的运行
首先,作为一款服务器,我们必须能够随时给用户提供服务,所以服务器是一个不能够退出的进程,需要使用死循环。然后不断接收从客户端发送过来的请求,进行处理,将结果返回给客户端。
我们一般使用recvform函数接收客户端发送过来的请求:
NAME recv, recvfrom, recvmsg - receive a message from a socket SYNOPSIS #include #include ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
参数说明:
sockfd:服务器绑定的套接字,buf:读取到特定缓冲区,len:缓冲区长度。
flags:读取的方式,默认为0,阻塞读取。
src_addr:收到除了消息本身,还得知道是谁发的,输入输出型参数,返回对应的消息内容是从哪一个客户端来的,len:src_addr大小。
返回值:返回-1表示失败,成功返回字节数
服务器收到消息,进行处理后,我们需要将结果发回给客户端,我们一般使用sendto函数:
NAME send, sendto, sendmsg - send a message on a socket SYNOPSIS #include #include ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
2、udp_server.cc
服务端进行调用的代码逻辑:构建udpServer的对象,然后进行初始化,在进行启动起来;调用逻辑如下:
因为运行后服务器会自动绑定所有的IP,所以我们只需要绑定端口号即可。
#include #include "udp_server.hpp" #include #include static void usage(const std::string &proc) { std::cout