目录
一、echo Server
1.1 Log.hpp
1.2 Mutex.hpp
1.3 UdpClient.cc
1.4 UdpServer.hpp
1.5 UdpServer.cc
二、dict server
2.1 Log.hpp
2.2 Mutex.hpp
2.3 Dict.hpp
2.4 InetAddr.hpp
2.5 UdpClient.cc
2.6 UdpServer.hpp
2.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
参数五:输出型参数,表示发送者的套接字信息
参数六:输出型参数,发送者的套接字结构体的大小。
返回值:表示收到的字节数,失败返回-1
4:发消息:
参数一:套接字fd
参数二:要发送的数据字符串地址
参数三:字符串的长度
参数四:设置为0,表示阻塞式IO
参数五:表示发送给谁
参数六:表示发送给谁
返回值:表示发送的字节数,失败返回-1
udp套接字,既可以读,又可以写,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_ptr<UdpServer> usvr = std::make_unique<UdpServer>(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_map<std::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::function<std::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_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string &word, InetAddr &client) -> std::string { return dict.Translate(word, client); }); usvr->Init(); usvr->Start(); return 0; }