当前位置: 首页 > news >正文

Linux---网络套接字

端口号

端口号

端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
一个端口号只能被一个进程占用

在公网上,IP地址能表示唯一的一台主机,端口号port,用来表示该主机上的唯一的一个进程,IP:port = 标识全网唯一的一个进程。

现在用户要刷抖音,假如抖音的端口号为4321,获取一个抖音短视频,抖音应用通过协议连接到抖音服务器的IP地址,然后建立连接,抖音服务器传送短视频的数据到达用户的设备上。在通信的过程中,一定要知道端口号,不知道的话无法通信。

一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定。

认识TCP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题.
传输层协议
有连接
可靠传输
面向字节流

认识UDP协议

此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论.
传输层协议
无连接
不可靠传输
面向数据报

网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢 ?

大端:低位地址存放高位数据,高位地址存放低位数据
小端:低位地址存放低位数据,高位地址存放高位数据

大端和小端只是对数据类型长度是两个及以上的,如int short,对于单字节没限制,在网络中经常需要考虑大端和小端的是ip和端口。

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

在网络通信中,为了确保数据在不同系统之间能正确解释,网络字节序被定义为大端序。
 


为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回 ;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。


IP地址转换函数

inet_pton 函数是一个用于将点分十进制的IPv4或IPv6地址转换为二进制形式的函数,
即将地址从文本表示形式转换为网络字节序的二进制形式。这个函数的声明如下:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
af 参数表示地址族,可以是 AF_INET 表示IPv4,也可以是 AF_INET6 表示IPv6。
src 参数是包含要转换的IP地址的点分十进制字符串。
dst 参数是指向存储转换后二进制形式的地址的指针。
函数的返回值是整数,如果转换成功,则返回1(表示成功),
如果地址格式无效或发生错误,则返回nullptr(表示无效的地址格式)
或-1(表示发生了错误)。
  1. IP 地址转换函数:
  2. p->表示点分十进制的字符串形式
  3. to->到
  4. n->表示 network 网络

地址转换函数

inet_ntop 函数是用于将网络字节序的地址转换为字符串表示的函数。
它是IPv4和IPv6通用的函数,可以用于将网络地址转换为点分十进制字符串或IPv6的十六进制字符串。
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);af:地址族(Address Family),通常是 AF_INET 表示IPv4,AF_INET6 表示IPv6。
src:指向存储网络地址的结构体的指针。
dst:用于存储转换后的字符串的缓冲区指针。
size:缓冲区的大小。
该函数成功时返回指向转换后的字符串的指针,失败时返回 NULL。

还有另一个函数

#include <arpa/inet.h>in_addr_t inet_addr(const char *cp);
这个函数接受一个表示IP地址的字符串(cp参数),
并返回一个in_addr_t类型的二进制格式表示的IP地址。
如果转换失败,函数返回INADDR_NONE。这个函数仅支持IPV4

socket编程常见接口

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器
int socket(int domain, int type, int protocol)
函数描述:创建socket
参数说明:
domain:协议版本AF_INET IPV4AF_INET6 IPV6AF_UNIX_LOACL 本地套接字使用
type:协议类型
SOCK_STREAM 流式,默认使用的协议是tcp协议
SOCK_DGRAM报式,默认使用的是udp协议
protocal:
一般填0,表示使用对应类型的默认协议

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
函数描述:将socket文件描述符和ip,port绑定
参数说明:
socket: 调用 socket 孟数返回的文件描述符addr: 本地服务器的 IP 地址和 PORT,struct sockaddr_in serv;serv.sin_family = AF_INET:serv.sin_port = htons(8888)://serv.sin_addr.s_addr = htonl(INADDR_ANY)://INADDR_ANY: 表示使用本机任意有效的可用IP
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr)
addrlen: addr 变量的占用的内存大小
返回值:失败返回-1,并设置errno

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog)
函数描述: 将套接字由主动态变为被动态
参数说明:
socket: 调用 socket 函数返回的文件描述符
backlog: 同时请求连接的最大个数(还未建立连接)
返回值:成功: 返回0失败: 返回-1,并设置errno

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
accept 函数是一个阻寒函数,若没有新的连接请求,则一直阻寒.
从已连接队列中获取一个新的连接,并获得一个新的文件描述符,该文件描
述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)
函数参数:
sockfd:调用 socket 函数返回的文件描述符
addr:传出参数,保存客户端的地址信息
addrlen: n:传入传出参数,addr变量所占内存空间大小
返回值:
成功:返回一个新的文件描述符,用于和客户端通信
失败:返回-1,并设置errno值,

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
函数说明: 连接服务器
函教参款:
sockfd: 调用 socket 函数返回的文件描述符
addr: 服务端的地址信息
addrlen: addr 变量的内存大小
返回值:
成功: 返回0
失败: 返回-1,并设置errno 值

socket变成用到的重要的结构体 struct sockaddr

sockaddr结构

sockaddr_in结构

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址.

in_addr结构
 


in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数 。


UDP通信

在网络通信中,一般不使用read和write这两个接口的。

当有人来发送消息的时候,你想知道是谁发的消息。

可以使用recvfrom函数,这个函数中存在一个src_addr参数,这个结构体需要我们自己定义,然后将结构体对象传入进去,就可以知道是谁发送的信息了。

发送数据可以使用sendto。


