小松 建设项目 网站,浏览器主页网址推荐,龙岗网站建设公司网络服务,网页和网站有什么关系这份代码利用下面所有知识编写了一个简易聊天室#xff08;基于Linux操作系统#xff09;。虽然字数挺多其实并不复杂#xff0c;这里如果能够看完或许会对你的知识进行一下串联#xff0c;这篇文章比较杂并且网络编程这块知识需要用到系统编程的知识#xff0c;希望能帮助…这份代码利用下面所有知识编写了一个简易聊天室基于Linux操作系统。虽然字数挺多其实并不复杂这里如果能够看完或许会对你的知识进行一下串联这篇文章比较杂并且网络编程这块知识需要用到系统编程的知识希望能帮助到您。
知识汇总
1.IP地址与端口号
我们知道同一台主机的进程间通信有system V共享内存消息队列信号量这些方式而跨主机的进程间通信怎么搞呢使用IP地址与端口号
IP地址用来网络中标识唯一一台主机是一个32位无符号整数常常用192.163.1.1这样点分十进制的字符串形式表示。
端口号用来表示一台主机中的一个进程它是一个16位无符号整数所以端口号最小是0最大是65536。那么端口号如何表示一个进程呢如下图端口号作为数组的下标数组中存放的是进程PID。它相当于一个哈希表根据下标即端口号就可以找到对应的进程。
这里有一个问题为什么不直接用进程PID呢非要多走一步端口号感觉有点多此一举。
我是这样理解的我们使用的应用程序都是有对应的服务器维护的我们作为一个客户端需要和服务器进行数据交互那么就必须明白两个问题一是服务器在哪二是与服务器的哪个进程进行通信。当我们通信之前就必须知道服务器的IP地址与进程PID那么我们怎么知道呢IP地址我们可以视为客户端提前知晓且并不变更那进程PID呢服务器每重新打开一次进程PID会一样吗显然不会那么我怎么找到服务器的对应进程呢这里就陷入了一个死循环。
网络请问您是要和服务器123.123.123.123通信吗
客户端对的。
网络请告诉我你是要和服务器的哪个进程通信呢
客户端不知道啊它的进程每次重新启动进程号都会变更。
网络对不起先生没有进程号我们没法帮您通信。
客户端我不跟服务器通信我怎么知道服务器的进程号。
当然只有ip地址也是可以接收到数据的但是交由哪个进程处理这些数据是什么意思用来干什么的就成了问题。
为了避免这个问题就有了端口号的概念。服务器的相应进程会放到一个固定的端口号上客户端都是提前知晓这个端口号的所以在通信时客户端只需要端口号就可以找到对应进程。这也使得许多端口号约定成俗比如常见的8080端口。
2.主机序列与网络序列
每台计算机的存储顺序不同分为大端存储和小端存储。大端存储就是低字节放到高地址小端存储就是高字节放到低地址。如下图定义一个int num1 可以看到01放到高地址处的是大端存储放到低地址处的是小端存储。
既然有这种主机存储顺序的不同那么在进行网络通信时如果两个终端存储顺序不同那么数据就会被错误解读。为了解决这个问题就定义了一个共同的标准在传输网络数据的时候都以大端存储为标准。 因为客户端发送数据携带的目的ip与目的端口都是网络序列的服务器端要对比数据是给哪个端口所以本地ip和端口必须转为网络序列。
3.多网卡/多IP
这块是关于创建套接字后使用bind函数绑定端口号与ip的一个细节。
云服务器或者一款服务器不要bind一个具体的ip因为服务器可能有多个网卡多个ip地址这些ip都有可能接收指定端口的数据所以需要在服务器启动的时候bind任意一个ip地址这就要求在对sockaddr里面的sin_addr里面的s_addr初始化时使用INADDR_ANY进行初始化。 接口函数
socket:
#include sys/types.h
#include sys/socket.hint socket(int domain, int type, int protocol);socket函数用来创建一个套接字。domain选择协议家族来进行通信ipv4网络通信使用AF_INET,ipv6使用AF_INET6。type是用来选择套接字类型的 SOCK_STREAM就是面向连接可靠的SOCK_DGRAM就是无连接不可靠的。protocol用0即可选择默认合适的协议。socket创建成功会返回一个文件描述符创建失败返回-1。
bind
#include sys/types.h
#include sys/socket.hint bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);bind函数用来绑定本地主机ip与端口号。sockfd就是创建套接字成功返回的文件描述符。 我们可以看到sockaddr是个结构体那这个结构体的成员有哪些呢
sockaddr结构体: __SOCKADDR_COMMON (sa_)就是#define __SOCKADDR_COMMON(sa_prefix) \ sa_family_t sa_prefix##family其实绕来绕去就是sa_family_t sa_family,一个16位短整型变量下面sockaddr_in结构体的第一个成员也大体一样sa_family_t sin_family一个16位短整型变量,用来表示地址类型如AF_INET。char sa_data[14]就是14字节的地址数据。
不过我们在进行网络通信时使用的是sockaddr_in类型的结构体
sockaddr_in结构体 由上图可以看出sockaddr结构体里面的sin_port是一个16位无符号整数in_addr结构体里面有唯一一个成员---32位无符号整数。他们分别代表一个端口号和IP地址。sin_zero结构体就是填充字段可以看到用sockaddr结构体大小减去了sockaddr_in结构体里面的三个成员的大小最后自然sockaddr和sockaddr_in结构体的大小就一样了。这不明摆着是让sockaddr和sockaddr_in适配么。使用时直接取地址然后强转就可以了。
所以得出下面的结论 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结构体指针做为参数; addrlen就是一个无符号整形指明sockaddr结构体大小的。 recvfrom:
#include sys/types.h
#include sys/socket.hssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);利用创建的套接字把接收到的最大为len字节长度的数据放到buf中flags标志位表示是否阻塞接收设为0即可,src_addr指针和addrlen指针分别指向一个输入性参数用来接收发送方的IP地址端口号以及结构体大小。数据成功则返回实际接收到的字符数失败返回-1。 sendto:
#include sys/types.h
#include sys/socket.hssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); 利用创建的套接字发送最大为len字节长度的数据flags标志位表示是否阻塞发送设为0即可,dest_addr指针指向一个sockaddr_in结构体里面有目的ip和目的端口号addrlen为该结构体大小。成功则返回实际传送出去的字符数失败返回-1。
inet_addr:
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.hin_addr_t inet_addr(const char *cp);inet_addr 函数将互联网主机地址 cp 字符串从 IPv4 数字和点表示法转换为按网络字节顺序的二进制数据。
inet_ntoa:
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.hchar *inet_ntoa(struct in_addr in);inet_ntoa 函数将按网络字节顺序给出的互联网主机地址转换为 IPv4 点分十进制表示法的字符串。 字符串以静态分配的缓冲区后续调用将覆盖该缓冲区。不过这里的in_addr是sockaddr_in结构体里面的一个结构体成员这个in_addr结构体里面存放的是一个32位无符号整数IP地址。
htons:
#include arpa/inet.huint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);已知端口号是16位无符号整数ip地址是32位无符号整数。所以这里四个函数就是把主机字节序转换成网络字节序or网络字节序转换成主机字节序IP地址用uint32_t,端口号用uint16_t。
popen #include stdio.hFILE *popen(const char *command, const char *type);int pclose(FILE *stream);command 是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c标志shell 将执行这个命令。 type 只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出如果 type 是 “w” 则文件指针连接到 command 的标准输入。
如果调用成功则返回一个读或者打开文件的指针如果失败返回NULL。
man手册中关于popen函数的解释popen 函数通过创建管道、分叉和调用 shell 来打开进程。 由于管道根据定义是单向的因此类型参数可以指定只有阅读或写作而不是两者兼而有之;生成的流相应地是只读或只写的。
popen 的返回值在所有方面都是正常的标准 I/O 流除了它必须使用 pclose 而不是 fclose3 关闭。 写入这样的流写入命令的标准输入;该命令的标准输出与调用 popen 的进程的标准输出相同除非命令对此进行了更改本身。相反从“打开的”流中读取会读取命令的标准输出并且命令的标准输入与进程的标准输入相同称为 popen。
fopen函数 可以看到popen函数与fopen函数极其相似都是标准I/O库函数且返回值都是一个文件流指针FILE*都需要用close函数关闭。但是fopen函数是用于打开一个文件而popen函数作用是创建管道并创建子进程并利用子进程处理command命令处理结果返回到一个文件。调用popen函数的进程就是父进程。
fgets:文件I/O与标准I/O #include stdio.hchar *fgets(char *s, int size, FILE *stream);fgets 从流中最多读取一个小于size大小的字符并将它们存储到 S 指向的缓冲区中。 读取在 EOF 或换行符后停止。 如果是新的行被读取它被存储到缓冲区中。 终止空字节 0 存储在缓冲区中最后一个字符之后。
s 代表要保存到的内存空间的首地址可以是字符数组名也可以是指向字符数组的字符指针变量名。size 代表的是读取字符串的长度。stream 表示从何种流中读取可以是标准输入流 stdin也可以是文件流即从某个文件中读取。
可以看到fgets函数与gets函数相似但fgets函数更为安全并且可以从文件中读取字符而gets只能从标准输入中获取。fegts还能检查预留存储区的大小保证字符串不会超出预留空间。gets 将一行从 stdin 读取到 s 指向的缓冲区中直到终止换行符或 EOF它用空字节 0 替换它但并不检查缓冲区是否溢出。 文件I/O与标准I/O部分
写到这里有一个小问题为什么stdin可以传入FILE*类型参数stdin是什么?明白的可以自动跳过这里 。
下面主要是文件I/O与标准I/O的知识。。。。
我们知道当打开一个文件时OS会先使用inode编号在磁盘文件系统里面去寻找这个文件找到以后根据文件的属性为其创建一个内核层面的结构体来描述这个文件该结构体里面含有文件的属性信息大小拥有者创建修改时间。当我们上层用户要对文件进行操作时一定是需要使用系统调用函数openwrite,readclose等等依赖操作系统来进行操作这些函数是底层用于文件I/O的。为了提供比底层系统调用更为方便、好用的调用接口设计了标准I/O库函数fopenfwritefreadfclosefflush等等使用时需要包含头文件stdio.h。 对于标准 I/O 库函数来说它们的操作是围绕 FILE 指针进行的当使用标准 I/O 库函数打开或创建一个文件时会返回一个指向 FILE 类型对象的指针使用该 FILE 指针与被打开或创建的文件相关联然后该 FILE 指针就用于后续的标准 I/O 操作使用标准 I/O 库函数进行 I/O 操作所以由此可知FILE 指针的作用相当于文件描述符只不过 FILE 指针用于标准 I/O 库函数中、而文件描述符则用于文件 I/O 系统调用中。
FILE 是一个结构体数据类型它包含了标准 I/O 库函数为管理文件所需要的所有信息包括用于实际 I/O 的文件描述符、指向文件缓冲区的指针、缓冲区的长度、当前缓冲区中的字节数以及出错标志等。FILE 数据结构定义在标准 I/O 库函数头文件 stdio.h 中。
FILE结构体如下图 通过上面两图可以看到stdin其实就是一个结构体FILE* 指针。
在操作系统层面当一个进程被启动时进程会默认打开0,1,2号文件描述符对应标准输入设备文件标准输出设备文件标准错误设备文件这些设备也相当于文件。
在用户开发者层面标准 I/O 库中使用 stdin、stdout、stderr 来表示标准输入、标准输出和标准错误它们都是FILE结构体指针都有一个文件描述符所以我们才可以通过库函数调用系统函数来对文件进行操作。当我们使用fopen函数打开一个文件时返回函数就是FILE*类型指针因为在标准I/O层面无法使用文件描述符进行文件操作。 代码
简介下面的代码包括一个封装好的环形队列作为服务器接受客户端发送消息的容器、只需要传入互斥量指针就自动加锁自动解锁的类、封装好的线程类以及客户端服务器主程序。其实代码逻辑很简单从udp_server.hpp的UdpServer类里面的私有成员变量入手就好。
服务器启动需要绑定一个端口号端口号以命令行参数形式传入。 客户端启动需要在命令行输入服务器ip与端口号 udp_client.cc
#include iostream
using namespace std;
#include sys/types.h
#include sys/socket.h
#include cstdlib
#include cerrno
#include cstring
#include pthread.h//sockaddr_in结构体的头文件,当然也包含一些主机转网络序列的函数比如htons
#include netinet/in.h
#include arpa/inet.h#include error.hppstatic void* rfo(void *args)
{int sock*(static_castint*(args));while(true){//收char buffer[4096];struct sockaddr_in tmp;//输入型参数socklen_t lensizeof(tmp);//要初始化不然没法修改//阻塞式接收int nrecvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)tmp,len);if(n0)//接收服务器数据成功{buffer[n]0;coutbufferendl;}}}//当传入程序参数个数不对时调用这个Usage函数告诉他什么是他妈的惊喜
static void Usage(string proc)
{coutUsage:\n\tproc serverip serverport\nendl;
}// ./udp_client serverip serverport
int main(int argc,char* argv[])
{if(argc!3){Usage(argv[0]);exit(USAGE_ERR);}//保留输入的服务器的IP地址与端口号string serveripargv[1];uint16_t serverportatoi(argv[2]);int sock socket(AF_INET, SOCK_DGRAM, 0);if (sock 0){cerr create socket error strerror(errno) endl;exit(SOCKET_ERR);}//client要不要bind呢要的socket通信的本质[clientip,clientport ::serverip,serverport]//要不要自己bind呢不需要自己bind也不要自己bindOS自动bind-- 客户端的端口号要操作系统随机分配防止客户端出现启动冲突。//创建线程去接收pthread_t tid;pthread_create(tid,nullptr,rfo,(void*)sock);//明确server是谁struct sockaddr_in server;memset((void*)server,0,sizeof(server));server.sin_familyAF_INET;server.sin_porthtons(serverport);//主机序列转网络序列server.sin_addr.s_addrinet_addr(serverip.c_str());//点分十进制字符串ip转成32位无符号整数并转为网络序列这个函数有两个功能while(true){string message;coutplease Enter# ;getline(cin,message);//在首次调用sendto函数时操作系统自动给本程序绑定IP地址和端口号客户端不能自己绑定端口号和ip地址因为端口号和IP地址会变。sendto(sock,message.c_str(),message.size(),0,(const struct sockaddr*)server,sizeof(server));}return 0;
}
udp_server.cc #include iostream
#include udp_server.hpp#include memory
#include cstdiousing namespace ns_server;// 上层的业务处理不关心网络发送只负责信息处理即可
// 客户端输入命令服务器执行命令结果返回给客户端// 业务1字符串全部转大写
string transaction(string request)
{string result;char c;for (auto e : request){if (islower(e)){c toupper(e);result.push_back(c);}else{result.push_back(e);}}return result;
}bool notsecure(string command)
{bool ret false;int pos;pos command.find(rm);if (pos ! string::npos)ret true;pos command.find(while);if (pos ! string::npos)ret true;pos command.find(mv);if (pos ! string::npos)ret true;pos command.find(kill);if (pos ! string::npos)ret true;return ret;
}
// 业务二服务器端获取命令字符串服务器执行完成后给客户端返回结果
string excuteCommand(string command)
{// 1.安全检查if (notsecure(command))return Sorry,you can do that!;// 2.业务逻辑处理FILE *fp popen(command.c_str(), r);//popen函数是创建管道在创建子进程利用子进程来处理命令并把结果输出到一个文件的返回值是文件指针。if (fp nullptr)return None;// 3.获取结果char line[1024];string result;// 这里用while的原因是fgets函数遇到换行符或EOF读取结束,也就是说一次读一行使用while循环读到文件结尾while (fgets(line, sizeof(line), fp)!nullptr){result line;}pclose(fp);return result;
}// 当传入程序参数个数不对时调用这个Usage函数告诉他什么是他妈的惊喜
static void Usage(string proc)
{cout Usage:\n\t proc port\n endl;
}// ./udp_server serverport服务器自己设置端口号
int main(int argc, char *argv[])
{if (argc ! 2) // 命令行传入参数不够{Usage(argv[0]);exit(USAGE_ERR);}// 把字符串port转换成16位整数uint16_t port atoi(argv[1]);// 智能指针构造UdpServer对象构造函数需要传入自己想定义的port//unique_ptrUdpServer usvr(new UdpServer(excuteCommand, port));unique_ptrUdpServer usvr(new UdpServer(port));//usvr-InitServer(); // 服务器初始化usvr-StartServer(); // 服务器开始服务return 0;
}
udp_server.hpp
#pragma once
#include iostream
#include sys/types.h /* See NOTES */
#include sys/socket.h
#include errno.h
#include string.h
#include string
#include pthread.h
#include stdlib.h
#include functional
#include unordered_map
// sockaddr_in结构体的头文件
#include netinet/in.h
#include arpa/inet.h
#include error.hpp
using namespace std;
#include RingQueue.hpp
#include Thread.hpp
#include lockGuard.hppnamespace ns_server
{// const uint16_t default_port 8081;using func_t functionstring(string); // func_t是指代返回值为string参数为string的函数指针class UdpServer{public:// // 构造服务器对象必须绑定端口号指定服务器处理方法// UdpServer(func_t cb, uint16_t port default_port)// : _port(port), _service(cb)// {// cout Server Port : _port endl;// }// 构造服务器对象必须绑定端口号指定服务器处理方法UdpServer(uint16_t port): _port(port), _p(){cout Server Port : _port endl;pthread_mutex_init(_mutex, nullptr); // 初始化锁// 这里使用c11 bind函数相当于函数适配器构建了一个可调用对象函数参数顺序也可以占位符标定_1,_2类似这样_p new Thread(1, bind(UdpServer::Recv, this));_c new Thread(1, bind(UdpServer::Broadcast, this));}void StartServer(){// 1.创建socket接口打开网络文件_socket socket(AF_INET, SOCK_DGRAM, 0);if (_socket 0){cerr create socket error: strerror(errno) endl;exit(SOCKET_ERR);}cout create socket success: _socket endl; // 3// 2.给服务器绑定本地IP和端口号要知道是哪个IP哪个端口号接收数据struct sockaddr_in local;bzero(local, sizeof(local)); // 清零local.sin_family AF_INET;local.sin_port htons(_port); // 端口号local.sin_addr.s_addr htonl(INADDR_ANY); // IP地址if (bind(_socket, (const struct sockaddr *)local, sizeof(local)) 0) // 绑定本地Ip与端口号{cerr bind socket error: strerror(errno) endl;exit(BIND_ERR);}cout bind socket success: _socket endl;_p-run();_c-run();}void addUser(const string name, const struct sockaddr_in peer){lockGuard lock(_mutex);auto it _onlineUser.find(name);if (it ! _onlineUser.end())return;// 没有就插入_onlineUser.insert(pairstring, struct sockaddr_in(name, peer));}// 接收client数据并记录用户ip和端口void Recv(){char buffer[1024];while (true){// 收struct sockaddr_in peer; // 输入性参数获得客户端ip与端口号socklen_t len sizeof(peer);int n recvfrom(_socket, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)peer, len); // 接收客户端发送过来的消息和客户端ip与端口if (n 0)buffer[n] \0;elsecontinue;// 提取client信息---debugstring client_ip inet_ntoa(peer.sin_addr);uint16_t client_port ntohs(peer.sin_port); // 网络序列转为主机序列cout client_ip - client_port # buffer endl; // 显示客户端发来的数据// 利用ip和端口构建一个用户名string name client_ip;name -;name to_string(client_port);addUser(name, peer); // 存入用户ip和端口后面把消息转发给所有用户string messagename;message;messagebuffer;_rq.push(message); // 接收到的消息加工一下存入环形队列// 业务处理// string message _service(buffer);// 发// sendto(_socket, message.c_str(), message.size(), 0, (struct sockaddr *)peer, sizeof(peer));}}// 广播void Broadcast(){while (true){string message;// 因为封装好的环形队列里面有信号量和互斥量所以这里不必担心线程安全问题_rq.pop(message);vectorstruct sockaddr_in v;// 这里需要设置互斥量因为两个线程访问了临界资源结果具有不确定性{lockGuard lock(_mutex);for (auto user : _onlineUser){v.push_back(user.second);}}for (auto e : v) // 给所有用户发消息{sendto(_socket, message.c_str(), message.size(), 0, (const struct sockaddr *)e, sizeof(e));// 测试消息发送出去了没cout send done... message endl;}}}~UdpServer(){pthread_mutex_destroy(_mutex);// 等待线程结束_p-join();_c-join();// 回收堆空间delete _p;delete _c;}private:int _socket;uint16_t _port;// func_t _service; // 上一个版本只是简单的IO现在要进行业务处理unordered_mapstring, struct sockaddr_in _onlineUser; // 把所有用户ip和端口保存起来后面要给所有人转发消息RingQueuestring _rq; // 环形队列存放用户发的消息pthread_mutex_t _mutex;// 两个线程一个收一个发Thread *_p;Thread *_c;};
}
#pragma onceenum{ USAGE_ERR1,SOCKET_ERR,BIND_ERR};
RingQueue.hpp
#pragma once#includeiostream
#includesemaphore.h#includectime
#includeunistd.h
#includevectorusing namespace std;const int N50;templateclass T
class RingQueue
{void P(sem_t* sem){sem_wait(sem);}void V(sem_t* sem){sem_post(sem);}void Lock(pthread_mutex_t mutex){pthread_mutex_lock(mutex);}void UnLock(pthread_mutex_t mutex){pthread_mutex_unlock(mutex);}
public:RingQueue(int numN):_ring(num),_cup(num),_consumer_step(0),_productor_step(0){sem_init(_data_sem,0,0);sem_init(_space_sem,0,_cup);pthread_mutex_init(_c_mutex,nullptr);pthread_mutex_init(_p_mutex,nullptr);}void push(const T in){P(_space_sem);Lock(_p_mutex);_ring[_productor_step]in;_productor_step % _cup;//消费者信号量加一数据V(_data_sem);UnLock(_p_mutex);}void pop(T* out){P(_data_sem);Lock(_c_mutex);*out_ring[_consumer_step];_consumer_step % _cup;//生产者信号量加一空间V(_space_sem);UnLock(_c_mutex);}~RingQueue(){sem_destroy(_data_sem);sem_destroy(_space_sem);pthread_mutex_destroy(_c_mutex);pthread_mutex_destroy(_p_mutex);}private:vectorT _ring;//数组模拟环形队列int _cup;//容量sem_t _data_sem;//消费者信号量sem_t _space_sem;//生产者信号量int _consumer_step;//消费者下标int _productor_step;//生产者下标// 单生产和单消费不存在竞争问题只要有信号量即可但是多生产和多消费的线程可能都申请到了信号量但是都在竞争同一块资源无法保证原子性pthread_mutex_t _c_mutex;pthread_mutex_t _p_mutex;};
lockGuard.hpp
#pragma once#include pthread.h
#include iostreamclass Mutex//成员加锁函数和解锁函数
{
public:Mutex(pthread_mutex_t* pmutex):_pmutex(pmutex) {}void lock(){pthread_mutex_lock(_pmutex);}void unlock(){pthread_mutex_unlock(_pmutex);}~Mutex(){}private:pthread_mutex_t* _pmutex;//需要传入一个互斥量锁的指针
};//对Mutex进行二次封装
//创建该对象时自动加锁析构时自动解锁
class lockGuard
{
public:lockGuard(pthread_mutex_t* pmutex):_mutex(pmutex)//利用锁的指针构建Mutex对象{_mutex.lock();}~lockGuard(){_mutex.unlock();}private:Mutex _mutex;//类内创建对象
};
Thread.hpp
#pragma once#include iostream
#include unistd.h
#include pthread.h
#include cstdlib
#include stringclass Thread
{
public://typedef void (*func_t) (void*);using func_tfunctionvoid();//fun_t无返回值无参数的函数指针typedef enum{NEW0,RUNNING,EXITED}ThreadStatus;public:Thread(int num,func_t func):_tid(0),_status(NEW),_func(func){char name[128];snprintf(name,sizeof(name),thread-%d,num);_namename;}//状态newrunningexitedint status(){return _status;}//线程名std::string threadname(){return _name;}//线程ID共享库中的进程地址空间的虚拟地址pthread_t threadid(){if(_statusRUNNING)//线程已经被创建线程id已经输入到成员变量_tid中return _tid;else {std::coutthread is not running,no tid!std::endl;return 0;}}static void* runHelper(void *args){//静态成员函数不能访问类内所有成员因为没有this指针Thread* td(Thread*)args;(*td)();//该对象调用仿函数return nullptr; }void operator()()//仿函数{_func();}//创建线程void run(){//因为runHelper函数必须只能有一个void*参数所以runHelper函数在类内必须定义为static这样才没有this指针int npthread_create(_tid,nullptr,runHelper,this);if(n!0) return exit(0);//线程创建失败那么直接退出进程_statusRUNNING;}//等待线程结束void join(){int npthread_join(_tid,nullptr);if(n!0) {std::cerrmain thread join thread _name error std::endl;return;}_statusEXITED;//线程退出}
private:pthread_t _tid;//线程ID原生线程库中为该线程所创建的TCB起始虚拟地址std::string _name;//线程名func_t _func;//线程要执行的回调//void* _args;//线程回调函数参数ThreadStatus _status;//枚举类型状态};
makefile
.PHONY:all
all:udp_server udp_clientudp_server:udp_server.ccg $^ -o $ -stdc11 -lpthread
udp_client:udp_client.ccg $^ -o $ -stdc11 -lpthread.PHONY:clean
clean:rm -f udp_client udp_server