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、补充内容

img

Linux网络编程之UDP

1、端口号

端口号是计算机网络中用来标识特定应用程序或服务的数字标识符。在网络通信中,每个数据包都包含一个目标端口号和源端口号,这样可以确保数据包能够正确地路由到目标应用程序或服务

  • 端口号是一个16位的数字,范围从0到65535。

  • 用于在传输层(通常是TCP或UDP协议)标识特定的应用程序或服务。

  • 允许同一台计算机上的多个应用程序同时进行网络通信,每个应用程序使用不同的端口号。

  • 在目标设备上,操作系统通过端口号将数据包传递给正确的应用程序或服务。

  • IP地址 + 端口号 = 套接字,能够标识网络上的某一台主机的某一个进程。

端口号使得计算机网络中的不同应用程序能够通过网络进行可靠的通信和数据交换,同时确保数据能够安全地到达目标应用程序。

一个端口号只能被一个进程占用。


2、端口号和进程ID的区别

  • 用途不同:端口号用于标识网络中的应用程序或服务,而进程ID用于操作系统内部管理和标识正在运行的进程。

  • 作用范围:端口号主要在网络通信中使用,进程ID主要在操作系统内部使用。

  • 分配方式:端口号是在应用程序设计或网络配置时指定的,而进程ID是由操作系统动态分配给每个新创建的进程。

如果端口号和进程ID合并,那么系统的耦合度会增加,不符合低耦合的编程思想

另外,一个进程可以有多个端口号,但是一个端口号不能被多个进程使用。


3、重新认识网络通讯过程

  1. 我们上网,无非就是两种动作:a. 把远端数据拉取到本地 b. 把数据发送到远端。

  2. 大部分的网络通信行为,都是用户触发的,在计算机中,用户就是进程!

  3. 把数据发送到目标主机,不是目的,而是手段。真正的目的,是把数据交给目的主机上的某个服务(进程)。

  4. 网络通信的本质,其实就是进程在帮我们进行网络通信(进程间通信),无论是对于客户端还是服务器。

  5. IP地址 + 端口号 = 套接字,能够标识网络上的某一台主机的某一个进程。

  6. 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主页

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/47930.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue学习笔记(十)——Vuex(状态管理,组件间共享数据)

1. vuex基础-介绍 1.1 为什么会有Vuex ? 在现代 Web 开发复杂多变的需求驱动之下&#xff0c;组件化开发已然成为了事实上的标准。然而大多数场景下的组件都并不是独立存在的&#xff0c;而是相互协作共同构成了一个复杂的业务功能。 组件间的通信成为了必不可少的开发需求。…

《Linux运维总结:基于ARM64架构CPU使用docker-compose一键离线部署单机版tendis2.4.2》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;《Linux运维篇&#xff1a;Linux系统运维指南》 一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面对不同的客户部署业务系统&#xff0…

数学建模——快递包裹装箱优化问题(2023年长三角数学建模A题问题一、问题二)

快递包裹装箱优化问题 2022 年&#xff0c;中国一年的包 裹已经超过1000 亿件&#xff0c;占据了全球快递事务量的一半以上。近几年&#xff0c;中国每年新增包裹数量相当于美国整个国家一年的包裹数量&#xff0c;十年前中国还是物流成本最昂贵的国家&#xff0c;当前中国已经…

【IC前端虚拟项目】sanity_case的编写与通包测试

【IC前端虚拟项目】数据搬运指令处理模块前端实现虚拟项目说明-CSDN博客 在花了大力气完成reference model之后,整个验证环境的搭建就完成了,再多看一下这个结构然后就可以进行sanity_case和通包测试: 关于sanity_case和通包测试我在很多篇文章中说过好多次了在这里就不赘述…

el-menu弹出菜单样式不生效

1. 使用 ruoyi 项目时出现的问题。 <template><el-menu:default-active"activeMenu":collapse"false":unique-opened"true"class"container":collapse-transition"true"mode"horizontal"><sideba…

华为od 100问 持续分享6-入职体检

我是一名软件开发培训机构老师&#xff0c;我的学生已经有上百人通过了华为OD机试&#xff0c;学生们每次考完试&#xff0c;会把题目拿出来一起交流分享。 重要&#xff1a;2024年5月份开始&#xff0c;考的都是OD统一考试&#xff08;D卷&#xff09;&#xff0c;题库已经整…

Linux驱动开发-05APP和驱动的交互方式