在这里,简单的写了一个日志系统,后面就不再使用perror,而是直接使用日志系统来提示错误。

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <cstring>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2 
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}// void logmessage(int level, const char *format, ...)// {//     time_t t = time(nullptr);//     struct tm *ctime = localtime(&t);//     char leftbuffer[SIZE];//     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),//              ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,//              ctime->tm_hour, ctime->tm_min, ctime->tm_sec);//     // va_list s;//     // va_start(s, format);//     char rightbuffer[SIZE];//     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);//     // va_end(s);//     // 格式:默认部分+自定义部分//     char logtxt[SIZE * 2];//     snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);//     // printf("%s", logtxt); // 暂时打印//     printLog(level, logtxt);// }void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;std::string path;
};// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }//     va_end(s); //s = NULL
//     return sum;
// }

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include "log1.hpp"std::string defaultip = "0.0.0.0";Log log;#define SIZE 1024class UdpServer
{
public:UdpServer(uint16_t port, std::string ip = defaultip):_port(port),_ip(ip){}void Init(){// 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "sockfd successful");// 2.绑定端口号struct sockaddr_in serv;bzero(&serv, sizeof(serv)); // 初始化addrserv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));if (bd < 0){log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(bd, "bind successful");}void Run(){IsConnect = true;char inbuf[SIZE];while (IsConnect){struct sockaddr_in client;bzero(&client, sizeof(client));socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, (socklen_t *)&len);if (n < 0){log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);continue;}inbuf[n] = 0;std::string info = inbuf;std::string echo_string = "server echo# " + info;sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){}
public:uint16_t _port;std::string _ip;bool IsConnect;int sockfd;
};
#include "UdpServer.hpp"
#include <memory>int main()
{std::unique_ptr<UdpServer> udp(new UdpServer(8080));udp->Init();udp->Run();return 0;
}

通过 netstat -naup可以查看运行的udp的情况。

只要能用上面的指令查到,就说明我们的服务已经启动了。


当我们把IP改成云服务器的ip,会出现这样的情况,首先端口号是没问题的。

如果你用的是虚拟机,代码是可以运行的。但我今天用的是云服务器,是禁止直接bind公网IP的。当我们服务器有多张网卡的时候,这个IP地址可能就不是唯一的,其他IP地址也可以连接我们这个服务器,所以一般在绑定IP的时候,可以设为0,bind(IP:0), 凡是发给我这台主机的数据,我们都要根据端口号向上交付。

绑定IP地址为0----任意地址bind。


现在将端口号改为80.

会出现这样的错误。

在进行提权之后,就能绑定这个端口号了。

一般情况下,[1,1023]是系统内定的端口号,一般都有固定的应用层协议使用,http:80, https:443, mysql:3306,这种端口号一般就别在使用了。可以考虑8000以上的。


修改一下UdpServer,使用命令行,以./main port的方式执行程序。

#include "UdpServer.hpp"
#include <memory>int main(int argc, char *argv[])
{if (argc != 2){std::cout << "fail!" << std::endl;return 1;}uint16_t port = std::atoi(argv[1]);std::unique_ptr<UdpServer> udp(new UdpServer(port));udp->Init();udp->Run();return 0;
}


现在简单实现一个客户端,来完成通信。

客户端需要bind吗?服务端的bind部分写了一大堆,客户端也需要绑定,服务器要找到客户端,所以也需要bind,只不过不需要用户显示的bind。bind的过程一般会有OS自由随机选择。

一个端口号只能被一个进程bind,对server是如此,对client也是如此。其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以。系统什么时候bind呢?首次发送数据的时候。


#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>using namespace std;int main(int argc, char *argv[])
{if (argc != 3){cout << "fail" << endl;return 1;}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &serv.sin_addr.s_addr);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socket fail" << endl;return 1;}string msg;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, msg);sendto(sockfd, msg.c_str(), msg.size(), 0, (const sockaddr*)&serv, sizeof(serv));struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom (sockfd, buffer, 1023, 0, (struct sockaddr*)&serv, &len);if (s > 0){buffer[s] = 0;cout << buffer << endl;}}close(sockfd);return 0;
}

这就是一个简单的客户端,在运行服务端和客户端的时候,就能完成两者之间的通信了。


