文章目录
- 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主页