一、传输数据 APP和驱动: copy_to_usercopy_from_user驱动和硬件: 各个子系统的函数通过ioremap映射寄存器地址后,直接访问寄存器二、APP使用驱动的四种方式 驱动程序:提供能力,不提供策略 非阻塞(查询)(应用程序访问底层驱动时(read、write时),驱动没有数据不等待,…

基于 CNN(二维卷积Conv2D)+LSTM 实现股票多变量时间序列预测(PyTorch版)

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…

单机、集群、分布式服务器比较:

1. 单机服务器的瓶颈&#xff1a; 单机服务器&#xff1a;一台服务器独立运行一个工程所需的全部的业务模块 受限于服务器硬件资源&#xff0c;所承受用户并发量受限&#xff0c;32位linux操作系统最大并发量为两万任一模块的变动和修改&#xff0c;都会导致整个项目代码重新编…

PHP上门按摩专业版防东郊到家系统源码小程序

&#x1f486;‍♀️【尊享级体验】上门按摩专业版&#xff0c;告别东郊到家&#xff0c;解锁全新放松秘籍&#xff01;&#x1f3e0;✨ &#x1f525;【开篇安利&#xff0c;告别传统束缚】&#x1f525; 亲们&#xff0c;是不是厌倦了忙碌生活中的疲惫感&#xff1f;想要享…

从微软发iPhone,聊聊企业设备管理

今天讲个上周的旧闻&#xff0c;微软给员工免费发iPhone。其实上周就有很多朋友私信问我&#xff0c;在知乎上邀请我回答相关话题&#xff0c;今天就抽点时间和大家一起聊聊这事。我不想讨论太多新闻本身&#xff0c;而是更想聊聊事件的主要原因——微软企业设备管理&#xff0…

利用AI与数据分析优化招聘决策

一、引言 在竞争激烈的职场环境中&#xff0c;招聘是组织获取人才、实现战略目标的关键环节。然而&#xff0c;传统的招聘方式往往依赖人力资源部门的主观经验和直觉&#xff0c;难以准确预测招聘效果&#xff0c;评估招聘渠道的效率。随着人工智能&#xff08;AI&#xff09;…

CSPVD 智慧工地安全帽安全背心检测开发包

CSPVD SDK适用于为各种智慧工地应用增加安全防护穿戴合规的检测能力&#xff0c;能够有效检测未戴安全帽和未穿 安全背心的人员&#xff0c;提供Web API和原生API。官方下载&#xff1a;CSPVD工地安全防护检测 1、目录组织 CSPVD开发包的目录组织说明如下&#xff1a; xlpr_…

linux进程——状态——linux与一般操作系统的状态

前言&#xff1a;博主在之前的文章已经讲解了PCB里面的pid——主要讲解了父子进程PID&#xff0c; 以及fork的相关内容。 本节进入PCB的下一个成员——状态&#xff0c; 状态是用来表示一个进程在内存中的状态的&#xff0c; 进程在内存中肯能处于各种状态&#xff0c; 比如运行…

云原生系列 - Jenkins

Jenkins Jenkins&#xff0c;原名 Hudson&#xff0c;2011 年改为现在的名字。它是一个开源的实现持续集成的软件工具。 官方网站&#xff08;英文&#xff09;&#xff1a;https://www.jenkins.io/ 官方网站&#xff08;中文&#xff09;&#xff1a;https://www.jenkins.io…

【Linux】汇总TCP网络连接状态命令

输入命令&#xff1a; netstat -na | awk /^tcp/ {S[$NF]} END {for(a in S) print a, S[a]} 显示&#xff1a; 让我们逐步解析这个命令&#xff1a; netstat -na: netstat 是一个用于显示网络连接、路由表、接口统计等信息的命令。 -n 选项表示输出地址和端口以数字格式显示…

贝锐蒲公英远程运维方案:即装即用、无需专线,断网也可远程维护

目前&#xff0c;公路、隧道、桥梁、航道&#xff0c;甚至是施工现场和工业生产环境等&#xff0c;都采用了实时监测方案。 通过部署各类传感器和摄像头等设备&#xff0c;现场视频画面和控制单元&#xff08;如PLC、工控机等&#xff09;数据可以实时回传&#xff0c;用于集中…

AI批量剪辑,批量发布大模型矩阵系统搭建开发

目录 前言 一、AI矩阵系统功能 二、AI批量剪辑可以解决什么问题&#xff1f; 总结&#xff1a; 前言 基于ai生成或剪辑视频的原理&#xff0c;利用ai将原视频进行混剪&#xff0c;生成新的视频素材。ai会将剪辑好的视频加上标题&#xff0c;批量发布到各个自媒体账号上。这…

Android车载MCU控制音量和ARM控制音量的区别和优缺点—TEF6686 FM/AM芯片

不要嫌前进的慢&#xff0c;只要一直在前进就好 文章目录 前言一、系统架构图1.MCU控制音量的架构图&#xff08;老方法&#xff09;2.ARM控制音量的架构图&#xff08;新方法&#xff09; 二、为啥控制音量不是用AudioManager而是执着去直接控制TDA7729&#xff1f;三、MCU控制…

基于JAVA+SpringBoot+Vue+uniApp的校园日常作品商品分享小程序

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、SpringCloud、Layui、Echarts图表、Nodejs、爬…