#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include "log1.hpp"std::string defaultip = "0.0.0.0";Log log;#define SIZE 1024class UdpServer
{
public:UdpServer(uint16_t port, std::string ip = defaultip):_port(port),_ip(ip){}void Init(){// 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "sockfd successful");// 2.绑定端口号struct sockaddr_in serv;bzero(&serv, sizeof(serv)); // 初始化addrserv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));if (bd < 0){log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "bind successful");}void Run(){IsConnect = true;char inbuf[SIZE];while (IsConnect){struct sockaddr_in client;bzero(&client, sizeof(client));socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, &len);if (n < 0){log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);continue;}inbuf[n] = 0; std::string info = inbuf;std::string echo_string = "server echo# " + info;std::cout << echo_string << std::endl;sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){close(sockfd);}
public:uint16_t _port;std::string _ip;bool IsConnect;int sockfd;
};
#include "UdpServer.hpp"
#include <memory>int main(int argc, char *argv[])
{if (argc != 2){std::cout << "fail!" << std::endl;return 1;}uint16_t port = std::atoi(argv[1]);std::unique_ptr<UdpServer> udp(new UdpServer(port));udp->Init();udp->Run();return 0;
}

服务端和客户端可以直接

以这样的方式运行,

./UdpClient ip port。

通过这个选项可以将远程服务器中的文件发送到本地。

通过rz可以将本地文件发送到远程服务器。现在我使用两个不同的服务器,来运行客户端。上传之后是没有x权限的,使用chmod +x 文件名 可以获得x权限。

然后运行程序,是可以通信的。

所以网络通信最终就是这样子的,只不过可以在进行适当的结耦。


当然udp不仅仅可以只进行通信,如果客户端发送的是指令,我们接收到这个指令的时候可以去处理这个指令。

通过popen函数。

用于创建一个由调用进程执行命令并建立到该命令的标准输入或标准输出的管道。
FILE *popen(const char *command, const char *mode);
  • command 参数是一个字符串,表示要执行的命令。这个字符串会被传递给 shell 进行解释。
  • mode 参数是一个字符串,表示使用的管道的模式。 "r"(只读模式)和 "w"(只写模式)。

popen 返回一个指向 FILE 结构的指针,该结构描述了与新进程的连接。可以使用返回的文件指针进行读取或写入,就像处理常规文件一样。

std::string Command(const std::string &cmd)
{FILE* fp = popen(cmd.c_str(), "r");if (fp == nullptr){perror("popen");return "error";}std::string ret;char buf[2048];while (true){char *tail = fgets(buf, sizeof(buf), fp);if (tail == nullptr){break;}ret += buf;}return ret;
}

当然, 在处理客户端的指令之前,要进行检查,避免出现客户端执行rm -rf等之类的指令。


上述通信的源代码

// UdpServer.hpp
#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <functional>
#include "log1.hpp"// 在Run中带一个参数,这个参数是客户端需要做的事情。可以用function进行封装。然后通过传入的参数来改变客户端需要做的事情。
using func_t = std::function<std::string(const std::string&)>;std::string defaultip = "0.0.0.0";Log log;#define SIZE 1024class UdpServer
{
public:UdpServer(uint16_t port, std::string ip = defaultip):_port(port),_ip(ip){}void Init(){// 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "sockfd successful");// 2.绑定端口号struct sockaddr_in serv;bzero(&serv, sizeof(serv)); // 初始化addrserv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));if (bd < 0){log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "bind successful");}void Run(func_t func){IsConnect = true;char inbuf[SIZE];while (IsConnect){struct sockaddr_in client;bzero(&client, sizeof(client));socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, &len);if (n < 0){log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);continue;}inbuf[n] = 0; std::string info = inbuf;std::string echo_string = func(info);sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){close(sockfd);}
public:uint16_t _port;std::string _ip;bool IsConnect;int sockfd;
};
// Main.cc
#include "UdpServer.hpp"
#include <cstdio>
#include <memory>std::string Handler(const std::string &msg)
{std::string res = "Server get a message : ";res += msg;std::cout << res << std::endl;return res;
}std::string Command(const std::string &cmd)
{FILE* fp = popen(cmd.c_str(), "r");if (fp == nullptr){perror("popen");return "error";}std::string ret;char buf[2048];while (true){char *tail = fgets(buf, sizeof(buf), fp);if (tail == nullptr){break;}ret += buf;}return ret;
}int main(int argc, char *argv[])
{if (argc != 2){std::cout << "fail!" << std::endl;return 1;}uint16_t port = std::atoi(argv[1]);std::unique_ptr<UdpServer> udp(new UdpServer(port));udp->Init();udp->Run(Command);return 0;
}
// UdpClient.cc
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>using namespace std;int main(int argc, char *argv[])
{if (argc != 3){cout << "fail" << endl;return 1;}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &serv.sin_addr.s_addr);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socket fail" << endl;return 1;}string msg;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, msg);sendto(sockfd, msg.c_str(), msg.size(), 0, (const sockaddr*)&serv, sizeof(serv));struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom (sockfd, buffer, 1023, 0, (struct sockaddr*)&serv, &len);if (s > 0){buffer[s] = 0;cout << buffer << endl;}}close(sockfd);return 0;
}
.PHONY:all
all:UdpServer UdpClient
UdpServer:Main.ccg++ -o $@ $^ -std=c++11 -lpthreadUdpClient:UdpClient.ccg++ -o $@ $^ -std=c++11 -lpthread.PHONY:clean
clean:rm UdpClient UdpServer

使用Udp做一个聊天

简单的对UdpServer.hpp中的代码进行了一个更改。

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <functional>
#include <unordered_map>
#include <netinet/in.h>
#include "log1.hpp"// 在Run中带一个参数,这个参数是客户端需要做的事情。可以用function进行封装。然后通过传入的参数来改变客户端需要做的事情。
using func_t = std::function<void(const std::string&, std::string&, uint16_t&)>;std::string defaultip = "0.0.0.0";Log log;#define SIZE 1024class UdpServer
{
public:
UdpServer(uint16_t port, std::string ip = defaultip)
:_port(port)
,_ip(ip)
{}void Init()
{// 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "sockfd successful");// 2.绑定端口号struct sockaddr_in serv;bzero(&serv, sizeof(serv)); // 初始化addrserv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));if (bd < 0){log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "bind successful");
}void CheckOnlineUser(const struct sockaddr_in &client,std::string& ip, uint16_t& port)
{auto iter = online_user.find(ip);if (iter == online_user.end()){online_user.insert({ip, client});std::cout << "[" + ip + ":" + std::to_string(port) + "]#" << "add new client" << std::endl;}
}std::string func(std::string info, std::string ip, uint16_t port)
{std::string echo_string = "[" + ip + ":" + std::to_string(port) + "]#" + info;return echo_string;
}void BroadCast(std::string &info, std::string &ip, uint16_t &port)
{for (auto &it : online_user){sendto(sockfd, info.c_str(), info.size(), 0, (const struct sockaddr*)&it.second, sizeof(it.second));}
}void Run()
{IsConnect = true;char inbuf[SIZE];while (IsConnect){struct sockaddr_in client;bzero(&client, sizeof(client));socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, &len);if (n < 0){log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);continue;}inbuf[n] = 0; std::string client_ip = inet_ntoa(client.sin_addr);// 获取用户的ipuint16_t client_port = ntohs(client.sin_port);// 获取用户的端口号CheckOnlineUser(client,client_ip, client_port);// 检查在线的用户,若有新上线的,会进行提醒std::string info = inbuf;// std::string echo_string = func(info);std::string echo_string = func(info, client_ip, client_port);// 将信息拼接为字符串BroadCast(echo_string,client_ip, client_port);// 将这条信息转发给所有人// sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){close(sockfd);}
public:uint16_t _port;std::string _ip;bool IsConnect;int sockfd;std::unordered_map<std::string, struct sockaddr_in> online_user;// 用来存储所有客户端的IP和sockaddr
};


inet_ntoa 是一个用于将 IPv4 地址从二进制表示转换为点分十进制字符串表示的函数。它通常在网络编程中使用,特别是在处理套接字编程时。

因为客户端是单线程的原因,每次循环收到一条信息后,在getline中会被阻塞,这就导致服务端转发消息的时候,不能及时的将所有的信息转发出来。


下面将客户端改为多线程。

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <mutex>using namespace std;pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;struct ThreadData
{struct sockaddr_in serv;int sockfd;
};void *recv_msg(void *args)
{// OpenTerminal();ThreadData *td = static_cast<ThreadData *>(args);char buffer[1024];while (true){memset(buffer, 0, sizeof(buffer));struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;cerr << buffer << endl;}}
}void *send_msg(void *args)
{ThreadData *td = static_cast<ThreadData *>(args);string message;socklen_t len = sizeof(td->serv);sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->serv), len);while (true){cout << "Please Enter@ ";getline(cin, message);sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->serv), len);}
}int main(int argc, char *argv[])
{if (argc != 3){cout << "fail" << endl;return 1;}struct ThreadData td;std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);bzero(&td.serv, sizeof(td.serv));td.serv.sin_family = AF_INET;td.serv.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &td.serv.sin_addr.s_addr);td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd < 0){cout << "socket fail" << endl;return 1;}pthread_t client_send, client_recv;pthread_create(&client_send, nullptr, send_msg, &td);pthread_create(&client_recv, nullptr, recv_msg, &td);pthread_join(client_send, nullptr);pthread_join(client_recv, nullptr);close(td.sockfd);return 0;
}

这样就可以实时的将所有用户发送的信息转发到屏幕上了,类似于群聊。

TcpServer

TCP协议跟 UDP协议有点不同。

