Linux网络编程之UDP
文章目录
- Linux网络编程之UDP
- 1、端口号
- 2、端口号和进程ID的区别
- 3、重新认识网络通讯过程
- 4、UDP协议的简单认识
- 5、网络字节序
- 6、socket编程接口
- 6.1、socket常见接口
- 6.2、sockaddr通用地址结构
- 7、简单的UDP网络程序
- 7.1、服务器响应程序
- 7.2、服务器执行命令行
- 7.3、服务器英语单词字典
- 7.4、服务器聊天室
- 8、补充内容
Linux网络编程之UDP
1、端口号
端口号是计算机网络中用来标识特定应用程序或服务的数字标识符。在网络通信中,每个数据包都包含一个目标端口号和源端口号,这样可以确保数据包能够正确地路由到目标应用程序或服务。
端口号是一个16位的数字,范围从0到65535。
用于在传输层(通常是TCP或UDP协议)标识特定的应用程序或服务。
允许同一台计算机上的多个应用程序同时进行网络通信,每个应用程序使用不同的端口号。
在目标设备上,操作系统通过端口号将数据包传递给正确的应用程序或服务。
IP地址 + 端口号 = 套接字,能够标识网络上的某一台主机的某一个进程。
端口号使得计算机网络中的不同应用程序能够通过网络进行可靠的通信和数据交换,同时确保数据能够安全地到达目标应用程序。
一个端口号只能被一个进程占用。
2、端口号和进程ID的区别
用途不同:端口号用于标识网络中的应用程序或服务,而进程ID用于操作系统内部管理和标识正在运行的进程。
作用范围:端口号主要在网络通信中使用,进程ID主要在操作系统内部使用。
分配方式:端口号是在应用程序设计或网络配置时指定的,而进程ID是由操作系统动态分配给每个新创建的进程。
如果端口号和进程ID合并,那么系统的耦合度会增加,不符合低耦合的编程思想。
另外,一个进程可以有多个端口号,但是一个端口号不能被多个进程使用。
3、重新认识网络通讯过程
我们上网,无非就是两种动作:a. 把远端数据拉取到本地 b. 把数据发送到远端。
大部分的网络通信行为,都是用户触发的,在计算机中,用户就是进程!
把数据发送到目标主机,不是目的,而是手段。真正的目的,是把数据交给目的主机上的某个服务(进程)。
网络通信的本质,其实就是进程在帮我们进行网络通信(进程间通信),无论是对于客户端还是服务器。
IP地址 + 端口号 = 套接字,能够标识网络上的某一台主机的某一个进程。
client --> server : client进程 – > server进程
client ip + client port = client进程
server ip + server port = server进程
在网络中都能唯一找到彼此
4、UDP协议的简单认识
传输层协议
无连接
不可靠传输
面向数据报
5、网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。 那么如何定义网络数据流的地址呢?
答:网络数据流的地址按大端来定义的。
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据
如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:
#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);助记:h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回
如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回
IP是32位,端口号是16位,因此IP转网络字节序是使用带l的,端口号是使用带s的
6、socket编程接口
6.1、socket常见接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol);// 绑定端口号 (TCP/UDP, 服务器) int bind(int socket, const struct sockaddr *address, socklen_t address_len);// 开始监听socket (TCP, 服务器) int listen(int socket, int backlog);// 接收请求 (TCP, 服务器) int accept(int socket, struct sockaddr* address, socklen_t* address_len);// 建立连接 (TCP, 客户端) int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);我们可以看到,上述接口中基本都使用到了sockaddr结构体,下面我们想象解释一下。
6.2、sockaddr通用地址结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket,然而。各种网络协议的地址格式并不相同。sockaddr通用地址结构就是为了统一接口,在不同的操作系统下也可以正常使用(强转)。
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址.
IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
socket API可以都用struct sockaddr *类型表示,在使用的时候需要强制转化成sockaddr_in;这样的好处是程序的通用性,可以接收IPv4,IPv6,以及UNIX Domain Socket各种类型的sockaddr结构体指针作为参数。
sockaddr通用地址结构:
/* Structure describing a generic socket address. */ struct sockaddr{__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */char sa_data[14]; /* Address data. */};sockaddr_in结构:
/* Structure describing an Internet socket address. */ struct sockaddr_in{__SOCKADDR_COMMON (sin_); // 地址类型in_port_t sin_port; /* Port number. */struct in_addr sin_addr; /* Internet address. *//* Pad to size of `struct sockaddr'. */unsigned char sin_zero[sizeof (struct sockaddr) // 8字节填充- __SOCKADDR_COMMON_SIZE- sizeof (in_port_t)- sizeof (struct in_addr)];};虽然socket api的接口是sockaddr,但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in;这个结构里主要有三部分信息:地址类型,端口号,IP地址。
in_addr结构:
/* Internet address. */ typedef uint32_t in_addr_t; struct in_addr{in_addr_t s_addr;};in_addr用来表示一个IPv4的IP地址。其实就是一个32位的无符号整数。
7、简单的UDP网络程序
7.1、服务器响应程序
主要功能是客户端给服务器发送消息,服务器收到消息进行响应(把收到的消息发送给客户端)。
用到的几个接口:接收和发送网络数据:
#include <sys/types.h> #include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);参数:buf -- 输出型参数,即存接收到的数据len -- 期望收到的数据长度flag -- 默认为0src_addr -- 输出型参数,存发送数据的对象addrlen -- 输入输出型参数,存src_addr的大小ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);参数:buf -- 输入型参数,即要存发送的数据len -- 发送数据的长度flag -- 默认为0src_addr -- 输出型参数,存发送数据的对象addrlen -- 输入输出型参数,存src_addr的大小字节序列转换:
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>// 将字符串类型的IP转换为无符号整数类型IP并转换为网络字节序列 in_addr_t inet_addr(const char *cp); // 从网络字节序列中将无符号整数类型IP转换为字符串类型的IP char *inet_ntoa(struct in_addr in);
接下来就是代码文件了。
InetAddr.hpp文件#pragma once#include <iostream> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string>class InetAddr {void GetAddress(std::string *ip, uint16_t *port){// char *inet_ntoa(struct in_addr in);*ip = inet_ntoa(_addr.sin_addr);*port = ntohs(_addr.sin_port);}public:InetAddr(const struct sockaddr_in &addr) : _addr(addr){GetAddress(&_ip, &_port);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr() {}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port; };
LockGuard.hpp文件#include <pthread.h>class LockGuard { public:LockGuard(pthread_mutex_t *mutex) : _mutex(mutex){pthread_mutex_lock(_mutex); // 构造加锁}~LockGuard(){pthread_mutex_unlock(_mutex); // 析构解锁}private:pthread_mutex_t *_mutex; };
Log.hpp文件#pragma once#include <string> #include <iostream> #include <fstream> #include <unistd.h> #include <stdarg.h> #include <sys/types.h> #include "LockGuard.hpp"bool isSave = false; // 默认向显示器打印 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; #define FILEPATH "./log.txt"enum level {DEBUG = 0,INFO,WARNING,ERROR,FATAL };void SaveToFile(const string &message) {ofstream out(FILEPATH, ios_base::app);if (!out.is_open())return;out << message;out.close(); }std::string LevelToString(int level) {switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknow";} }std::string GetTimeString() {time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None";char buff[1024];snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900,format_time->tm_mon + 1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return buff; }void LogMessage(const std::string filename, int line, bool issave, int level, const char *format, ...) {std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t pid = getpid();char buff[1024];va_list arg;// int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 使用可变参数va_start(arg, format);vsnprintf(buff, sizeof(buff), format, arg);va_end(arg);LockGuard lock(&mutex);std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(pid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buff + '\n';if (issave == false)std::cout << message;elseSaveToFile(message); }// 固定文件名和行数 #define LOG(level, format, ...) \do \{ \LogMessage(__FILE__, __LINE__, isSave, level, format, ##__VA_ARGS__); \} while (0)#define EnableScreen() \do \{ \isSave = false; \} while (0)#define EnableFile() \do \{ \isSave = true; \} while (0)void Test(int num, ...) {va_list arg;va_start(arg, num);while (num--){int data = va_arg(arg, int);std::cout << data << " ";}std::cout << std::endl;va_end(arg); }
Main.cc文件#include <iostream> #include <memory> #include "UdpServer.hpp"void Usage() {// printf("./udp_server serverip serverport\n");printf("Usage : ./udp_server serverport\n"); // ip 已经设置为0 }int main(int argc, char *argv[]) {// if (argc != 3)if (argc != 2){Usage();exit(USAGE_ERROR);}// std::string ip = argv[1];// uint16_t port = std::stoi(argv[2]);uint16_t port = std::stoi(argv[1]);// std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(ip, port);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);usvr->InitServer();usvr->Start();return 0; }
UdpClient.cc文件#include <iostream> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h>enum errorcode {USAGE_ERROR = 1, };void Usage() {printf("Usage : ./udp_client serverip serverport\n"); }int main(int argc, char *argv[]) {if (argc != 3){Usage();exit(USAGE_ERROR);}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 << "socket error" << std::endl;}// 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!// a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!// b. 什么时候bind呢?首次发送数据的时候struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport); // 主机序列转网络序列 -- 网络序列是大端// a.字符串序列点分十进制IP地址 转换为 4字节IP// b.主机序列转网络序列// in_addr_t inet_addr(const char *cp);server.sin_addr.s_addr = inet_addr(serverip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addrstd::string message;// 直接通信即可,已经自动绑定while (true){std::cout << "Please Enter:# ";std::getline(std::cin, message);// 发送数据// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addrsendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));// 获取回应数据char buff[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addrssize_t n = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);if (n > 0){buff[n] = 0;std::cout << "Server Echo:# " << buff << std::endl;}} }
UdpServer.cc文件#pragma once#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h>#include <string.h> #include <error.h>using namespace std;#include "Log.hpp" #include "InetAddr.hpp"const int Defaultfd = -1;enum errorcode {CREATE_ERROR = 1,BIND_ERROR,USAGE_ERROR };class UdpServer { public:// UdpServer(std::string ip, uint16_t port) : _sockfd(Defaultfd), _ip(ip), _port(port), _isrunning(false)UdpServer(uint16_t port) : _sockfd(Defaultfd), _port(port), _isrunning(false){}void InitServer(){// 1. 创建udp套接字 -- 必须要做的_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // udp通信if (_sockfd < 0){LOG(FATAL, "socke create error ! error string %s error %d", strerror(errno), errno);exit(CREATE_ERROR);}// 创建socket成功LOG(INFO, "create socket success ! sockfd:", _sockfd);// 2.0. 填充sockaddr_in结构struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port); // 主机序列转网络序列 -- 网络序列是大端// a.字符串序列点分十进制IP地址 转换为 4字节IP// b.主机序列转网络序列// in_addr_t inet_addr(const char *cp);// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addrlocal.sin_addr.s_addr = INADDR_ANY; // 0 -- 链接当前服务器的所有ip都接受// 2.1. 绑定网络信息int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "socke bind error ! error string %s error %d", strerror(errno), errno);exit(BIND_ERROR);}// 绑定成功LOG(INFO, "bind socket success !");}void Start(){// 一直运行,直到管理者不想运行了, 服务器都是死循环// UDP是面向数据报的协议_isrunning = true;while (true){// 获取数据// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addrstruct sockaddr_in peer;socklen_t len = sizeof(peer); // 既是输入(必须初始化) ,也是输出(peer的实际长度)char buff[1024];ssize_t n = recvfrom(_sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);if (n > 0){buff[n] = 0;// 回答发送方// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addrInetAddr addr(peer);LOG(DEBUG, "get message , sender:[%s:%d] , content: %s", addr.Ip().c_str(), addr.Port(), buff);sendto(_sockfd, buff, strlen(buff), 0, (struct sockaddr *)&peer, len);}}_isrunning = false;}~UdpServer(){}private:int _sockfd;// std::string _ip; // 暂时先这样写 -- 不需要uint16_t _port;bool _isrunning; };
Makefile文件.PHONY:all all:udp_client udp_serverudp_client:UdpClient.ccg++ -o $@ $^ -std=c++14 udp_server:Main.ccg++ -o $@ $^ -std=c++14.PHONY:clean clean:rm -f udp_server udp_client运行结果:
7.2、服务器执行命令行
主要是客户端发送命令给服务器,服务器执行命令后返回给客户端。
InetAddr.hpp文件#pragma once#include <iostream> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string>class InetAddr {void GetAddress(std::string *ip, uint16_t *port){// char *inet_ntoa(struct in_addr in);*ip = inet_ntoa(_addr.sin_addr);*port = ntohs(_addr.sin_port);}public:InetAddr(const struct sockaddr_in &addr) : _addr(addr){GetAddress(&_ip, &_port);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr() {}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port; };
LockGuard.hpp文件#include <pthread.h>class LockGuard { public:LockGuard(pthread_mutex_t *mutex) : _mutex(mutex){pthread_mutex_lock(_mutex); // 构造加锁}~LockGuard(){pthread_mutex_unlock(_mutex); // 析构解锁}private:pthread_mutex_t *_mutex; };
Log.hpp文件#pragma once#include <string> #include <iostream> #include <fstream> #include <unistd.h> #include <stdarg.h> #include <sys/types.h> #include "LockGuard.hpp"bool isSave = false; // 默认向显示器打印 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; #define FILEPATH "./log.txt"enum level {DEBUG = 0,INFO,WARNING,ERROR,FATAL };void SaveToFile(const string &message) {ofstream out(FILEPATH, ios_base::app);if (!out.is_open())return;out << message;out.close(); }std::string LevelToString(int level) {switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknow";} }std::string GetTimeString() {time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None";char buff[1024];snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900,format_time->tm_mon + 1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return buff; }void LogMessage(const std::string filename, int line, bool issave, int level, const char *format, ...) {std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t pid = getpid();char buff[1024];va_list arg;// int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 使用可变参数va_start(arg, format);vsnprintf(buff, sizeof(buff), format, arg);va_end(arg);LockGuard lock(&mutex);std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(pid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buff + '\n';if (issave == false)std::cout << message;elseSaveToFile(message); }// 固定文件名和行数 #define LOG(level, format, ...) \do \{ \LogMessage(__FILE__, __LINE__, isSave, level, format, ##__VA_ARGS__); \} while (0)#define EnableScreen() \do \{ \isSave = false; \} while (0)#define EnableFile() \do \{ \isSave = true; \} while (0)void Test(int num, ...) {va_list arg;va_start(arg, num);while (num--){int data = va_arg(arg, int);std::cout << data << " ";}std::cout << std::endl;va_end(arg); }
Main.cc文件#include <iostream> #include <memory> #include <stdio.h> #include <vector> #include "UdpServer.hpp"void Usage() {// printf("./udp_server serverip serverport\n");printf("Usage : ./udp_server serverport\n"); // ip 已经设置为0 }std::string OnMessage(std::string request) {return request + " got you!"; }bool CheckCommand(std::string command) {vector<string> cmd = {"kill","rm","dd","top","reboot","shutdown","mv","cp","halt","unlink","exit","chmod"};for (auto &e : cmd){if (command.find(e) != std::string::npos)return false;}return true; }std::string OnCommand(std::string command) {if (!CheckCommand(command)){return "bad man!";}// FILE *popen(const char *command, const char *type);FILE *pp = popen(command.c_str(), "r");if (!pp){return "popen error!";}std::string response;char buff[1024];while (true){// char *fgets(char *s, int size, FILE *stream);char *s = fgets(buff, sizeof(buff), pp);if (!s)break;elseresponse += buff;}pclose(pp);return response.empty() ? "not command" : response; }int main(int argc, char *argv[]) {// if (argc != 3)if (argc != 2){Usage();exit(USAGE_ERROR);}// std::string ip = argv[1];// uint16_t port = std::stoi(argv[2]);uint16_t port = std::stoi(argv[1]);// std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(ip, port);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(OnCommand, port);usvr->InitServer();usvr->Start();return 0; }
Makefile文件.PHONY:all all:udp_client udp_serverudp_client:UdpClient.ccg++ -o $@ $^ -std=c++14 udp_server:Main.ccg++ -o $@ $^ -std=c++14.PHONY:clean clean:rm -f udp_server udp_client
UdpClient.cc文件#include <iostream> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h>enum errorcode {USAGE_ERROR = 1, };void Usage() {printf("Usage : ./udp_client serverip serverport\n"); }int main(int argc, char *argv[]) {if (argc != 3){Usage();exit(USAGE_ERROR);}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 << "socket error" << std::endl;}// 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!// a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!// b. 什么时候bind呢?首次发送数据的时候struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport); // 主机序列转网络序列 -- 网络序列是大端// a.字符串序列点分十进制IP地址 转换为 4字节IP// b.主机序列转网络序列// in_addr_t inet_addr(const char *cp);server.sin_addr.s_addr = inet_addr(serverip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addrstd::string message;// 直接通信即可,已经自动绑定while (true){std::cout << "Please Enter:# ";std::getline(std::cin, message);// 发送数据// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addrsendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));// 获取回应数据char buff[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addrssize_t n = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);if (n > 0){buff[n] = 0;std::cout << "Server Echo:# " << buff << std::endl;}} }
UdpServer.hpp文件#pragma once#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h>#include <string.h> #include <error.h> #include <functional>using namespace std;#include "Log.hpp" #include "InetAddr.hpp"using task_t = function<std::string(std::string)>;const int Defaultfd = -1;enum errorcode {CREATE_ERROR = 1,BIND_ERROR,USAGE_ERROR };class UdpServer { public:// UdpServer(std::string ip, uint16_t port) : _sockfd(Defaultfd), _ip(ip), _port(port), _isrunning(false)UdpServer(task_t OnMessage, uint16_t port) : _sockfd(Defaultfd), _port(port), _isrunning(false), _OnMessage(OnMessage){}void InitServer(){// 1. 创建udp套接字 -- 必须要做的_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // udp通信if (_sockfd < 0){LOG(FATAL, "socke create error ! error string %s error %d", strerror(errno), errno);exit(CREATE_ERROR);}// 创建socket成功LOG(INFO, "create socket success ! sockfd:", _sockfd);// 2.0. 填充sockaddr_in结构struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port); // 主机序列转网络序列 -- 网络序列是大端// a.字符串序列点分十进制IP地址 转换为 4字节IP// b.主机序列转网络序列// in_addr_t inet_addr(const char *cp);// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addrlocal.sin_addr.s_addr = INADDR_ANY; // 0 -- 链接当前服务器的所有ip都接受// 2.1. 绑定网络信息int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "socke bind error ! error string %s error %d", strerror(errno), errno);exit(BIND_ERROR);}// 绑定成功LOG(INFO, "bind socket success !");}void Start(){// 一直运行,直到管理者不想运行了, 服务器都是死循环// UDP是面向数据报的协议_isrunning = true;while (true){// 获取数据// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addrstruct sockaddr_in peer;socklen_t len = sizeof(peer); // 既是输入(必须初始化) ,也是输出(peer的实际长度)char buff[1024];ssize_t n = recvfrom(_sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);if (n > 0){buff[n] = 0;// 回答发送方// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addrInetAddr addr(peer);std::string response = _OnMessage(buff); // 处理收到的消息LOG(DEBUG, "get message:%s", buff);sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len);}}_isrunning = false;}~UdpServer(){}private:int _sockfd;// std::string _ip; // 暂时先这样写 -- 不需要uint16_t _port;bool _isrunning;task_t _OnMessage; };
- 运行结果:
7.3、服务器英语单词字典
即客户端输入英文单词,服务器返回中文意思。
Dict.hpp文件#pragma once#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string> #include <fstream> #include <unordered_map>#include "Log.hpp"const std::string dict_path = "./dict.txt"; const std::string sep = ": ";class Dict { private:bool Load(){std::ifstream in(_file_path);if (!in.is_open()){LOG(FATAL, "open %s error", _file_path.c_str());return false;}std::string line;while (std::getline(in, line)){auto pos = line.find(sep);if (pos == std::string::npos)continue;std::string english = line.substr(0, pos);std::string chinese = line.substr(pos + sep.size()); // abc: cc -- pos:3LOG(DEBUG, "add %s : %s success", english.c_str(), chinese.c_str());_dict[english] = chinese;}LOG(DEBUG, "Load %s success", _file_path.c_str());return true;}public:Dict(const std::string file_path = dict_path) : _file_path(file_path){Load(); // 加载文件到内存}std::string Translate(std::string &str, bool *ok){*ok = false;for (auto &e : _dict){if (e.first == str){*ok = true;return e.second;}}return "未找到";}~Dict() {}private:const std::string _file_path;std::unordered_map<std::string, std::string> _dict; };
dict.txt文件apple: 苹果 banana: 香蕉 cat: 猫 dog: 狗 elephant: 大象 fish: 鱼 grape: 葡萄 house: 房子 ice: 冰 juice: 果汁 kite: 风筝 lion: 狮子 monkey: 猴子 night: 夜晚 orange: 橙子 piano: 钢琴 queen: 女王 rabbit: 兔子 sun: 太阳 tree: 树 umbrella: 雨伞 violin: 小提琴 water: 水 xylophone: 木琴 yogurt: 酸奶 zebra: 斑马 book: 书 chair: 椅子 desk: 桌子 ear: 耳朵 flower: 花 glove: 手套 hat: 帽子 island: 岛 jacket: 夹克 key: 钥匙 lamp: 灯 mountain: 山 notebook: 笔记本 ocean: 海洋 pencil: 铅笔 queen: 女王 river: 河流 shoe: 鞋子 telephone: 电话 umbrella: 雨伞 vase: 花瓶 window: 窗户 yard: 院子 zoo: 动物园 ant: 蚂蚁 bird: 鸟 cloud: 云 door: 门 egg: 鸡蛋 frog: 青蛙 guitar: 吉他 horse: 马 ink: 墨水 jelly: 果冻 king: 国王 leaf: 叶子 moon: 月亮 nest: 鸟巢 octopus: 章鱼 pen: 钢笔 quilt: 被子 rain: 雨 star: 星星 turtle: 乌龟 vulture: 秃鹫 whale: 鲸鱼 x-ray: X光 yo-yo: 溜溜球 airplane: 飞机 beach: 海滩 car: 汽车 diamond: 钻石 eagle: 老鹰 forest: 森林 gold: 黄金 hill: 小山 igloo: 冰屋 jungle: 丛林 kangaroo: 袋鼠 lake: 湖泊 mango: 芒果 nest: 鸟巢 owl: 猫头鹰 pizza: 披萨 queen: 女王 road: 道路 ship: 船 train: 火车 volcano: 火山 window: 窗户
InetAddr.hpp文件#pragma once#include <iostream> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string>class InetAddr {void GetAddress(std::string *ip, uint16_t *port){// char *inet_ntoa(struct in_addr in);*ip = inet_ntoa(_addr.sin_addr);*port = ntohs(_addr.sin_port);}public:InetAddr(const struct sockaddr_in &addr) : _addr(addr){GetAddress(&_ip, &_port);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr() {}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port; };
LockGuard.hpp文件#include <pthread.h>class LockGuard { public:LockGuard(pthread_mutex_t *mutex) : _mutex(mutex){pthread_mutex_lock(_mutex); // 构造加锁}~LockGuard(){pthread_mutex_unlock(_mutex); // 析构解锁}private:pthread_mutex_t *_mutex; };
Log.hpp文件#pragma once#include <string> #include <iostream> #include <fstream> #include <unistd.h> #include <stdarg.h> #include <sys/types.h> #include "LockGuard.hpp"bool isSave = false; // 默认向显示器打印 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; #define FILEPATH "./log.txt"enum level {DEBUG = 0,INFO,WARNING,ERROR,FATAL };void SaveToFile(const string &message) {ofstream out(FILEPATH, ios_base::app);if (!out.is_open())return;out << message;out.close(); }std::string LevelToString(int level) {switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknow";} }std::string GetTimeString() {time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None";char buff[1024];snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900,format_time->tm_mon + 1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return buff; }void LogMessage(const std::string filename, int line, bool issave, int level, const char *format, ...) {std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t pid = getpid();char buff[1024];va_list arg;// int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 使用可变参数va_start(arg, format);vsnprintf(buff, sizeof(buff), format, arg);va_end(arg);LockGuard lock(&mutex);std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(pid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buff + '\n';if (issave == false)std::cout << message;elseSaveToFile(message); }// 固定文件名和行数 #define LOG(level, format, ...) \do \{ \LogMessage(__FILE__, __LINE__, isSave, level, format, ##__VA_ARGS__); \} while (0)#define EnableScreen() \do \{ \isSave = false; \} while (0)#define EnableFile() \do \{ \isSave = true; \} while (0)void Test(int num, ...) {va_list arg;va_start(arg, num);while (num--){int data = va_arg(arg, int);std::cout << data << " ";}std::cout << std::endl;va_end(arg); }
Main.cc文件#include <iostream> #include <memory> #include "UdpServer.hpp"void Usage() {// printf("./udp_server serverip serverport\n");printf("Usage : ./udp_server serverport\n"); // ip 已经设置为0 }int main(int argc, char *argv[]) {// if (argc != 3)if (argc != 2){Usage();exit(USAGE_ERROR);}// std::string ip = argv[1];// uint16_t port = std::stoi(argv[2]);uint16_t port = std::stoi(argv[1]);// std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(ip, port);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);usvr->InitServer();usvr->Start();return 0; }
Makefile文件.PHONY:all all:udp_client udp_serverudp_client:UdpClient.ccg++ -o $@ $^ -std=c++14 udp_server:Main.ccg++ -o $@ $^ -std=c++14.PHONY:clean clean:rm -f udp_server udp_client
UdpClient.cc文件#include <iostream> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h>enum errorcode {USAGE_ERROR = 1, };void Usage() {printf("Usage : ./udp_client serverip serverport\n"); }int main(int argc, char *argv[]) {if (argc != 3){Usage();exit(USAGE_ERROR);}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 << "socket error" << std::endl;}// 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!// a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!// b. 什么时候bind呢?首次发送数据的时候struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport); // 主机序列转网络序列 -- 网络序列是大端// a.字符串序列点分十进制IP地址 转换为 4字节IP// b.主机序列转网络序列// in_addr_t inet_addr(const char *cp);server.sin_addr.s_addr = inet_addr(serverip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addrstd::string message;// 直接通信即可,已经自动绑定while (true){std::cout << "Please Enter:# ";std::getline(std::cin, message);// 发送数据// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addrsendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));// 获取回应数据char buff[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addrssize_t n = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);if (n > 0){buff[n] = 0;std::cout << "Server Echo:# " << buff << std::endl;}} }
UdpServer.hpp文件#pragma once#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h>#include <string.h> #include <error.h> #include <functional>using namespace std;#include "Log.hpp" #include "InetAddr.hpp" #include "Dict.hpp"const int Defaultfd = -1;enum errorcode {CREATE_ERROR = 1,BIND_ERROR,USAGE_ERROR };class UdpServer { public:// UdpServer(std::string ip, uint16_t port) : _sockfd(Defaultfd), _ip(ip), _port(port), _isrunning(false)UdpServer(uint16_t port) : _sockfd(Defaultfd), _port(port), _isrunning(false){}void InitServer(){// 1. 创建udp套接字 -- 必须要做的_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // udp通信if (_sockfd < 0){LOG(FATAL, "socke create error ! error string %s error %d", strerror(errno), errno);exit(CREATE_ERROR);}// 创建socket成功LOG(INFO, "create socket success ! sockfd: %d", _sockfd);// 2.0. 填充sockaddr_in结构struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port); // 主机序列转网络序列 -- 网络序列是大端// a.字符串序列点分十进制IP地址 转换为 4字节IP// b.主机序列转网络序列// in_addr_t inet_addr(const char *cp);// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addrlocal.sin_addr.s_addr = INADDR_ANY; // 0 -- 链接当前服务器的所有ip都接受// 2.1. 绑定网络信息int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "socke bind error ! error string %s error %d", strerror(errno), errno);exit(BIND_ERROR);}// 绑定成功LOG(INFO, "bind socket success !");}void Start(){// 一直运行,直到管理者不想运行了, 服务器都是死循环// UDP是面向数据报的协议_isrunning = true;while (true){// 获取数据// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addrstruct sockaddr_in peer;socklen_t len = sizeof(peer); // 既是输入(必须初始化) ,也是输出(peer的实际长度)char buff[1024];ssize_t n = recvfrom(_sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);if (n > 0){buff[n] = 0;// 回答发送方// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addrInetAddr addr(peer);Dict dict;std::string message = buff;auto translate = std::bind(&Dict::Translate, Dict(), placeholders::_1, placeholders::_2);bool flag;std::string response = translate(message, &flag);if(!flag) {// 没找到response = "单词没找到";}LOG(DEBUG, "get message , sender:[%s:%d] , content: %s", addr.Ip().c_str(), addr.Port(), buff);sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len);}}_isrunning = false;}~UdpServer(){}private:int _sockfd;// std::string _ip; // 暂时先这样写 -- 不需要uint16_t _port;bool _isrunning; };
- 运行结果:
7.4、服务器聊天室
可以多人聊天的聊天室,一人发消息,在聊天室的其他人都可以收到消息内容。
InetAddr.hpp文件#pragma once#include <iostream> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string>class InetAddr {void GetAddress(std::string *ip, uint16_t *port){// char *inet_ntoa(struct in_addr in);*ip = inet_ntoa(_addr.sin_addr);*port = ntohs(_addr.sin_port);}public:InetAddr(const struct sockaddr_in &addr) : _addr(addr){GetAddress(&_ip, &_port);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}bool operator==(InetAddr &addr){return _ip == addr.Ip() && _port == addr.Port();}const struct sockaddr_in& GetAddr(){return _addr;}~InetAddr() {}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port; };
LockGuard.hpp文件# pragma once#include <pthread.h>class LockGuard { public:LockGuard(pthread_mutex_t *mutex) : _mutex(mutex){pthread_mutex_lock(_mutex); // 构造加锁}~LockGuard(){pthread_mutex_unlock(_mutex); // 析构解锁}private:pthread_mutex_t *_mutex; };
Log.hpp文件#pragma once#include <string> #include <iostream> #include <fstream> #include <unistd.h> #include <stdarg.h> #include <sys/types.h> #include "LockGuard.hpp"bool isSave = false; // 默认向显示器打印 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; #define FILEPATH "./log.txt"enum level {DEBUG = 0,INFO,WARNING,ERROR,FATAL };void SaveToFile(const string &message) {ofstream out(FILEPATH, ios_base::app);if (!out.is_open())return;out << message;out.close(); }std::string LevelToString(int level) {switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknow";} }std::string GetTimeString() {time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None";char buff[1024];snprintf(buff, sizeof(buff), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900,format_time->tm_mon + 1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return buff; }void LogMessage(const std::string filename, int line, bool issave, int level, const char *format, ...) {std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t pid = getpid();char buff[1024];va_list arg;// int vsnprintf(char *str, size_t size, const char *format, va_list ap); // 使用可变参数va_start(arg, format);vsnprintf(buff, sizeof(buff), format, arg);va_end(arg);LockGuard lock(&mutex);std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[pid:" + std::to_string(pid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "] " + buff + '\n';if (issave == false)std::cout << message;elseSaveToFile(message); }// 固定文件名和行数 #define LOG(level, format, ...) \do \{ \LogMessage(__FILE__, __LINE__, isSave, level, format, ##__VA_ARGS__); \} while (0)#define EnableScreen() \do \{ \isSave = false; \} while (0)#define EnableFile() \do \{ \isSave = true; \} while (0)void Test(int num, ...) {va_list arg;va_start(arg, num);while (num--){int data = va_arg(arg, int);std::cout << data << " ";}std::cout << std::endl;va_end(arg); }
Main.cc文件#include <iostream> #include <memory>#include "UdpServer.hpp"void Usage() {// printf("./udp_server serverip serverport\n");printf("Usage : ./udp_server serverport\n"); // ip 已经设置为0 }int main(int argc, char *argv[]) {// if (argc != 3)if (argc != 2){Usage();exit(USAGE_ERROR);}// std::string ip = argv[1];// uint16_t port = std::stoi(argv[2]);uint16_t port = std::stoi(argv[1]);// std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(ip, port);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);usvr->InitServer();usvr->Start();return 0; }
Makefile文件.PHONY:all all:udp_client udp_serverudp_client:UdpClient.ccg++ -o $@ $^ -std=c++14 -lpthread udp_server:Main.ccg++ -o $@ $^ -std=c++14 -lpthread.PHONY:clean clean:rm -f udp_server udp_client
Thread.hpp文件#ifndef __THREAD_HPP__ #define __THREAD_HPP__#include <iostream> #include <string> #include <unistd.h> #include <functional> #include <pthread.h>using namespace std;// 封装Linux线程 namespace ThreadModule {using func_t = function<void(string &)>;class Thread{public:// /* ThreadData* */Thread(func_t<T> func, T data, const string& name = "default name") : _func(func), _data(data), _threadname(name), _stop(true) {}Thread(func_t func, const string &name = "default name") : _func(func), _threadname(name), _stop(true) {}void Execute(){_func(_threadname);// _func(_data);}// 隐含thisstatic void *threadroutine(void *arg){Thread *self = static_cast<Thread *>(arg);self->Execute(); // static 访问不了成员变量return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, threadroutine, this);if (!n){_stop = false;return true;}else{return false;}}void Detach(){if (!_stop){pthread_detach(_tid);}}void Join(){if (!_stop){pthread_join(_tid, nullptr);}}string name(){return _threadname;}void Stop(){_stop = true;}// ~Thread() {}private:pthread_t _tid;string _threadname;func_t _func;bool _stop;};} // namespace ThreadModule#endif
Threadpool.hpp文件#pragma once#include <vector> #include <queue> #include <queue> #include "Thread.hpp" #include <pthread.h> #include "LockGuard.hpp"using namespace ThreadModule;const int NUM = 3;template <typename T> class Threadpool {void LockQueue(pthread_mutex_t &mutex){pthread_mutex_lock(&mutex);}void UnLockQueue(pthread_mutex_t &mutex){pthread_mutex_unlock(&mutex);}void SleepThread(pthread_cond_t &cond, pthread_mutex_t &mutex){pthread_cond_wait(&cond, &mutex);}void WakeUpThread(pthread_cond_t &cond){pthread_cond_signal(&cond);}void WakeUpAll(pthread_cond_t &cond){pthread_cond_broadcast(&_cond);}Threadpool(const int threadnum = NUM) : _threadnum(threadnum), _waitnum(0), _isrunning(false){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);LOG(INFO, "Threadpool Constructor successful ! ");}void TaskHandler(string &name){// sleep(1);// cout << name << " : hh " << endl;// sleep(1);LOG(DEBUG, "%s is running", name.c_str());while (true){LockQueue(_mutex);while (_task_queue.empty() && _isrunning){// 等待++_waitnum;SleepThread(_cond, _mutex);--_waitnum;}// 此时一定大于一个线程没有休眠if (_task_queue.empty() && !_isrunning){// 此时任务队列已经没有内容,且此时线程池已经停止UnLockQueue(_mutex);cout << name << " quit ... " << endl;break;}LOG(DEBUG, "%s get task sucessful !", name.c_str());// 其他情况就得处理任务T t = _task_queue.front();_task_queue.pop();UnLockQueue(_mutex);// 处理任务t();// cout << name << " : " << t.stringResult() << endl;// LOG(DEBUG, "%s handler task sucessful ! Result is %s", name.c_str(), t.stringResult().c_str());sleep(1);}}void InitThreadPool(){for (int i = 0; i < _threadnum; ++i){string name = "Thread - " + to_string(i + 1);_threads.emplace_back(bind(&Threadpool::TaskHandler, this, placeholders::_1), name);}_isrunning = true;LOG(INFO, "Init Threadpool successful !");}public:static Threadpool<T> *GetInstance(int threadnum = NUM){if (_instance == nullptr){LockGuard lockguard(&_lock);if (_instance == nullptr){// pthread_mutex_lock(&_lock);// 第一次创建线程池_instance = new Threadpool<T>(threadnum);_instance->InitThreadPool();_instance->Start();LOG(DEBUG, "第一次创建线程池");// pthread_mutex_unlock(&_lock);return _instance;}}LOG(DEBUG, "获取线程池");return _instance;}bool Enqueue(const T &in){bool ret = false;LockQueue(_mutex);if (_isrunning){_task_queue.push(in);if (_waitnum > 0)WakeUpThread(_cond);LOG(DEBUG, "enqueue sucessful...");ret = true;}UnLockQueue(_mutex);return ret;}void Stop(){LockQueue(_mutex);_isrunning = false;if (_waitnum > 0)WakeUpAll(_cond);UnLockQueue(_mutex);}void Start(){for (auto &thread : _threads){thread.Start();LOG(INFO, "%s is start sucessful...", thread.name().c_str());}}void Wait(){for (auto &thread : _threads){thread.Join();LOG(INFO, "%s is quit...", thread.name().c_str());}}~Threadpool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);LOG(INFO, "delete mutex sucessful !");}private:vector<Thread> _threads;queue<T> _task_queue;int _threadnum;int _waitnum;pthread_mutex_t _mutex; // 互斥访问任务队列pthread_cond_t _cond;bool _isrunning;// 懒汉模式static Threadpool<T> *_instance;static pthread_mutex_t _lock; };template <typename T> Threadpool<T> *Threadpool<T>::_instance = nullptr; template <typename T> pthread_mutex_t Threadpool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;
UdpClient.cc文件#include <iostream> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include "Thread.hpp"using namespace ThreadModule;enum errorcode {USAGE_ERROR = 1, };void Usage() {printf("Usage : ./udp_client serverip serverport\n"); }class ThreadData { public:ThreadData(int sock, struct sockaddr_in &server) : _sockfd(sock), _server(server) {}~ThreadData() {}public:int _sockfd;struct sockaddr_in _server; };void RecverRoute(ThreadData &td, std::string &threadname) {while (true){// 获取回应数据char buff[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addrssize_t n = recvfrom(td._sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);if (n > 0){buff[n] = 0;std::cerr << threadname << " | " << buff << std::endl; // 方便重定位,分离发送窗口和接收窗口}} }void SenderRoute(ThreadData &td, std::string &threadname) {pthread_detach(pthread_self());while (true){std::string message;std::cout << threadname << " | Please Enter:# ";std::getline(std::cin, message);if (message == "QUIT"){std::cout <<threadname << " : 不玩了!" << std::endl;break;}// 发送数据// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addrint n = sendto(td._sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&td._server, sizeof(td._server));if (n <= 0){std::cout << "sendto error" << std::endl;break;}} }int main(int argc, char *argv[]) {if (argc != 3){Usage();exit(USAGE_ERROR);}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 << "socket error" << std::endl;}// 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!// a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!// b. 什么时候bind呢?首次发送数据的时候struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport); // 主机序列转网络序列 -- 网络序列是大端// a.字符串序列点分十进制IP地址 转换为 4字节IP// b.主机序列转网络序列// in_addr_t inet_addr(const char *cp);server.sin_addr.s_addr = inet_addr(serverip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addr// 直接通信即可,已经自动绑定// 创建两个线程,一个收消息,一个发消息ThreadData td(sockfd, server);auto boundRecv = std::bind(RecverRoute, td, std::placeholders::_1);auto boundSend = std::bind(SenderRoute, td, std::placeholders::_1);Thread recver(boundRecv, "recver");recver.Start();Thread sender(boundSend, "sender");sender.Start();// // udp是全双工的// // 下面代码只能半双工,不能不能同时收发// while (true)// {// std::cout << "Please Enter:# ";// std::getline(std::cin, message);// // 发送数据// // ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addr// sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));// // 获取回应数据// char buff[1024];// struct sockaddr_in peer;// socklen_t len = sizeof(peer);// // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addr// ssize_t n = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);// if (n > 0)// {// buff[n] = 0;// std::cout << "Server Echo:# " << buff << std::endl;// }// }sender.Join();recver.Join();return 0; }
UdpServer.hpp文件#pragma once#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h>#include <string.h> #include <error.h> #include <pthread.h> #include <functional>using namespace std;#include "Log.hpp" #include "InetAddr.hpp" #include "Threadpool.hpp"const int Defaultfd = -1;enum errorcode {CREATE_ERROR = 1,BIND_ERROR,USAGE_ERROR };using task_t = function<void()>;class UdpServer { public:// UdpServer(std::string ip, uint16_t port) : _sockfd(Defaultfd), _ip(ip), _port(port), _isrunning(false)UdpServer(uint16_t port) : _sockfd(Defaultfd), _port(port), _isrunning(false){pthread_mutex_init(&_mutex, nullptr);}void InitServer(){// 1. 创建udp套接字 -- 必须要做的_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // udp通信if (_sockfd < 0){LOG(FATAL, "socke create error ! error string %s error %d", strerror(errno), errno);exit(CREATE_ERROR);}// 创建socket成功LOG(INFO, "create socket success ! sockfd:", _sockfd);// 2.0. 填充sockaddr_in结构struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port); // 主机序列转网络序列 -- 网络序列是大端// a.字符串序列点分十进制IP地址 转换为 4字节IP// b.主机序列转网络序列// in_addr_t inet_addr(const char *cp);// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // sin_addr 是一个结构体,里面的成员是s_addrlocal.sin_addr.s_addr = INADDR_ANY; // 0 -- 链接当前服务器的所有ip都接受// 2.1. 绑定网络信息int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "socke bind error ! error string %s error %d", strerror(errno), errno);exit(BIND_ERROR);}// 绑定成功LOG(INFO, "bind socket success !");// Threadpool<task_t>::GetInstance()->Start(); // 启动线程池 // 单例模式GetInstance里面已经启动线程池了,直接放入任务即可}void AddOnlineUser(InetAddr user){LockGuard lockguard(&_mutex);for (auto &e : _online_user){if (e == user)return;}_online_user.push_back(user);}void DelOnlineUser(InetAddr user){LockGuard lockguard(&_mutex);for (auto iter = _online_user.begin(); iter != _online_user.end(); ++iter){if (*iter == user)_online_user.erase(iter);}}void Route(std::string message){LockGuard lockguard(&_mutex);for (auto &user : _online_user){sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&user.GetAddr(), sizeof(user.GetAddr()));}}void Start(){// 一直运行,直到管理者不想运行了, 服务器都是死循环// UDP是面向数据报的协议_isrunning = true;while (true){// 获取数据// ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // src_addrstruct sockaddr_in peer;socklen_t len = sizeof(peer); // 既是输入(必须初始化) ,也是输出(peer的实际长度)char buff[1024];ssize_t n = recvfrom(_sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&peer, &len);if (n > 0){buff[n] = 0;// 回答发送方// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // dest_addrInetAddr addr(peer);AddOnlineUser(addr); // 添加用户if (!strcmp(buff, "QUIT")){DelOnlineUser(addr); // 删除用户continue;}// 转发std::string message = "[";message += addr.Ip();message += ":";message += std::to_string(addr.Port());message += "] ";message += buff;task_t task = std::bind(&UdpServer::Route, this, message); // 这里绑定后,task的参数就是void(),task就是绑定后的RouteThreadpool<task_t>::GetInstance()->Enqueue(task); // 启动线程池LOG(DEBUG, "get message , sender:[%s:%d] , content: %s", addr.Ip().c_str(), addr.Port(), buff);// sendto(_sockfd, buff, strlen(buff), 0, (struct sockaddr *)&peer, len);}}_isrunning = false;}~UdpServer(){pthread_mutex_destroy(&_mutex);}private:int _sockfd;// std::string _ip; // 暂时先这样写 -- 不需要uint16_t _port;bool _isrunning;pthread_mutex_t _mutex;std::vector<InetAddr> _online_user; };
运行结果:
注意,下面用到的clienta和clientb都是管道文件,用来对文件的标准输出和标准错误分离。
8、补充内容
地址转换函数:将点分十进制的IP字符串转换为in_addr类型的IP地址,或者反过来。
#include <arpa/inet.h>int inet_pton(int af, const char *src, void *dst); int inet_aton(const char *cp, struct in_addr *inp);const char *inet_ntop(int af, const void *src,char *dst, socklen_t size); char *inet_ntoa(struct in_addr in); // The inet_ntoa() function converts the Internet host address in, given in networkbyte order, to a string in IPv4 dotted-decimal notation. The string is returned in a statically allocated buffer,which subsequent calls will overwrite.对应inet_ntoa,man手册上说会返回一个静态分配的缓冲区,后续调用会覆盖前面的内容,不需要我们手动释放。那么如果我们多次调用这个函数会出现什么情况?
在我们使用多线程调用该函数时,可能会出现数据错误的情况(需要使用互斥锁的保护)!
并且,在 APUE 中, 明确提出 inet_ntoa 不是线程安全的函数。
在多线程环境下,推荐使用 inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。
OKOK,Linux网络编程之UDP就到这里,如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。
Xpccccc的github主页
相关文章:
Linux网络编程之UDP
文章目录 Linux网络编程之UDP1、端口号2、端口号和进程ID的区别3、重新认识网络通讯过程4、UDP协议的简单认识5、网络字节序6、socket编程接口6.1、socket常见接口6.2、sockaddr通用地址结构 7、简单的UDP网络程序7.1、服务器响应程序7.2、服务器执行命令行7.3、服务器英语单词…...
graham 算法计算平面投影点集的凸包
文章目录 向量的内积(点乘)、外积(叉乘)确定旋转方向numpy 的 cross 和 outernp.inner 向量与矩阵计算示例np.outer 向量与矩阵计算示例 python 示例生成样例散点数据图显示按极角排序的结果根据排序点计算向量转向并连成凸包 基本…...
【海外云手机】静态住宅IP集成解决方案
航海大背景下,企业和个人用户对于网络隐私、稳定性以及跨国业务的需求日益增加。静态住宅IP与海外云手机的结合,提供了一种创新的集成解决方案,能够有效应对这些需求。 本篇文章分为三个部分;静态住宅优势、云手机优势、集成解决…...
最新!CSSCI(2023-2024)期刊目录公布!
【SciencePub学术】据鲁迅美术学院7月16日消息,近日,南京大学中国社会科学研究评价中心公布了中文社会科学引文索引(CSSCI)(2023—2024)数据库最新入选目录。 C刊一般指CSSCI来源期刊,即南大核心…...
C语言 | Leetcode C语言题解之第237题删除链表中的节点
题目: 题解: /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/void deleteNode(struct ListNode* node) {struct ListNode * p node->next;int temp;temp node->val;node->val…...
linux LED代码设计
设计目标: 写RGB LED灭、亮、闪烁等效果,不同颜色也需要设置 #include <iostream> #include <unistd.h> // 对于usleep() #include <fcntl.h> // 对于open(), close() #include <sys/ioctl.h> // 对于ioctl() #include <li…...
Jvm基础(一)
目录 JVM是什么运行时数据区域线程私有1.程序计数器2.虚拟机栈3.本地方法栈 线程共享1.方法区2.堆 二、对象创建1.给对象分配空间(1)指针碰撞(2)空闲列表 2.对象的内存布局对象的组成Mark Word类型指针实例数据:对齐填充 对象的访问定位句柄法 三、垃圾收集器和内存…...
深入理解FFmpeg--软/硬件解码流程
FFmpeg是一款强大的多媒体处理工具,支持软件和硬件解码。软件解码利用CPU执行解码过程,适用于各种平台,但可能对性能要求较高。硬件解码则利用GPU或其他专用硬件加速解码,能显著降低CPU负载,提升解码效率和能效。FFmpe…...
新的铸造厂通过 PROFIBUS 技术实现完全自动化
钢铁生产商某钢以其在厚钢板类别中极高的产品质量而闻名。其原材料(板坯连铸机)在钢铁厂本地生产,该厂最近新建了一座垂直连铸厂。该项目的一个主要目标是从一开始就完全自动化这座新工厂和整个铸造过程,以高成本效率实现最佳产品…...
【UE5.1】NPC人工智能——04 NPC巡逻
效果 步骤 一、准备行为树和黑板 1. 对我们之前创建的AI控制器创建一个子蓝图类 这里命名为“BP_NPC_AIController_Lion”,表示专门用于控制狮子的AI控制器 2. 打开狮子蓝图“Character_Lion” 在类默认值中将“AI控制器类”修改为“BP_NPC_AIController_Lion” 3…...
计算机视觉主流框架及其应用方向
文章目录 前言一、计算机视觉领域的主要框架1、深度学习框架1.1、TensorFlow1.2、PyTorch 2、神经网络模型2.1、卷积神经网络(CNN)2.2、循环神经网络(RNN) 二、框架在计算机视觉任务中的应用1、TensorFlow1.1、概述:1.…...
群晖 搭建alist 记录
docker搭建 使用docker-compose 创建一个 docker-compose.yml version: 3.5services:qbittorrent:image: linuxserver/qbittorrent:latestcontainer_name: qbittorrent# network_mode: hostenvironment:- PUID1000- PGID100- TZAsia/Shanghai- WEBUI_PORT8181 # 将外部端口…...
【北航主办丨本届SPIE独立出版丨已确认ISSN号】第三届智能机械与人机交互技术学术会议(IHCIT 2024,7月27)
由北京航空航天大学指导,北京航空航天大学自动化科学与电气工程学院主办,AEIC学术交流中心承办的第三届智能机械与人机交互技术学术会议(IHCIT 2024)将定于2024年7月27日于中国杭州召开。 大会面向基础与前沿、学科与产业…...
深入浅出WebRTC—NACK
WebRTC 中的 NACK(Negative Acknowledgment)机制是实时通信中处理网络丢包的关键组件。网络丢包是常见的现象,尤其是在无线网络或不稳定连接中。NACK 机制旨在通过请求重传丢失的数据包来减少这种影响,从而保持通信的连续性和质量…...
简单工厂模式、工厂模式和抽象工厂模式的区别
简单工厂模式、工厂模式和抽象工厂模式都是创建型设计模式,它们之间在目的、实现方式和适用场景上存在显著的区别。以下是对这三种模式的详细比较: 一、定义与目的 简单工厂模式(Simple Factory Pattern) 定义: 简单工…...
JVM-垃圾回收与内存分配
目录 垃圾收集器与内存分配策略 引用 对象的访问方式有哪些?(句柄和直接指针) Java的引用有哪些类型? 如何判断对象是否是垃圾? 请列举一些可作为GC Roots的对象? 对象头了解吗? mark word(hashcode、分代、锁标志位)、…...
Jolt路线图
1. 引言 a16z crypto团队2024年7月更新了其Jolt路线图: 主要分为3大维度: 1)链上验证维度: 1.1)Zeromorph:见Aztec Labs团队2023年论文 Zeromorph: Zero-Knowledge Multilinear-Evaluation Proofs from…...
NEEP-EN2-2019-Text4
英二-2019-Text4摘自赫芬顿邮报《The Huffington Post》2018年6月的一篇名为“Let’s Stop Pretending Quitting Straws Will Solve Plastic Pollution”的文章。 以下为个人解析,非官方公开标准资料,可能有误,仅供参考。(单词解释…...
docker 部署wechatbot-webhook 并获取接口实现微信群图片自动保存到chevereto图库等
功能如图: docker部署 version: "3" services:excalidraw:image: dannicool/docker-wechatbot-webhook:latestcontainer_name: wechatbot-webhookdeploy:resources:limits:cpus: 0.15memory: 500Mreservations:cpus: 0.05memory: 80Mrestart: alwayspor…...
OpenWrt安装快速入门指南
在刷新 OpenWrt 固件之前,建议进行以下准备: 1、不要急于安装,慢慢来。如果在安装过程中出现奇怪之处,请先找到答案,然后再继续。 2、准备好设备的精确型号,以便能够选择正确的OpenWrt固件。 3、手上有关…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
Python爬虫实战:研究Restkit库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地采集这些数据并将其应用于实际业务中,成为了许多企业和开发者关注的焦点。网络爬虫技术作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 RESTful API …...
拟合问题处理
在机器学习中,核心任务通常围绕模型训练和性能提升展开,但你提到的 “优化训练数据解决过拟合” 和 “提升泛化性能解决欠拟合” 需要结合更准确的概念进行梳理。以下是对机器学习核心任务的系统复习和修正: 一、机器学习的核心任务框架 机…...
高保真组件库:开关
一:制作关状态 拖入一个矩形作为关闭的底色:44 x 22,填充灰色CCCCCC,圆角23,边框宽度0,文本为”关“,右对齐,边距2,2,6,2,文本颜色白色FFFFFF。 拖拽一个椭圆,尺寸18 x 18,边框为0。3. 全选转为动态面板状态1命名为”关“。 二:制作开状态 复制关状态并命名为”开…...
20250609在荣品的PRO-RK3566开发板的Android13下解决串口可以执行命令但是脚本执行命令异常的问题
20250609在荣品的PRO-RK3566开发板的Android13下解决串口可以执行命令但是脚本执行命令异常的问题 2025/6/9 20:54 缘起,为了跨网段推流,千辛万苦配置好了网络参数。 但是命令iptables -t filter -F tetherctrl_FORWARD可以在调试串口/DEBUG口正确执行。…...




