16.udp_socket
一.概念回顾
建议先学上篇博客,再向下学习,上篇博客的链接如下:
https://blog.csdn.net/weixin_60668256/article/details/154657323?fromshare=blogdetail&sharetype=blogdetail&sharerId=154657323&sharerefer=PC&sharesource=weixin_60668256&sharefrom=from_link
二.server封装
1.makefile实现
.PHONY:all
all:server_udp client_udpserver_udp:UdpServerMain.ccg++ -o $@ $^ -std=c++17
client_udp:UdpClientMain.ccg++ -o $@ $^ -std=c++17.PHONY:clean
clean:rm -f server_udp client_udp
2.UdpServer的InitServer()实现
a.创建套接字





返回值为一个文件描述符
![]()
void InitServer(){// 1.创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket: " << strerror(errno);Die(1);}LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;}
b.bind套接字







void InitServer(){// 1.创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket: " << strerror(errno);Die(1);}LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;// 2.填充网络信息,再进行bindstruct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family = AF_INET;local.sin_port = ::htons(_port); //要被发送给对方的,即要发送到网络中local.sin_addr.s_addr = ::inet_addr(_ip.c_str());//1.string ip -> 4bytes 2.newwork order// 2.1 bindint n = ::bind(_sockfd,CONV(&local),sizeof(local));}

bind的本质就是将我们对应的_sockfd设置进入内核中

这些往后都是套路
3.Start函数的实现







void Start(){_isrunning = true;while(true){char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = ::recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if(n > 0){//1.消息内容 && 2.谁给我发的inbuffer[n] = 0;LOG(LogLevel::DEBUG) << "client say@ " << inbuffer;std::string echo_string = "echo# ";echo_string += inbuffer;::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,CONV(&peer),sizeof(peer));}}}


三.client实现
对于我们的client,我们就不进行封装了,我们就直接进行对应的实现即可(有时间的可以尝试实现封装)
#include "UdpClient.hpp"
#include <iostream>
#include <cstring>
#include <string.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"// ./client_udp serverip serverport
int main(int argc,char* argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl;Die(USAGE_ERR);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);//1.创建socketint sockfd = ::socket(AF_INET,SOCK_DGRAM,0);if (sockfd < 0){std::cerr << "scoket error" << std::endl;Die(SOCKET_ERR);}//1.1填充server信息struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = ::htons(serverport); //要被发送给对方的,即要发送到网络中server.sin_addr.s_addr = ::inet_addr(serverip.c_str());//2.client donewhile(true){std::cout << "please Enter# " << std::endl;std::string message;std::getline(std::cin,message);// client 不需要进行bind吗? socket <->socket// client 必须要有自己的ip和端口!但是客户端,不需要自己显示调用bind!!// 而是,客户端首次sendto消息的时候,由OS自动进行bind// 1.如何理解client自动随机bind端口号? 一个端口号,由OS自动进行bind(客户端不需要被人找到)// 2.如何理解server需要显示的bind? 服务器端口号必须稳定,所以这里不能由OS随机bindint n = ::sendto(sockfd,message.c_str(),message.size(),0,CONV(&server),sizeof(server));(void)n;struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];n = ::recvfrom(sockfd,buffer,sizeof(buffer)-1,0,CONV(&temp),&len);if(n > 0){buffer[n] = 0;std::cout << buffer << std::endl;}}return 0;
}
![]()
所以这里的客户端由我们对应的OS自动进行随机bind(不用被别人知道)
四.测试

打印对应的发送消息的客户端的IP + 端口


客户端进行修改:
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <errno.h>
#include <strings.h>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "Common.hpp"
#include "Log.hpp"using namespace LogModule;const static int gsockfd = -1;
const static std::string gdefaultip = "127.0.0.1";//表示本地主机
const static uint16_t gdefaultport = 8080;class UdpServer
{
public:UdpServer(const std::string & ip = gdefaultip,uint16_t port = gdefaultport):_sockfd(gsockfd),_ip(ip),_port(port),_isrunning(false){}void InitServer(){// 1.创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket: " << strerror(errno);Die(SOCKET_ERR);}LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;// 2.填充网络信息,再进行bindstruct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family = AF_INET;local.sin_port = ::htons(_port); //要被发送给对方的,即要发送到网络中local.sin_addr.s_addr = ::inet_addr(_ip.c_str());//1.string ip -> 4bytes 2.newwork order// 2.1 bind :: 设置进入内核中int n = ::bind(_sockfd,CONV(&local),sizeof(local));if(n < 0){LOG(LogLevel::FATAL) << "bind: " << strerror(errno);Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success";}void Start(){_isrunning = true;while(true){char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = ::recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if(n > 0){//1.消息内容 && 2.谁给我发的uint16_t clientport = ::ntohs(peer.sin_port);std::string clientip = ::inet_ntoa(peer.sin_addr);inbuffer[n] = 0;std::string clientinfo = clientip + " : " + std::to_string(clientport) + " # " + inbuffer;LOG(LogLevel::DEBUG) << clientinfo;std::string echo_string = "echo# ";echo_string += inbuffer;::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,CONV(&peer),sizeof(peer));}}}~UdpServer(){}private:int _sockfd;uint16_t _port; //服务器的端口std::string _ip; //服务器的IPbool _isrunning; //服务器运行状态
};#endif


五.本地通信改成网络通信
"ServerMain.cc"#include "UdpServer.hpp"// ./server_udp localip localport
int main(int argc,char* argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << " localip localport" << std::endl;Die(USAGE_ERR);}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);ENABLE_CONSOLE_LOG();std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(ip,port);svr_uptr->InitServer();svr_uptr->Start();return 0;
}





所以,我们的服务器根本就不需要ip,只要一个port来标识唯一性就行了


"Server.hpp"#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <errno.h>
#include <strings.h>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "Common.hpp"
#include "Log.hpp"using namespace LogModule;const static int gsockfd = -1;
const static std::string gdefaultip = "127.0.0.1";//表示本地主机
const static uint16_t gdefaultport = 8080;class UdpServer
{
public:UdpServer(uint16_t port = gdefaultport):_sockfd(gsockfd),_port(port),_isrunning(false){}void InitServer(){// 1.创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket: " << strerror(errno);Die(SOCKET_ERR);}LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;// 2.填充网络信息,再进行bindstruct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family = AF_INET;local.sin_port = ::htons(_port); //要被发送给对方的,即要发送到网络中local.sin_addr.s_addr = INADDR_ANY;// 2.1 bind :: 设置进入内核中int n = ::bind(_sockfd,CONV(&local),sizeof(local));if(n < 0){LOG(LogLevel::FATAL) << "bind: " << strerror(errno);Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success";}void Start(){_isrunning = true;while(true){char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = ::recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if(n > 0){//1.消息内容 && 2.谁给我发的uint16_t clientport = ::ntohs(peer.sin_port);std::string clientip = ::inet_ntoa(peer.sin_addr);inbuffer[n] = 0;std::string clientinfo = clientip + " : " + std::to_string(clientport) + " # " + inbuffer;LOG(LogLevel::DEBUG) << clientinfo;std::string echo_string = "echo# ";echo_string += inbuffer;::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,CONV(&peer),sizeof(peer));}}}~UdpServer(){}private:int _sockfd;uint16_t _port; //服务器的端口bool _isrunning; //服务器运行状态
};#endif
"Main.cc"#include "UdpServer.hpp"// ./server_udp localport
int main(int argc,char* argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " localport" << std::endl;Die(USAGE_ERR);}uint16_t port = std::stoi(argv[1]);ENABLE_CONSOLE_LOG();std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);svr_uptr->InitServer();svr_uptr->Start();return 0;
}

六.InetAddr封装 以及 代码的修改
1.InetAddr的封装

后续我们的网络转主机,就使用这个
#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"class InetAddr
{
private:void PortNetToHost(){_port = ::ntohs(_net_addr.sin_port);}void IpNetToHost(){char ipbuffer[64];const char* ip = ::inet_ntop(AF_INET,&_net_addr.sin_addr,ipbuffer,sizeof(ipbuffer));_ip = ipbuffer;(void)ip;}
public:InetAddr(){}InetAddr(const struct sockaddr_in& addr):_net_addr(addr){PortNetToHost();IpNetToHost();}InetAddr(uint16_t port):_port(port){_net_addr.sin_family = AF_INET;_net_addr.sin_port = htons(_port);_net_addr.sin_addr.s_addr = INADDR_ANY;}struct sockaddr* NetAddr(){return CONV(&_net_addr);}socklen_t NetAddrLen(){return sizeof(_net_addr);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}
private:struct sockaddr_in _net_addr;std::string _ip;uint16_t _port;
};
2.代码整体修改
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <errno.h>
#include <strings.h>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"using namespace LogModule;const static int gsockfd = -1;
const static std::string gdefaultip = "127.0.0.1";//表示本地主机
const static uint16_t gdefaultport = 8080;class UdpServer
{
public:UdpServer(uint16_t port = gdefaultport):_sockfd(gsockfd),_addr(port),_isrunning(false){}void InitServer(){// 1.创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket: " << strerror(errno);Die(SOCKET_ERR);}LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;// 2.1 bind :: 设置进入内核中int n = ::bind(_sockfd,_addr.NetAddr(),_addr.NetAddrLen());if(n < 0){LOG(LogLevel::FATAL) << "bind: " << strerror(errno);Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success";}void Start(){_isrunning = true;while(true){char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = ::recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if(n > 0){//1.消息内容 && 2.谁给我发的InetAddr cli(peer);inbuffer[n] = 0;std::string clientinfo = cli.Ip() + " : " + std::to_string(cli.Port()) + " # " + inbuffer;LOG(LogLevel::DEBUG) << clientinfo;std::string echo_string = "echo# ";echo_string += inbuffer;::sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,CONV(&peer),sizeof(peer));}}_isrunning = false;}~UdpServer(){if(_sockfd > gsockfd){::close(gsockfd);}}private:int _sockfd;InetAddr _addr;bool _isrunning; //服务器运行状态
};#endif