在街上,会碰到一些饭店门口站的有专门拉客的人,如果有客人要去他们饭店吃饭,他会把你领到饭店里面,然后进去之后让其他人接待你,然后拉客的人继续去外面拉客。而TCP中的listen就是拉客的人。它负责监听每一个要连接服务端的人。而accept就是服务员,你要向客户端发送什么信息都是通过accept返回的文件描述符来进行通信的。

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log1.hpp"Log lg;const std::string defaultip = "0.0.0.0";
const int backlog = 5;class TcpServer
{
public:TcpServer(uint16_t port, const std::string ip = defaultip, int lsfd = -1):_port(port),_ip(ip),_listensockfd(lsfd){}void Init(){_listensockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听socket的文件描述符if (_listensockfd < 0){lg(Fatal, "socket fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "socket successful");struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);if (bind(_listensockfd, (const struct sockaddr*)&serv, sizeof(serv)) < 0) // 注册{lg(Fatal, "bind fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "bind successful");if (listen(_listensockfd, backlog) < 0) // 监听{lg(Fatal, "listen fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "listen successful");   }void Service(){char buf[4096];while (true){ssize_t n = read(_sockfd, buf, 4096);if (n > 0){buf[n] = 0;std::cout << buf << std::endl;}std::string res = "Server get a message : ";res += buf;write(_sockfd, res.c_str(), res.size());}}void Start(){for (;;){struct sockaddr_in client;socklen_t len = sizeof(client);_sockfd = accept(_listensockfd, (struct sockaddr*)&client, &len); // 创建文件描述符,因为tcp是有连接,面向字节流的,可以通过accept返回值使用read和write来完成通信if (_sockfd < 0){lg(Fatal, "accept fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "accept successful");Service();}}~TcpServer(){close(_sockfd);close(_listensockfd);}private:uint16_t _port;std::string _ip;int _listensockfd;int _sockfd;
};
#include "TcpServer.hpp"
#include <memory>int main(int argc, char *argv[])
{if (argc != 2){std::cout << "port" << std::endl;exit(-1);}uint16_t port = std::atoi(argv[1]);std::unique_ptr<TcpServer> tcp(new TcpServer(port));tcp->Init();tcp->Start();return 0;
}
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>int main(int argc, char *argv[])
{if (argc != 3){std::cout << "client fail" << std::endl;exit(-1);}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;return 1;}struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &serv.sin_addr.s_addr);// tcp客户端要不要bind? 要不要显示的bind?// 客户端发起connect的时候,进行自动随机bindsocklen_t len = sizeof(serv);int n = connect(sockfd, (const struct sockaddr*)&serv, len);if (n < 0){std::cerr << "connect fail" << std::endl;exit(-1);}// 连接成功就可以发送信息了。std::string msg;while (true){std::cout << "Please Enter# ";std::getline(std::cin, msg);write(sockfd, msg.c_str(), msg.size());char inbuf[4096];int n = read(sockfd, inbuf, sizeof(inbuf));if (n > 0){inbuf[n] = 0;std::cout << inbuf << std::endl;}}close(sockfd);return 0;
}

这样就能完成通信了。

下面可以将代码进行一些细节上的处理。


tcp在通信的时候,如果客户端推出了,服务端读取的时候就会读取到0.

可以做一下修改。

当前我们代码开两个客户端的时候,一端发送信息,后连的客户端也发送信息,会阻塞。这是因为目前我们的通信代码是单进程版的通信。一个客户连上去了,另一个客户需要等待。


