目录一、echo Server1.1 Log.hpp1.2 Mutex.hpp1.3 UdpClient.cc1.4 UdpServer.hpp1.5 UdpServer.cc二、dict server2.1 Log.hpp2.2 Mutex.hpp2.3 Dict.hpp2.4 InetAddr.hpp2.5 UdpClient.cc2.6 UdpServer.hpp2.7 UdpServer.cc一、echo Server简单的回显服务器和客户端代码1打开网络文件的接口参数一表示是想做本地通信还是网络通信本地通信传入AF_UNIX网络通信传入AF_INET。参数二表明创建的套接字类型是什么。UDP通信使用SOCK_DGRAM。参数三代表要设定的协议类型第一个参数和第二个参数可以直接表明UDP了直接设置为0.返回值创建成功返回文件描述符失败返回-1.2:绑定socket的接口参数一网络文件的文件描述符参数二sockaddr的结构体参数三sockaddr的结构体大小返回值成功返回0失败返回-1。创建套接字只是打开了文件将来别人发信息的时候ip是多少端口号是多少别人并不清楚而且这个文件并没有套接字相关的信息。所以需要将打开的网络文件和网络套接字信息进行绑定也就是给这个网络文件设置ip地址设置端口号。这就是绑定的意义第二个参数类型包含在如下头文件中注意我们常见的IP地址是“192.168.0.123字符串形式的但是在网络通信的时候表示ip地址只需要使用4字节的整数就可以了。//在网络通信的时候服务器会不会将ip和端口号发送给client//ip和端口号一定会发送到网络//ip和端口号是本地存储格式所以需要将本地格式转换为网络序列3:收消息网络文件和套接字绑定之后我们就可以从网络中获取消息了系统调用如下参数一套接字fd参数二用户自定义的缓冲区用来存放收到的数据参数三缓冲区的长度参数四设置为0表示阻塞式IO参数五输出型参数表示发送者的套接字信息参数六输出型参数发送者的套接字结构体的大小。返回值表示收到的字节数失败返回-14发消息参数一套接字fd参数二要发送的数据字符串地址参数三字符串的长度参数四设置为0表示阻塞式IO参数五表示发送给谁参数六表示发送给谁返回值表示发送的字节数失败返回-1udp套接字既可以读又可以写UDP通信其实是全双工的。当客户端需要访问服务器的时候必须要指定服务器的ip地址和端口号但是客户端怎么知道服务器的ip地址和端口号呢客户端和服务器是一家公司写的客户端以一定的形式内置了服务器的ip地址和端口客户端创建网络文件之后需要和客户端的ip和port绑定嘛答案客户端是需要绑定的但是客户端不需要显示的调用bind来绑定首次发送消息时操作系统会自动给客户端进行bind操作系统知道客户端的ip地址端口号采用随机端口号的方式。但是为什么服务器需要显示的bind呢服务器会有很多的客户端来访问服务器所以服务器的端口和ip必须是众所周知且不能随便修改的。一旦修改了客户端可能就找不到服务器了。运行代码的时候需要指定服务器的ip地址和端口那么我们直接使用公网ip地址嘛答案是不可以我们可以使用本地环回地址127.0.0.1要求客户端和服务器必须在一台机器上表明我们是本地通信客户端发送的数据不会被推送到网络而是在操作系统内部转一圈直接交给对象的服务器。本地环回地址经常用来进行网络代码的测试。本地主机上除了有本地环回地址还有内网ip我们可以bind内网ip嘛如下所示但是我们的服务器一般不建议bind特定的ip一般都是绑定INADDR_ANY;也就是说我们再写服务器的时候要求bind的ip是INADDR_ANY。为什么要这样呢将来可能一台机器上配了两三个ip地址客户端这三个ip地址都可以访问客户端可以使用不同的ip来访问同样的服务同样的端口号我们的服务器就需要收到这三个ip地址的报文。但是如果服务器端bind了一个特定的ip那么就只能收到来自于这一个ip的报文了。我们不想让我们的服务只能收到特定ip的报文。凡是发送到服务器8080号端口的报文都应该给服务器。所以把服务器的ip地址设置为0表示它允许服务器可以接受来自8080端口的所有报文。因此在写服务器代码的时候可以不用设置ip。1.1 Log.hpp【Linux】28封装线程锁条件变量日志-CSDN博客1.2 Mutex.hpp【Linux】28封装线程锁条件变量日志-CSDN博客1.3 UdpClient.cc#include iostream #include cstring #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include string // ./updclient server_ip server_port int main(int argc, char *argv[]) { if (argc ! 3) { std::cerr Usage: argv[0] server_ip server_port std::endl; return 1; } std::string server_ip argv[1]; uint16_t server_port std::stoi(argv[2]); // 1创建套接字 int sockfd socket(AF_INET, SOCK_DGRAM, 0); if (sockfd 0) { std::cerr socket error std::endl; return 2; } // 2:本地的ip和端口是什么要不要和上面的“文件”关联呢 // 问题客户端要不要bind?需要bind。 // 客户端要不要显示的bind。首次发送消息操作系统会自动给客户端进行bind操作系统知道客户端的ip地址端口号采用随机端口号的方式。 // 为什么一个端口号只能和一个进程bind为了避免冲突 // 客户端的端口号是多少不重要只要是唯一的就可以了。 // 填写server ip信息 struct sockaddr_in server; memset(server, 0, sizeof(server)); server.sin_family AF_INET; server.sin_port htons(server_port); server.sin_addr.s_addr inet_addr(server_ip.c_str()); while (true) { std::string input; std::cout Please Enter:; std::getline(std::cin, input); int n sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)server, sizeof(server)); char buffer[1024]; struct sockaddr_in peer; socklen_t len sizeof(peer); int m recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)peer, len); if (m 0) { buffer[m] 0; std::cout buffer std::endl; } } return 0; }1.4 UdpServer.hpp#pragma once #include iostream #include string #include strings.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include Log.hpp using namespace LogModule; const int defaultfd -1; class UdpServer { public: UdpServer(uint16_t port) : _sockfd(defaultfd), //_ip(ip), _port(port), _isrunning(false) { } void Init() { // 1:创建套接字 _sockfd socket(AF_INET, SOCK_DGRAM, 0); if (_sockfd 0) { LOG(LogLevel::FATAL) socket error; exit(1); } LOG(LogLevel::INFO) socket success , sockfd: _sockfd; // 2:绑定socket信息主要是ip和端口. // 2.1填充socketaddr_in结构体 struct sockaddr_in local; bzero(local, sizeof(local)); local.sin_family AF_INET; // 在网络通信的时候服务器会不会将ip和端口号发送给client // ip和端口号一定会发送到网络 // ip和端口号是本地存储格式所以需要将本地格式转换为网络序列 local.sin_port htons(_port); // 1:ip转成4字节 2:4字节转成网络序列 -in_addr_t inet_addr(const char *cp); // local.sin_addr.s_addr inet_addr(_ip.c_str()); local.sin_addr.s_addr INADDR_ANY; int n bind(_sockfd, (struct sockaddr *)local, sizeof(local)); if (n 0) { LOG(LogLevel::FATAL) bind error; exit(2); } LOG(LogLevel::INFO) bind success , sockfd: _sockfd; } void Start() { _isrunning true; while (_isrunning) { char buffer[1024]; struct sockaddr_in peer; socklen_t len sizeof(peer); // 1收消息 ssize_t s recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)peer, len); if (s 0) { int peer_port ntohs(peer.sin_port); std::string peer_ip inet_ntoa(peer.sin_addr); buffer[s] 0; LOG(LogLevel::DEBUG) [ peer_ip : peer_port ] buffer: buffer; // 2发消息 std::string echo_string server ehco; echo_string buffer; sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)peer, len); } } } ~UdpServer() {} private: int _sockfd; uint16_t _port; // std::string _ip; // 用的是字符串风格点分十进制192.168.1.1 bool _isrunning; };1.5 UdpServer.cc#include iostream #include memory #include UdpServer.hpp // ./udpserver ip port int main(int argc, char *argv[]) { if (argc ! 2) { std::cerr Usage: argv[0] port std::endl; return 1; } // std::string ip argv[1]; uint16_t port std::stoi(argv[1]); Enable_Console_Log_Strategy(); std::unique_ptrUdpServer usvr std::make_uniqueUdpServer(port); usvr-Init(); usvr-Start(); return 0; }二、dict server为什么客户端会给服务器发消息啊不就是让服务器处理我们发送的数据嘛。udpserver是为了进行网络通信的我们可以在updserver增加一个回调函数来处理数据。2.1 Log.hpp【Linux】28封装线程锁条件变量日志-CSDN博客2.2 Mutex.hpp【Linux】28封装线程锁条件变量日志-CSDN博客2.3 Dict.hpp#pragma once #include iostream #include fstream #include string #include unordered_map #include Log.hpp #include InetAddr.hpp using namespace LogModule; const std::string default_dict_path ./dict.txt; const std::string sep : ; class Dict { public: Dict(const std::string path default_dict_path) : _dict_path(path) { } bool LoadDict() { std::ifstream in(_dict_path); if (!in.is_open()) { LOG(LogLevel::DEBUG) 打开字典 _dict_path 错误; return false; } std::string line; while (std::getline(in, line)) { // apple: 苹果 auto pos line.find(sep); if (pos std::string::npos) { LOG(LogLevel::WARNING) 解析 line 失败; continue; } std::string En line.substr(0, pos); std::string Ch line.substr(pos sep.size()); if (En.empty() || Ch.empty()) { LOG(LogLevel::WARNING) 没有有效内容 line; continue; } _dict.insert({En, Ch}); LOG(LogLevel::INFO) 加载 line; } in.close(); return true; } std::string Translate(const std::string word, InetAddr client) { auto iter _dict.find(word); if (iter _dict.end()) { LOG(LogLevel::DEBUG) 进入了翻译模块 client.Ip() : client.Port() [# word - None; return None; } LOG(LogLevel::DEBUG) 进入了翻译模块 client.Ip() : client.Port() [# word - iter-second; return iter-second; } ~Dict() { } private: std::string _dict_path; // 路径文件名 std::unordered_mapstd::string, std::string _dict; };2.4 InetAddr.hpp#pragma once #include iostream #include string #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h // 网络地址和主机地址转换的类 class InetAddr { public: InetAddr(struct sockaddr_in addr) : _addr(addr) { _port ntohs(addr.sin_port); _ip inet_ntoa(addr.sin_addr); } uint16_t Port() { return _port; } std::string Ip() { return _ip; } ~InetAddr() {} private: struct sockaddr_in _addr; std::string _ip; uint16_t _port; };2.5 UdpClient.cc#include iostream #include cstring #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include string // ./updclient server_ip server_port int main(int argc, char *argv[]) { if (argc ! 3) { std::cerr Usage: argv[0] server_ip server_port std::endl; return 1; } std::string server_ip argv[1]; uint16_t server_port std::stoi(argv[2]); // 1创建套接字 int sockfd socket(AF_INET, SOCK_DGRAM, 0); if (sockfd 0) { std::cerr socket error std::endl; return 2; } // 2:本地的ip和端口是什么要不要和上面的“文件”关联呢 // 问题客户端要不要bind?需要bind。 // 客户端要不要显示的bind。首次发送消息操作系统会自动给客户端进行bind操作系统知道客户端的ip地址端口号采用随机端口号的方式。 // 为什么一个端口号只能和一个进程bind为了避免冲突 // 客户端的端口号是多少不重要只要是唯一的就可以了。 // 填写server ip信息 struct sockaddr_in server; memset(server, 0, sizeof(server)); server.sin_family AF_INET; server.sin_port htons(server_port); server.sin_addr.s_addr inet_addr(server_ip.c_str()); while (true) { std::string input; std::cout Please Enter:; std::getline(std::cin, input); int n sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)server, sizeof(server)); char buffer[1024]; struct sockaddr_in peer; socklen_t len sizeof(peer); int m recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)peer, len); if (m 0) { buffer[m] 0; std::cout buffer std::endl; } } return 0; }2.6 UdpServer.hpp#pragma once #include iostream #include string #include strings.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include functional #include Log.hpp #include InetAddr.hpp using func_t std::functionstd::string(const std::string , InetAddr ); using namespace LogModule; const int defaultfd -1; class UdpServer { public: UdpServer(uint16_t port, func_t func) : _sockfd(defaultfd), //_ip(ip), _port(port), _isrunning(false), _func(func) { } void Init() { // 1:创建套接字 _sockfd socket(AF_INET, SOCK_DGRAM, 0); if (_sockfd 0) { LOG(LogLevel::FATAL) socket error; exit(1); } LOG(LogLevel::INFO) socket success , sockfd: _sockfd; // 2:绑定socket信息主要是ip和端口. // 2.1填充socketaddr_in结构体 struct sockaddr_in local; bzero(local, sizeof(local)); local.sin_family AF_INET; // 在网络通信的时候服务器会不会将ip和端口号发送给client // ip和端口号一定会发送到网络 // ip和端口号是本地存储格式所以需要将本地格式转换为网络序列 local.sin_port htons(_port); // 1:ip转成4字节 2:4字节转成网络序列 -in_addr_t inet_addr(const char *cp); // local.sin_addr.s_addr inet_addr(_ip.c_str()); local.sin_addr.s_addr INADDR_ANY; int n bind(_sockfd, (struct sockaddr *)local, sizeof(local)); if (n 0) { LOG(LogLevel::FATAL) bind error; exit(2); } LOG(LogLevel::INFO) bind success , sockfd: _sockfd; } void Start() { _isrunning true; while (_isrunning) { char buffer[1024]; struct sockaddr_in peer; socklen_t len sizeof(peer); // 1收消息 ssize_t s recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)peer, len); if (s 0) { InetAddr client(peer); buffer[s] 0; // 收到的消息当作英文单词然后返回对应的中文 std::string result _func(buffer, client); // 2发消息 sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)peer, len); } } } ~UdpServer() {} private: int _sockfd; uint16_t _port; // std::string _ip; // 用的是字符串风格点分十进制192.168.1.1 bool _isrunning; func_t _func; // 服务器的回调函数用来进行对数据进行处理 };2.7 UdpServer.cc#include iostream #include memory #include UdpServer.hpp //网络通信的功能 #include Dict.hpp //翻译的功能 // ./udpserver ip port int main(int argc, char *argv[]) { if (argc ! 2) { std::cerr Usage: argv[0] port std::endl; return 1; } uint16_t port std::stoi(argv[1]); Enable_Console_Log_Strategy(); // 1:字典对象提供翻译功能 Dict dict; dict.LoadDict(); // 2:网络服务器对象提供通信功能 std::unique_ptrUdpServer usvr std::make_uniqueUdpServer(port, [dict](const std::string word, InetAddr client) - std::string { return dict.Translate(word, client); }); usvr-Init(); usvr-Start(); return 0; }