改为多进程版,这里使用了孙子进程,是一个很妙的想法。

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include "log1.hpp"Log lg;const std::string defaultip = "0.0.0.0";
const int backlog = 5;class TcpServer
{
public:TcpServer(uint16_t port, const std::string ip = defaultip, int lsfd = -1):_port(port),_ip(ip),_listensockfd(lsfd){}void Init(){_listensockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听socket的文件描述符if (_listensockfd < 0){lg(Fatal, "socket fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "socket successful");struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);if (bind(_listensockfd, (const struct sockaddr*)&serv, sizeof(serv)) < 0) // 注册{lg(Fatal, "bind fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "bind successful");if (listen(_listensockfd, backlog) < 0) // 监听{lg(Fatal, "listen fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "listen successful");   }void Service(){char buf[4096];while (true){ssize_t n = read(_sockfd, buf, 4096);if (n > 0){buf[n] = 0;std::cout << buf << std::endl;}else if (n == 0) // 通信过程中,客户端突然关闭{lg(Info, "server close sockfd: %d", _sockfd);break;}else {lg(Warning, "read error, sockfd: %d", _sockfd);break;}std::string res = "Server get a message : ";res += buf;write(_sockfd, res.c_str(), res.size());}}void Start(){for (;;){struct sockaddr_in client;socklen_t len = sizeof(client);_sockfd = accept(_listensockfd, (struct sockaddr*)&client, &len); // 创建文件描述符,因为tcp是有连接,面向字节流的,可以通过accept返回值使用read和write来完成通信if (_sockfd < 0){lg(Fatal, "accept fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "accept successful");// 单进程// Service(); // close(_sockfd);// 多进程pid_t id = fork();if (id == 0){// childclose(_listensockfd);if (fork() > 0) exit(0); // 让孙子进程来处理,这个时候waitpid直接返回,因为父进程挂了,孙子进程会被系统领养。Service(); // 由子进程做服务close(_sockfd);exit(0);}// fatherclose(_sockfd); // 由子进程完成一些任务,子进程会拷贝父进程的文件描述符表,而父进程后续也不对文件进行操作,所以可直接将sockfd关闭pid_t rid = waitpid(id, nullptr, 0);(void)rid;}}~TcpServer(){close(_listensockfd);}private:uint16_t _port;std::string _ip;int _listensockfd;int _sockfd;
};

来了一个新连接,就创建一个进程,这样进程会变得越来越多。但是创建进程的成本是非常高的。


多线程版本

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include "log1.hpp"Log lg;const std::string defaultip = "0.0.0.0";
const int backlog = 5;class TcpServer;class ThreadData
{
public:ThreadData(TcpServer *t):ts(t){}~ThreadData(){}
public:TcpServer *ts;
};class TcpServer
{
public:TcpServer(uint16_t port, const std::string ip = defaultip, int lsfd = -1):_port(port),_ip(ip),_listensockfd(lsfd){}void Init(){_listensockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听socket的文件描述符if (_listensockfd < 0){lg(Fatal, "socket fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "socket successful");struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);if (bind(_listensockfd, (const struct sockaddr*)&serv, sizeof(serv)) < 0) // 注册{lg(Fatal, "bind fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "bind successful");if (listen(_listensockfd, backlog) < 0) // 监听{lg(Fatal, "listen fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "listen successful");   }void Service(){char buf[4096];while (true){ssize_t n = read(_sockfd, buf, 4096);if (n > 0){buf[n] = 0;std::cout << buf << std::endl;}else if (n == 0) // 通信过程中,客户端突然关闭{lg(Info, "server close sockfd: %d", _sockfd);break;}else {lg(Warning, "read error, sockfd: %d", _sockfd);break;}std::string res = "Server get a message : ";res += buf;write(_sockfd, res.c_str(), res.size());}}static void *Routinue(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData*>(args);td->ts->Service();}void Start(){for (;;){struct sockaddr_in client;socklen_t len = sizeof(client);_sockfd = accept(_listensockfd, (struct sockaddr*)&client, &len); // 创建文件描述符,因为tcp是有连接,面向字节流的,可以通过accept返回值使用read和write来完成通信if (_sockfd < 0){lg(Fatal, "accept fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "accept successful");// 1.单进程// Service(); // close(_sockfd);// 2.多进程// pid_t id = fork();// if (id == 0)// {//     // child//     close(_listensockfd);//     if (fork() > 0) exit(0); // 让孙子进程来处理,这个时候waitpid直接返回,因为父进程挂了,孙子进程会被系统领养。//     Service(); // 由子进程做服务//     close(_sockfd);//     exit(0);// }// // father// close(_sockfd); // 由子进程完成一些任务,子进程会拷贝父进程的文件描述符表,而父进程后续也不对文件进行操作,所以可直接将sockfd关闭// pid_t rid = waitpid(id, nullptr, 0);// (void)rid;// 3.多线程ThreadData* td = new ThreadData(this);pthread_t tid;pthread_create(&tid, nullptr, Routinue, td);}}~TcpServer(){close(_listensockfd);}private:uint16_t _port;std::string _ip;int _listensockfd;int _sockfd;
};

每个客户端到来的时候都需要创建线程,那么,可不可以在线程没有到的时候,提前准备好线程,为可能会连接的客户做准备?


#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include "ThreadPool.hpp"
#include "log1.hpp"
#include "Task.hpp"Log lg;const std::string defaultip = "0.0.0.0";
const int backlog = 5;class TcpServer;class ThreadData
{
public:ThreadData(TcpServer *t):ts(t){}~ThreadData(){}
public:TcpServer *ts;
};class TcpServer
{
public:TcpServer(uint16_t port, const std::string ip = defaultip, int lsfd = -1):_port(port),_ip(ip),_listensockfd(lsfd){}void Init(){_listensockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听socket的文件描述符if (_listensockfd < 0){lg(Fatal, "socket fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "socket successful");struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);if (bind(_listensockfd, (const struct sockaddr*)&serv, sizeof(serv)) < 0) // 注册{lg(Fatal, "bind fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "bind successful");if (listen(_listensockfd, backlog) < 0) // 监听{lg(Fatal, "listen fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "listen successful");   }void Service(){char buf[4096];while (true){ssize_t n = read(_sockfd, buf, 4096);if (n > 0){buf[n] = 0;std::cout << buf << std::endl;}else if (n == 0) // 通信过程中,客户端突然关闭{lg(Info, "server close sockfd: %d", _sockfd);break;}else {lg(Warning, "read error, sockfd: %d", _sockfd);break;}std::string res = "Server get a message : ";res += buf;write(_sockfd, res.c_str(), res.size());}}static void *Routinue(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData*>(args);td->ts->Service();}void Start(){ThreadPool<Task>::GetInstance()->Start();for (;;){struct sockaddr_in client;socklen_t len = sizeof(client);_sockfd = accept(_listensockfd, (struct sockaddr*)&client, &len); // 创建文件描述符,因为tcp是有连接,面向字节流的,可以通过accept返回值使用read和write来完成通信if (_sockfd < 0){lg(Fatal, "accept fail, error code: %d , error string: %s", errno, strerror(errno));exit(-1);}lg(Info, "accept successful");// 1.单进程// Service(); // close(_sockfd);// 2.多进程// pid_t id = fork();// if (id == 0)// {//     // child//     close(_listensockfd);//     if (fork() > 0) exit(0); // 让孙子进程来处理,这个时候waitpid直接返回,因为父进程挂了,孙子进程会被系统领养。//     Service(); // 由子进程做服务//     close(_sockfd);//     exit(0);// }// // father// close(_sockfd); // 由子进程完成一些任务,子进程会拷贝父进程的文件描述符表,而父进程后续也不对文件进行操作,所以可直接将sockfd关闭// pid_t rid = waitpid(id, nullptr, 0);// (void)rid;// 3.多线程// ThreadData* td = new ThreadData(this);// pthread_t tid;// pthread_create(&tid, nullptr, Routinue, td);// 4. 线程池版Task task(_sockfd);ThreadPool<Task>::GetInstance()->Push(task);}}~TcpServer(){close(_listensockfd);}private:uint16_t _port;std::string _ip;int _listensockfd;int _sockfd;
};

#pragma once#include <iostream>
#include <string>
#include "log1.hpp"extern Log lg;std::string opers="+-*/%";enum{DivZero=1,ModZero,Unknown
};class Task
{
public:Task(int fd):_sockfd(fd){}Task(){}void run(){char buf[4096];ssize_t n = read(_sockfd, buf, 4096);if (n > 0){buf[n] = 0;std::cout << buf << std::endl;}else if (n == 0) // 通信过程中,客户端突然关闭{lg(Info, "server close sockfd: %d", _sockfd);}else {lg(Warning, "read error, sockfd: %d", _sockfd);}std::string res = "Server get a message : ";res += buf;write(_sockfd, res.c_str(), res.size());close(_sockfd);}void operator ()(){run();}std::string GetTask(){}~Task(){}private:int _sockfd;
};
#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>struct ThreadInfo
{pthread_t tid;std::string name;
};static const int defalutnum = 5;template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(pthread_t tid){for (const auto &ti : threads_){if (ti.tid == tid)return ti.name;}return "None";}public:static void *HandlerTask(void *args){ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsQueueEmpty()){tp->ThreadSleep();}T t = tp->Pop();tp->Unlock();t();}}void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name = "thread-" + std::to_string(i + 1);pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);}}T Pop(){T t = tasks_.front();tasks_.pop();return t;}void Push(const T &t){Lock();tasks_.push(t);Wakeup();Unlock();}static ThreadPool<T> *GetInstance(){if (nullptr == tp_) // ???{pthread_mutex_lock(&lock_);if (nullptr == tp_){std::cout << "log: singleton create done first!" << std::endl;tp_ = new ThreadPool<T>();}pthread_mutex_unlock(&lock_);}return tp_;}private:ThreadPool(int num = defalutnum) : threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}ThreadPool(const ThreadPool<T> &) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:std::vector<ThreadInfo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;static ThreadPool<T> *tp_;static pthread_mutex_t lock_;
};template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;
#include "TcpServer.hpp"
#include <memory>int main(int argc, char *argv[])
{if (argc != 2){std::cout << "port" << std::endl;exit(-1);}uint16_t port = std::atoi(argv[1]);std::unique_ptr<TcpServer> tcp(new TcpServer(port));tcp->Init();tcp->Start();return 0;
}
.PHONY:all
all:TcpServer TcpClient
TcpServer:Main.ccg++ -o $@ $^ -std=c++11 -lpthreadTcpClient:TcpClient.ccg++ -o $@ $^ -std=c++11 -lpthread.PHONY:clean
clean:rm TcpClient TcpServer

相关文章:

Linux---网络套接字

端口号 端口号 端口号是一个2字节16位的整数; 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理; IP地址 端口号能够标识网络上的某一台主机的某一个进程; 一个端口号只能被一个进程占用 在公网上&#xff0c;IP地址能表示唯一的一台主机&…...

前端vue 数字 字符串 丢失精度问题

1.问题 后端返回的数据 是这样的 一个字符串类型的数据 前端要想显示这个 肯定需要使用Json.parse() 转换一下 但是 目前有一个问题 转换的确可以 showId:1206381711026823172 有一个这样的字段 转换了以后 发现 字段成了1206381711026823200 精度直接丢了 原本的数据…...

智能汽车行业产业研究报告:4D成像毫米波雷达—自动驾驶最佳辅助

今天分享的是智能汽车系列深度研究报告&#xff1a;《智能汽车行业产业研究报告&#xff1a;4D成像毫米波雷达—自动驾驶最佳辅助》。 &#xff08;报告出品方&#xff1a;开源证券&#xff09; 报告共计&#xff1a;43页 视觉感知最佳辅助——4D 成像毫米波雷达 感知是自动…...

docker 3.1 镜像

docker 3.1 镜像命令 拉取镜像 docker pull debian #从 Docker Hub 拉取名为 debian 的镜像docker pull hello-world #从 Docker Hub 拉入名为 hello-world 的镜像‍ 运行镜像/容器 docker run hello-world ‍ 查看本地所有的镜像 docker images​​ 容器生成镜像…...

如何在极低成本硬件上落地人工智能算法 —— 分布式AI

一、背景 分布式AI的发展前景非常广阔&#xff0c;随着5G、6G等高速网络通信技术的普及和边缘计算能力的提升&#xff0c;以及AI算法和硬件的不断优化进步&#xff0c;分布式AI将在多个领域展现出强大的应用潜力和市场价值&#xff1a; 1. **物联网&#xff08;IoT&#xff0…...

机器学习:ROC曲线笔记

ROC曲线&#xff08;Receiver Operating Characteristic Curve&#xff09;是一种用于评估二分类模型性能的图形化工具&#xff0c;主要用于展示在不同阈值&#xff08;Threshold&#xff09;下模型的真阳性率&#xff08;True Positive Rate&#xff0c;TPR&#xff09;和假阳…...

【lesson54】线程互斥

文章目录 线程互斥 线程互斥 互斥量mutex 大部分情况&#xff0c;线程使用的数据都是局部变量&#xff0c;变量的地址空间在线程栈空间内&#xff0c;这种情况&#xff0c;变量归属单个线程&#xff0c;其他线程无法获得这种变量。但有时候&#xff0c;很多变量都需要在线程间…...

Android14音频进阶:MediaPlayerService如何启动AudioTrack 上篇(五十五)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只…...

K8sGPT 的使用

K8sGPT 介绍 k8sgpt 是一个扫描 Kubernetes 集群、诊断和分类问题的工具。它将 SRE 经验编入其分析器中&#xff0c;并帮助提取最相关的信息&#xff0c;通过人工智能来丰富它。它还可以与 OpenAI、Azure、Cohere、Amazon Bedrock 和本地模型结合使用。 K8sGPT Github 地址 …...

《CSS 简易速速上手小册》第4章:视觉美学(2024 最新版)

文章目录 4.1 颜色理论在 CSS 设计中的应用&#xff1a;网页的调色盘4.1.1 基础知识4.1.2 重点案例&#xff1a;创建一个具有情感设计的登录页面4.1.3 拓展案例 1&#xff1a;使用颜色增强信息的可视化表示4.1.4 拓展案例 2&#xff1a;利用颜色创建网站的品牌身份 4.2 字体与文…...

设计模式浅析

一、设计模式的使用场景 设计模式&#xff08;Design Patterns&#xff09;是在软件开发中经过验证的最佳实践&#xff0c;用于解决常见的设计问题。它们提供了一种可复用的解决方案&#xff0c;可以帮助开发人员提高代码质量、可维护性和可重用性。设计模式的采用通常在以下情…...

Linux环境中的git

目录 1.要使用git&#xff0c;首先要安装git 2.首次使用git需要做的操作 3.git操作 1.要使用git&#xff0c;首先要安装git 指令&#xff1a;sudo yum install -y git 2.首次使用git需要做的操作 在gitee网页&#xff0c;在你的仓库中找到&#xff1a; 先将下面两行代码分别…...

单测的思路

文章目录 单测的定义方法的单测几种生成工具的对比生成步骤 接口的单测场景的单测总结参考 单测的定义 单元测试&#xff08;Unit Testing&#xff09;是一种软件开发中的测试方法&#xff0c;它的主要目的是确保软件中的最小可测试单元&#xff08;通常是函数、方法或类&…...

Linux内核与驱动面试经典“小”问题集锦(6)

接前一篇文章&#xff1a;Linux内核与驱动面试经典“小”问题集锦&#xff08;5&#xff09; 问题8 问&#xff1a;如何判断一个数是否是2的幂次&#xff08;假设最多32位&#xff09;&#xff1f; 备注&#xff1a;此问题是笔者年前参加小米面试时遇到的一个问题&#xff0c…...

【zabbix】(四)-钉钉告警企业微信配置

前提条件&#xff1a; 已经安装了Python3环境&#xff08;脚本需要requests模块&#xff09;。Centos7.x自带Python2&#xff08;不含requests模块&#xff09; 钉钉告警配置 一 安装Python3 参考该优秀文档部署 查看Python的模块&#xff1a;pip list / pip3 list 报错 …...

python-自动化篇-办公-一键将word中的表格提取到excel文件中

文章目录 代码 工作中&#xff0c;经常需要将Word文档中的表格粘贴到Excel文件中&#xff0c;以便汇总及分析。一个一个复制粘贴&#xff0c;非常不方便&#xff0c;还是Python自动化操作&#xff0c;省心省力。要求如下图所示&#xff0c;即将word中的所有表格&#xff0c;转存…...

C#,数值计算,矩阵的行列式(Determinant)、伴随矩阵(Adjoint)与逆矩阵(Inverse)的算法与源代码

本文发布矩阵&#xff08;Matrix&#xff09;的一些初级算法。 一、矩阵的行列式&#xff08;Determinant&#xff09; 矩阵行列式是指矩阵的全部元素构成的行列式&#xff0c;设A(a)是数域P上的一个n阶矩阵&#xff0c;则所有A(a)中的元素组成的行列式称为矩阵A的行列式&…...

人工智能|推荐系统——基于tensorflow的个性化电影推荐系统实战(有前端)

代码下载&#xff1a; 基于tensorflow的个性化电影推荐系统实战(有前端).zip资源-CSDN文库 项目简介&#xff1a; dl_re_web : Web 项目的文件夹re_sys&#xff1a; Web app model&#xff1a;百度云下载之后&#xff0c;把model放到该文件夹下recommend&#xff1a; 网络模型相…...

Hive SQL编译成MapReduce任务的过程

目录 一、架构及组件介绍 1.1 Hive底层架构 1.2 Hive组件 1.3 Hive与Hadoop交互过程 二、Hive SQL 编译成MR任务的流程 2.1 HQL转换为MR源码整体流程介绍 2.2 程序入口—CliDriver 2.3 HQL编译成MR任务的详细过程—Driver 2.3.1 将HQL语句转换成AST抽象语法树 词法、语…...

【C++】快速上手map、multimap、set、multiset

文章目录 一、前言二、set / multiset1. 常见应用2. 核心操作 三、map / multimap1. 常见应用2. 核心操作 一、前言 S T L STL STL 中的关联式容器分为树型结构和哈希结构&#xff0c;树型结构主要有四种&#xff1a; s e t set set、 m u l t i s e t multiset multiset、 m a…...

【分享】图解ADS+JLINK调试ARM

文章是对LPC2148而写的&#xff0c;但是对三星的44B0芯片同样适用&#xff0c;只需要在选择时将相应的CPU选择的S3C44B0就可以了。 JLINK在ADS下调试心得 前两天一个客户用jlink在ADS下调试LPC2148总报错&#xff0c;这个错误我之前在调试LPC2200的时候也碰到过&#xff0c;后…...

反无人机系统技术分析,无人机反制技术理论基础,无人机技术详解

近年来&#xff0c;经过大疆、parrot、3d robotics等公司不断的努力&#xff0c;具有强大功能的消费级无人机价格不断降低&#xff0c;操作简便性不断提高&#xff0c;无人机正快速地从尖端的军用设备转入大众市场&#xff0c;成为普通民众手中的玩具。 然而&#xff0c;随着消…...

Kotlin和Java 单例模式

Java 和Kotlin的单例模式其实很像&#xff0c;只是Kotlin一部分单例可以用对象类和委托lazy来实现 Java /*** 懒汉式&#xff0c;线程不安全*/ class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (insta…...

软考 系统分析师系列知识点之信息系统战略规划方法(9)

接前一篇文章&#xff1a;软考 系统分析师系列知识点之信息系统战略规划方法&#xff08;8&#xff09; 所属章节&#xff1a; 第7章. 企业信息化战略与实施 第4节. 信息系统战略规划方法 7.4.5 信息工程方法 信息工程&#xff08;Information Engineering&#xff0c;IE&…...

政安晨:示例演绎TensorFlow的官方指南(一){基础知识}

为什么要示例演绎&#xff1f; 既然有了官方指南&#xff0c;咱们在官方指南上看看就可以了&#xff0c;为什么还要写示例演绎的文章呢&#xff1f; 其实对于初步了解TensorFlow的小伙伴们而言&#xff0c;示例演绎才是最重要的。 官方文档已经假定了您已经具备了相当合适的…...

node - 与数据库交互

在Web开发中,与数据库交互是常见的需求,用于持久化存储、检索和操作数据。不同的后端技术和数据库类型(如关系型数据库和非关系型数据库)有着不同的交互方式。下面介绍几种常见的数据库交互方法。 关系型数据库 关系型数据库(如MySQL、PostgreSQL、SQLite)使用结构化查…...

速盾:2024年cdn在5g时代重要吗

在2024年&#xff0c;随着5G技术的普及与应用&#xff0c;内容分发网络&#xff08;Content Delivery Network&#xff0c;CDN&#xff09;在数字化时代中的重要性将进一步巩固和扩大。CDN是一种用于快速、高效地分发网络内容的基础设施&#xff0c;它通过将内容部署在全球各地…...

微信小程序(四十一)wechat-http的使用

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.模块下载 2.模块的使用 在终端输入npm install wechat-http 没有安装成功vue的先看之前的一篇 微信小程序&#xff08;二十&#xff09;Vant组件库的配置- 如果按以上的成功配置出现如下报错先输入以下语句 …...

所有设计模式大全及学习链接

文章目录 创建型设计模式结构型设计模式行为型设计模式 创建型设计模式 一种创建对象的设计模式&#xff0c;它们提供了一种灵活的方式来创建对象&#xff0c;同时隐藏了对象的创建细节。以下是常见的创建型设计模式&#xff1a; 工厂方法模式&#xff08;Factory Method Patte…...

【Java程序设计】【C00264】基于Springboot的原创歌曲分享平台(有论文)

基于Springboot的原创歌曲分享平台&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的原创歌曲分享平台 本系统分为平台功能模块、管理员功能模块以及用户功能模块。 平台功能模块&#xff1a;在平台首页可以查看首…...

2024年,要特别注意这两个方位

家居风水对每个家庭都非常重要&#xff0c;可在无形中影响到人们的事业、财富以及健康运势。俗话说&#xff1a;“风水轮流转”&#xff0c;2024年为甲辰龙年&#xff0c;斗转星移、九宫飞星将改变宫位&#xff0c;新一年的磁场即将启动&#xff0c;方位的吉凶也会重新变动&…...

【Chrono Engine学习总结】5-sensor-5.1-sensor基础并创建一个lidar

由于Chrono的官方教程在一些细节方面解释的并不清楚&#xff0c;自己做了一些尝试&#xff0c;做学习总结。 1、Sensor模块 Sensor模块是附加模块&#xff0c;需要单独安装。参考&#xff1a;【Chrono Engine学习总结】1-安装配置与程序运行 Sensor Module Tutorial Sensor …...

springboot/ssm学生信息管理系统Java学生在线选课考试管理系统

springboot/ssm学生信息管理系统Java学生在线选课考试管理系统 开发语言&#xff1a;Java 框架&#xff1a;springboot&#xff08;可改ssm&#xff09; vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.…...

three.js 箭头ArrowHelper的实践应用

效果&#xff1a; 代码&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red"></div></div></el-main></…...

力扣hot2--哈希

推荐博客&#xff1a; for(auto i : v)遍历容器元素_for auto 遍历-CSDN博客 字母异位词都有一个特点&#xff1a;也就是对这个词排序之后结果会相同。所以将排序之后的string作为key&#xff0c;将排序之后能变成key的单词组vector<string>作为value。 class Solution …...

【正在更新】从零开始认识语音识别:DNN-HMM混合系统语音识别(ASR)原理

摘要 | Abstract 这是一篇对语音识别中的一种热门技术——DNN-HMM混合系统原理的透彻介绍。本文自2月10日开始撰写&#xff0c;计划一星期内写完。 1.前言 | Introduction 近期想深入了解语音识别(ASR)中隐马尔可夫模型(HMM)和深度神经网络-隐马尔可夫(DNN-HMM)混合模型&#…...

thinkphp+vue企业产品展示网站f7enu

本文首先介绍了企业产品展示网站管理技术的发展背景与发展现状&#xff0c;然后遵循软件常规开发流程&#xff0c;首先针对系统选取适用的语言和开发平台&#xff0c;根据需求分析制定模块并设计数据库结构&#xff0c;再根据系统总体功能模块的设计绘制系统的功能模块图&#…...

在Ubuntu22.04上部署ComfyUI

ComfyUI 是 一个基于节点流程的 Stable Diffusion 操作界面&#xff0c;可以通过流程&#xff0c;实现了更加精准的工作流定制和完善的可复现性。每一个模块都有特定的的功能&#xff0c;我们可以通过调整模块连接达到不同的出图效果&#xff0c;特点如下&#xff1a; 1.对显存…...

Springboot+vue的社区养老服务平台(有报告)。Javaee项目,springboot vue前后端分离项目

演示视频&#xff1a; Springbootvue的社区养老服务平台&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的社区养老服务平台&#xff0c;采用M&#xff08;model&…...

计算机设计大赛 深度学习+opencv+python实现车道线检测 - 自动驾驶

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络3.1卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV56 数据集处理7 模型训练8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &am…...

机器学习2---逻辑回归(基础准备)

逻辑回归是基于线性回归是直线分的也可以做多分类 ## 数学基础 import numpy as np np.pi # 三角函数 np.sin() np.cos() np.tan() # 指数 y3**x # 对数 np.log10(10) np.log2(2) np.e np.log(np.e) #ln(e)# 对数运算 # log(AB) log(A) logB np.log(3*4)np.log(3)np.log(4) #…...

JVM体系

JVM是一种虚拟的计算机&#xff0c;它模拟了一个完整的硬件系统&#xff0c;并运行在一个完全隔离的环境中。这意味着JVM可以看作是一个在操作系统之上的计算机系统&#xff0c;与VMware、Virtual Box等虚拟机类似。JVM的设计目标是提供一个安全、可靠、高效且跨平台的运行环境…...

.NET命令行(CLI)常用命令

本文用于记录了.NET软件开发全生命周期各阶段常用的一些CLI命令&#xff0c;用于开发速查。 .NET命令行&#xff08;CLI&#xff09;常用命令 项目创建&#xff08;1&#xff09;查看本机SDK&#xff08;2&#xff09;查看本机可以使用的.NET版本&#xff08;3&#xff09;生成…...

六、Redis之数据持久化及高频面试题

6.1 数据持久化 官网文档地址&#xff1a;https://redis.io/docs/manual/persistence/ Redis提供了主要提供了 2 种不同形式的持久化方式&#xff1a; RDB&#xff08;Redis数据库&#xff09;&#xff1a;RDB 持久性以指定的时间间隔执行数据集的时间点快照。AOF&#xff0…...

爬虫——ajax和selenuim总结

为什么要写这个博客呢&#xff0c;这个代码前面其实都有&#xff0c;就是结束了。明天搞个qq登录&#xff0c;这个就结束了。 当然也会更新小说爬取&#xff0c;和百度翻译&#xff0c;百度小姐姐的爬取&#xff0c;的对比爬取。总结嘛&#xff01;&#xff01;&#xff01;加…...

【Python】单元测试unittest框架

note 使用unittest框架进行单元测试是Python标准库的一部分&#xff0c;提供了编写测试用例、测试套件以及运行测试的能力。测试用例是继承自unittest.TestCase的类。在这个类中&#xff0c;你可以定义一系列的方法来测试不同的行为。每个测试方法都应该以test开头。 文章目录…...

(三十七)大数据实战——Solr服务的部署安装

前言 Solr是一个基于Apache Lucene的开源搜索平台&#xff0c;它提供了强大的全文搜索、分布式搜索和数据分析功能。Solr 可以用于构建高性能的搜索应用程序&#xff0c;支持从海量数据中快速检索和分析信息。Solr 使用倒排索引和先进的搜索算法&#xff0c;可实现快速而准确的…...

在Ubuntu22.04上部署FoooCUS2.1

Fooocus 是一款基于 Gradio的图像生成软件&#xff0c;Fooocus 是对 Stable Diffusion 和 Midjourney 设计的重新思考&#xff1a; 1、从 Stable Diffusion 学习&#xff0c;该软件是离线的、开源的和免费的。 2、从 Midjourney 中学到&#xff0c;不需要手动调整&#xff0c;…...

详解C语言中的野指针和assert断言

目录 1.野指针1.1 野指针成因1.1.1 指针未初始化1.1.2 指针越界访问1.1.3 指针指向的空间释放 1.2 如何规避野指针1.2.1 指针初始化1.2.2 小心指针越界1.2.3 指针变量不再使用时&#xff0c;及时置为NULL&#xff0c;指针使用之前检查1.2.4 避免返回局部变量的地址 2.assert断言…...

Vue源码系列讲解——模板编译篇【四】(文本解析器)

1. 前言 在上篇文章中我们说了&#xff0c;当HTML解析器解析到文本内容时会调用4个钩子函数中的chars函数来创建文本型的AST节点&#xff0c;并且也说了在chars函数中会根据文本内容是否包含变量再细分为创建含有变量的AST节点和不包含变量的AST节点&#xff0c;如下&#xff…...