网站搭建的流程,统计局网站建设情况,wordpress主题语言,一个人可以做网站文章目录 网络编程套接字(2): 简单的UDP网络程序3. 简单的UDP网络程序3.1 服务端创建(1) 创建套接字(2) 绑定端口号(3) sockaddr_in结构体(4) 数据的接收与发送接收发送 3.2 客户端创建3.3 代码编写(1) v1_简单发送消息(2) v2_小写转大写(3) v3_模拟命令行解释器(4) v4_多线程版… 文章目录 网络编程套接字(2): 简单的UDP网络程序3. 简单的UDP网络程序3.1 服务端创建(1) 创建套接字(2) 绑定端口号(3) sockaddr_in结构体(4) 数据的接收与发送接收发送  3.2 客户端创建3.3 代码编写(1) v1_简单发送消息(2) v2_小写转大写(3) v3_模拟命令行解释器(4) v4_多线程版本的群聊系统(5) v5_Windows与Linux配合聊天室    网络编程套接字(2): 简单的UDP网络程序 
3. 简单的UDP网络程序 
3.1 服务端创建 
(1) 创建套接字 create an endpoint for communication: 创建用于通信的端点 
头文件:#include sys/types.h         #include sys/socket.h函数原型:int socket(int domain, int type, int protocol);参数说明:第一个参数domain:   指定套接字的通信域第二个参数type:     指定套接字的服务类型(套接字的种类) 第三个参数protocol: 代表创建套接字的协议(默认为0), 给0,系统会自动判断是tcp还是udp返回值: 套接字创建成功: 返回一个文件描述符套接字创建失败: 返回-1, 并且设置错误码关于socket参数详细介绍: 
(1) domain: 指定套接字的通信域相当于 struct sockaddr结构体的前16比特位(2字节) domain的选项是以宏的形式给出的我们直接选用即可。常用就是上面框住的两个 
AF_UNIX本地通信AF_INETIPv4或者 AF_INET6IPv6网络通信 
(2) type: 指定套接字的服务类型 该参数的选项也是像domain一样以宏的形式给出直接选用。常用的是上面两个: SOCK_STREAM: 基于TCP的网络通信流式套接字提供的是流式服务(对应TCP的特点面向字节流)  SOCK_DGRAM: 基于UDP的网络通信套接字数据报提供的用户数据报服务(对应UDP的特点面向数据报)  
(2) 绑定端口号 bind a name to socket将名称绑定到套接字 
头文件:#include sys/types.h          #include sys/socket.h函数原型:int bind(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);参数说明:第一个参数sockfd:  文件描述符, 即要绑定的套接字第二个参数addr:    网络相关的结构体, 包含IP地址、端口号等第三个参数addrlen: 传入结构体addr(第二个参数)的实际长度大小返回值:绑定成功: 返回0绑定失败: 返回-1,并且设置错误码参数addr的类型是struct sockaddr *也就是如图的结构体 我们需要做的就是定义一个 sockaddr_in 的结构体即上图的第二个结构体然后对该结构体进行内容填充填完就把给结构体传给第二个参数addr需要强制类型转换 
(3) sockaddr_in结构体 __SOCKADDR_COMMON是一个宏 #define	__SOCKADDR_COMMON(sa_prefix) sa_family_t sa_prefix##familysa_prefix代表外面传入的参数sin_ 
sa_prefix##family##代表合并拼接意思是sa_prefix与family合并拼接 
sa_prefix就是sin_则sa_prefix##family表示sin_family 
sa_family_t代表16位整数 就是这16位地址类型 sin_port 是当前服务器需要绑定的端口号它的类型是 in_port_t代表16位的整数 sin_addr: 代表IP地址它的类型是一个in_addr的结构体它里面的内容是32位的整数 关于这个IP地址我们要传入字符串风格的但是这里需要4字节整数风格所以需要转化比如1.1.1.1- uint32_t问能不能强转呢 不能强转, 强转只能改变类型, 不改变二进制构成  我们转化完了还是本主机的4字节序列需要网络序列所以要将主机序列转化成为网络序列  
上面的2步用 inet_addr函数就可以完成 但是我们的云服务器,或者一款服务器,一般不要指明某一个确定的IP 
所以这里的ip地址我们填 INADDR_ANY这是一个宏代表 0.0.0.0叫做任意地址绑定 
sin_zero: 表示该结构体的填充字段(即上面讲的sin_familysin_portsin_addr.s_add) 
总结: 未来使用这个函数时需要所以填充sin_familysin_portsin_addr.s_addr这3个字段因为不关注其他字段所以在填充之前需要对该结构体清空我们可以采用 memset或 bzero函数来完成。 
bind的作用 
上面如果我们只设置了sockaddr_in这个结构体它只是在用户空间的特定函数栈帧上不在内核中所以bind的作用就是把文件字段进行绑定关联这样这个文件就是网络文件 
(4) 数据的接收与发送 
接收 receive a message from a socket从套接字接收消息 
头文件: #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);参数说明:第一个参数sockfd: 文件描述符,就是上面我们绑定好的套接字第二个参数buf: 接收数据的缓冲区(自己定义)第三参数len:   缓冲区的长度第四个参数flags: 读取方式,默认设为0表示阻塞式读取第五个参数src_addr: (输入)对应套接字的接收缓冲区第六个参数addrlen(输出)src_addr结构体的长度返回值:成功: 返回实际读到的字节数失败: 失败返回-1并设置错误码socklen_t 是一个32位的无符号整数 参数src_addr与addrlen输入输出型参数  src_addr: 输入时传入对应套接字的接收缓冲区输出时包含客户端的ip和port  addrlen: 输入时传入对应套接字的接收缓冲区输出时表示实际输出的结构体大小  
我们做的是定义一个 sockaddr_in 的结构体把结构体传给参数src_addr需要强制类型转换 
发送 send a message on a socket: 在套接字上发送消息 
头文件:#include sys/types.h#include sys/socket.h函数原型:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数说明:第一个参数sockfd: 文件描述符从哪个套接字去发送消息第二个参数buf: 发送数据的缓冲区第三参数len: 要发送的数据长度第四个参数flags发送方式,默认设为0表示阻塞式发送第五个参数dest_addr下面解释第六个参数addrlendest_addr结构体的长度返回值:成功: 实际发送的字节数失败: 失败返回-1并设置错误码dest_addr和addrlen 是一个输入型参数  dest_addr指向目的地址的结构体指针表示要发给谁  addrlen表示目的地址结构体的长度  
我们做的是定义一个 sockaddr_in 的结构体然后对该结构体进行内容填充填完就把给结构体传给dest_addr**需要强制类型转换** 
3.2 客户端创建 
还是3步创建套接字bind(不需要自己绑定由OS自动分配)处理数据接收与发送 
3.3 代码编写 
这里一共提供5个版本的udp代码 
err.hpp这个代码是公用的后续不在给出 
#pragma once
enum
{USAGE_ERR1,SOCKET_ERR,BIND_ERR,
};(1) v1_简单发送消息 
客户端向服务端发送消息服务端收到后再把消息发回给客户端 
udp_server.hpp 
#includeiostream
#includememory
#includesys/types.h
#includesys/socket.h
#includenetinet/in.h
#includearpa/inet.h
#includecerrno
#includecstring
#includecstdlib
#includeerr.hpp
using namespace std;namespace ns_server
{const static uint16_t default_port8080;class Udpserver{public:Udpserver(uint16_t portdefault_port):port_(port){coutserver addr: port_endl;}void InitServer(){// 1. 创建socket接口, 打开网络文件(本质)sock_socket(AF_INET,SOCK_DGRAM,0);if(sock_0){cerrcreate socket error: strerror(errno)endl;exit(SOCKET_ERR);}coutcreate socket success: sock_endl;   // sock_  3// 2. 给服务器指明IP地址和Port端口号// 填充一下服务器的IP和Portstruct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!// 清空localbzero(local,sizeof(local));  // 用memset也可以// 填充sockaddr_in结构local.sin_familyAF_INET;local.sin_porthtons(port_);         // 端口号要出现在网络中, 主机序列转网络序列// 使用 inet_addr就可以做下面两件事情:// (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成// (2) 需要将主机序列转化成为网络序列// (3)云服务器,或者一款服务器,一般不要指明某一个确定的IPlocal.sin_addr.s_addrINADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    // 把套接字字段和文件字段进行关联  --- 网络文件int nbind(sock_,(struct sockaddr*)local,sizeof(local));    if(n0){cerrbind socket error: strerror(errno)endl;exit(BIND_ERR);}coutbind socket success: sock_endl;   }void Start(){char buffer[1024];       // 保存用户数据的缓冲区while(true){// 收到来自客户端发送的消息struct sockaddr_in peer;          // 远端socklen_t len  sizeof(peer);     // 这里一定要写清楚, 未来你传入的缓冲区大小// 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加\0int n  recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)peer,                                  len);  if(n0)    // 读取数据成功buffer[n]\0;elsecontinue;// 提取client信息   --- debugstring clientipinet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的uint16_t clientportntohs(peer.sin_port);  // 网络序列转主机序列cout clientip  - clientport # bufferendl;// 把消息发给别人// 网络套接字本质是文件, 往文件中写入时\0并不需要写到文件中, \0是C语言的规定// 谁给我发的, 我就把消息转给谁// peer结构体字段是从网络中拿的, 本来就是网络序列, 直接发就行sendto(sock_,buffer,strlen(buffer),0,(struct sockaddr*)peer, 		  	                        sizeof(peer));}}~Udpserver(){}private:int sock_;         //套接字(文件描述符)uint16_t port_;    //端口号(本地主机序列构建的port)};
}udp_server.cc 
#includeudp_server.hpp
using namespace ns_server;// 运行格式: ./udp_server port// 使用手册
static void usage(string proc)
{coutusage:\n\tproc port\nendl;
}int main(int argc,char*argv[])
{if(argc ! 2){usage(argv[0]);exit(USAGE_ERR);}uint16_t portatoi(argv[1]);         //命令行参数转换成uint16_t类型unique_ptrUdpserver usvr(new Udpserver(port));usvr-InitServer();     // 服务器初始化usvr-Start();return 0;
}udp_client.cc 
#pragma once#includeiostream
#includeerr.hpp
#includesys/types.h
#includesys/socket.h
#includecstring
#includestring
#includenetinet/in.h
#includearpa/inet.h
using namespace std;// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试static void usage(string proc)
{coutusage:\n\tproc serverip serverport\n endl;
}// 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]);// 1. 创建套接字int socksocket(AF_INET,SOCK_DGRAM,0);if (sock  0){cerr  create socket error:   strerror(errno)  endl;exit(SOCKET_ERR);}// 2. 关于客户端的绑定// client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip:        serverport]// 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?// client的port要随机让OS分配防止client出现启动冲突 // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化// 明确服务器是谁struct sockaddr_in server;memset(server,0,sizeof(server));server.sin_familyAF_INET;server.sin_porthtons(serverport);     //主机序列转网络序列server.sin_addr.s_addrinet_addr(serverip.c_str());// 3. 向服务器发送消息(这里暂时由用户充当)while(true){// 用户输入string message;cout please Enter# ;cinmessage;// 什么时候bind呢?// 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport自己的IP, (1)bind (2)构建发送的数据报文// 发送sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)server,                        sizeof(server));// 把消息再收回来(回显回来)char buffer[1024];struct sockaddr_in temp;socklen_t len sizeof(temp);int nrecvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)temp,len);if(n0){buffer[n]\0;coutserver echo# bufferendl;}}
}运行结果 
先运行服务端再启动客户端客户端先用本地环回进行测试测试成功 运行程序后看到套接字是创建成功的对应得到到的文件描述符是3这也很好理解因为0、1、2默认被标准输入流、标准输出流和标准错误流占用了此时最小的、未被使用用的文件描述符就是3 
(2) v2_小写转大写 
v2在v1版本的基础增加了业务处理上层使用了回调函数实现大小写转换 
udp_server.hpp 
#pragma once#includeiostream
#includememory
#includesys/types.h
#includesys/socket.h
#includenetinet/in.h
#includearpa/inet.h
#includecerrno
#includecstring
#includecstdlib
#includefunctional
#includeerr.hpp
using namespace std;namespace ns_server
{const static uint16_t default_port8080;using func_t functionstring(string);    //这是一个函数class Udpserver{public:Udpserver(func_t cb, uint16_t portdefault_port):service_(cb),port_(port){coutserver addr: port_endl;}void InitServer(){// 1. 创建socket接口, 打开网络文件(本质)sock_socket(AF_INET,SOCK_DGRAM,0);if(sock_0){cerrcreate socket error: strerror(errno)endl;exit(SOCKET_ERR);}coutcreate socket success: sock_endl;   // sock_  3// 2. 给服务器指明IP地址和Port端口号// 填充一下服务器的IP和Portstruct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!// 清空localbzero(local,sizeof(local));  // 用memset也可以// 填充sockaddr_in结构local.sin_familyAF_INET;local.sin_porthtons(port_);         // 端口号要出现在网络中, 主机序列转网络序列// 使用 inet_addr就可以做下面两件事情:// (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成// (2) 需要将主机序列转化成为网络序列// (3)云服务器,或者一款服务器,一般不要指明某一个确定的IPlocal.sin_addr.s_addrINADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    // 把套接字字段和文件字段进行关联  --- 网络文件int nbind(sock_,(struct sockaddr*)local,sizeof(local));    if(n0){cerrbind socket error: strerror(errno)endl;exit(BIND_ERR);}coutbind socket success: sock_endl;   }void Start(){char buffer[1024];       // 保存用户数据的缓冲区while(true){// 收到来自客户端发送的消息struct sockaddr_in peer;                 // 远端socklen_t len  sizeof(peer);             // 这里一定要写清楚, 未来你传入的缓冲区大小// 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加\0int n  recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)peer,                                  len);  if(n0)    // 读取数据成功buffer[n]\0;elsecontinue;// 提取client信息   --- debugstring clientipinet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的uint16_t clientportntohs(peer.sin_port);  // 网络序列转主机序列cout clientip  - clientport # bufferendl;// 做业务处理string responseservice_(buffer);// 把消息发给别人// 网络套接字本质是文件, 往文件中写入时\0并不需要写到文件中, \0是C语言的规定// 谁给我发的, 我就把消息转给谁// peer结构体字段是从网络中拿的, 本来就是网络序列, 直接发就行sendto(sock_,response.c_str(),response.size(),0,(struct sockaddr*)peer, 						sizeof(peer));}}~Udpserver(){}private:int sock_;         //套接字(文件描述符)uint16_t port_;    //端口号(本地主机序列构建的port)func_t service_;    //我们的网络服务器刚刚解决的是网络IO的问题, 要进行业务处理(一个类内的回调方法)};
}udp_server.cc 
#includeudp_server.hpp
using namespace ns_server;// 运行格式: ./udp_server port// 使用手册
static void usage(string proc)
{coutusage:\n\tproc port\nendl;
}// 上层的业务处理, 不关心网络发送, 只负责信息处理即可// 这里是小写转大写
string transactionString(string request)   // request就是一个字符串
{string ret;char c;for(autor:request){if(islower(r)){ctoupper(r);ret.push_back(c);}else{ret.push_back(r);}}return ret;
}
int main(int argc,char*argv[])
{if(argc ! 2){usage(argv[0]);exit(USAGE_ERR);}uint16_t portatoi(argv[1]);         //命令行参数转换成uint16_t类型unique_ptrUdpserver usvr(new Udpserver(transactionString,port));usvr-InitServer();     // 服务器初始化usvr-Start();return 0;
}udp_client.cc 
#pragma once#includeiostream
#includeerr.hpp
#includesys/types.h
#includesys/socket.h
#includecstring
#includestring
#includenetinet/in.h
#includearpa/inet.h
using namespace std;// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试static void usage(string proc)
{coutusage:\n\tproc serverip serverport\n endl;
}// 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]);// 1. 创建套接字int socksocket(AF_INET,SOCK_DGRAM,0);if (sock  0){cerr  create socket error:   strerror(errno)  endl;exit(SOCKET_ERR);}// 2. 关于客户端的绑定// client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip:        		serverport]// 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?// client的port要随机让OS分配防止client出现启动冲突 // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化// 明确服务器是谁struct sockaddr_in server;memset(server,0,sizeof(server));server.sin_familyAF_INET;server.sin_porthtons(serverport);     //主机序列转网络序列server.sin_addr.s_addrinet_addr(serverip.c_str());// 3. 向服务器发送消息(这里暂时由用户充当)while(true){// 用户输入string message;cout please Enter# ;cinmessage;// 什么时候bind呢?// 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport自己的IP, (1)bind (2)构建发送的数据报文// 发送sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)server,                        sizeof(server));// 把消息再收回来(回显回来)char buffer[1024];struct sockaddr_in temp;socklen_t len sizeof(temp);int nrecvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)temp,len);if(n0){buffer[n]\0;coutserver echo# bufferendl;}}
}运行结果 (3) v3_模拟命令行解释器 
v3是在v2原有的业务处理下修改了功能只要我们在客户端输入命令服务端就会返回运行结果popen函数可以实现简单的命令行解释 
udp_server.hpp 
#pragma once#includeiostream
#includememory
#includesys/types.h
#includesys/socket.h
#includenetinet/in.h
#includearpa/inet.h
#includecerrno
#includecstring
#includecstdlib
#includefunctional
#includeerr.hpp
using namespace std;namespace ns_server
{const static uint16_t default_port8080;using func_t functionstring(string);    //这是一个函数class Udpserver{public:Udpserver(func_t cb, uint16_t portdefault_port):service_(cb),port_(port){coutserver addr: port_endl;}void InitServer(){// 1. 创建socket接口, 打开网络文件(本质)sock_socket(AF_INET,SOCK_DGRAM,0);if(sock_0){cerrcreate socket error: strerror(errno)endl;exit(SOCKET_ERR);}coutcreate socket success: sock_endl;   // sock_  3// 2. 给服务器指明IP地址和Port端口号// 填充一下服务器的IP和Portstruct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!// 清空localbzero(local,sizeof(local));  // 用memset也可以// 填充sockaddr_in结构local.sin_familyAF_INET;local.sin_porthtons(port_);         // 端口号要出现在网络中, 主机序列转网络序列// 使用 inet_addr就可以做下面两件事情:// (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成// (2) 需要将主机序列转化成为网络序列// (3)云服务器,或者一款服务器,一般不要指明某一个确定的IPlocal.sin_addr.s_addrINADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    // 把套接字字段和文件字段进行关联  --- 网络文件int nbind(sock_,(struct sockaddr*)local,sizeof(local));    if(n0){cerrbind socket error: strerror(errno)endl;exit(BIND_ERR);}coutbind socket success: sock_endl;   }void Start(){char buffer[1024];       // 保存用户数据的缓冲区while(true){// 收到来自客户端发送的消息struct sockaddr_in peer;                 // 远端socklen_t len  sizeof(peer);             // 这里一定要写清楚, 未来你传入的缓冲区大小// 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加\0int n  recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)peer,                                  len);  if(n0)    // 读取数据成功buffer[n]\0;elsecontinue;// 提取client信息   --- debugstring clientipinet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的uint16_t clientportntohs(peer.sin_port);  // 网络序列转主机序列cout clientip  - clientport # bufferendl;// 做业务处理string responseservice_(buffer);// 把消息发给别人// 网络套接字本质是文件, 往文件中写入时\0并不需要写到文件中, \0是C语言的规定// 谁给我发的, 我就把消息转给谁// peer结构体字段是从网络中拿的, 本来就是网络序列, 直接发就行sendto(sock_,response.c_str(),response.size(),0,(struct sockaddr*)peer, 						sizeof(peer));}}~Udpserver(){}private:int sock_;         //套接字(文件描述符)uint16_t port_;    //端口号(本地主机序列构建的port)func_t service_;    //我们的网络服务器刚刚解决的是网络IO的问题, 要进行业务处理(一个类内的回调方法)};
}udp_server.cc 
#includeudp_server.hpp
using namespace ns_server;// 运行格式: ./udp_server port// 使用手册
static void usage(string proc)
{coutusage:\n\tproc port\nendl;
}// 上层的业务处理, 不关心网络发送, 只负责信息处理即可
static bool isPass(string command)
{auto poscommand.find(rm);if(pos!string::npos) return false;poscommand.find(mv);if(pos!string::npos) return false;poscommand.find(while);if(pos!string::npos) return false;poscommand.find(kill);if(pos!string::npos) return false;return true;
}// 让同学们, 在你的本地把命令给我, server再把结果给你!
string excuteCommand(string command)   // command就是一个命令
{// 1. 安全检查if(!isPass(command))return you are a bad man;// 2. 业务逻辑处理FILE*fppopen(command.c_str(),r);if(fpnullptr)return None;// 3. 获取结果char line[1024];string ret;while(fgets(line,sizeof(line),fp)!NULL){retline;}pclose(fp);return ret;
}int main(int argc,char*argv[])
{if(argc ! 2){usage(argv[0]);exit(USAGE_ERR);}uint16_t portatoi(argv[1]);         //命令行参数转换成uint16_t类型unique_ptrUdpserver usvr(new Udpserver(excuteCommand,port));usvr-InitServer();     // 服务器初始化usvr-Start();return 0;
}udp_server.cc 
#pragma once#includeiostream
#includeerr.hpp
#includesys/types.h
#includesys/socket.h
#includecstring
#includestring
#includenetinet/in.h
#includearpa/inet.h
using namespace std;// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试static void usage(string proc)
{coutusage:\n\tproc serverip serverport\n endl;
}// 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]);// 1. 创建套接字int socksocket(AF_INET,SOCK_DGRAM,0);if (sock  0){cerr  create socket error:   strerror(errno)  endl;exit(SOCKET_ERR);}// 2. 关于客户端的绑定// client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip: serverport]// 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?// client的port要随机让OS分配防止client出现启动冲突 // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化// 明确服务器是谁struct sockaddr_in server;memset(server,0,sizeof(server));server.sin_familyAF_INET;server.sin_porthtons(serverport);     //主机序列转网络序列server.sin_addr.s_addrinet_addr(serverip.c_str());// 3. 向服务器发送消息(这里暂时由用户充当)while(true){// 用户输入string message;cout [遇健的服务器]# ;getline(cin,message);// 什么时候bind呢?// 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport自己的IP, (1)bind (2)构建            发送的数据报文// 发送sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)server,                       sizeof(server));// 把消息再收回来(回显回来)char buffer[2048];struct sockaddr_in temp;socklen_t len sizeof(temp);int nrecvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)temp,len);if(n0){buffer[n]\0;coutserver echo# bufferendl;}}
}运行结果 (4) v4_多线程版本的群聊系统 
v4在v3的基础上加入了之前写的生产消费者模型多线程实现了群聊系统 
LockGuard.hpp 
#includeiostream
#includepthread.h
using namespace std;class Mutex   //自己不维护锁,由外部传入
{
public:Mutex(pthread_mutex_t* mutex):_pmutex(mutex){}void lock(){pthread_mutex_lock(_pmutex);}void unlock(){pthread_mutex_unlock(_pmutex);}~Mutex(){}private:pthread_mutex_t* _pmutex;   //锁的指针
};class LockGuard  //自己不维护锁,由外部传入
{
public:LockGuard(pthread_mutex_t* mutex):_mutex(mutex){_mutex.lock();}~LockGuard(){_mutex.unlock();}private:Mutex _mutex;   //锁的指针
};RingQueue.hpp 
#includeiostream
#includepthread.h
#includevector
#includetime.h
#includesys/types.h
#includeunistd.h
#includesemaphore.h
#includemutex
using namespace std;// 生产者和消费者要有自己的下标来表征生产和消费要访问哪个资源
static const int N5;templateclass T
class RingQueue
{
private:void P(sem_t s){sem_wait(s);}void V(sem_t s){sem_post(s);}void Lock(pthread_mutex_t m){pthread_mutex_lock(m);}void Unlock(pthread_mutex_t m){pthread_mutex_unlock(m);}public:RingQueue(int numN):_ring(num),_cap(num){sem_init(_data_sem,0,0);sem_init(_space_sem,0,num);_c_step_p_step0;pthread_mutex_init(_c_mutex,nullptr);pthread_mutex_init(_p_mutex,nullptr);}void push(const Tin)     // 对应生产者{// 1.信号量的好处:// 可以不用在临界区内部做判断, 就可以知道临界资源的使用情况// 2.什么时候用锁, 什么时候用sem? --- 你对应的临界资源, 是否被整体使用!// 生产 --- 先要申请信号量// 信号量申请成功 - 则一定能访问临界资源P(_space_sem);Lock(_p_mutex);// 一定要有对应的空间资源给我!不用做判断, 是哪一个资源给生产者呢_ring[_p_step]in;_p_step%_cap;V(_data_sem);Unlock(_p_mutex);}void pop(T*out)           // 对应消费者{// 消费P(_data_sem);    // 1.   先申请信号量是为了更高效Lock(_c_mutex);  // 2. *out_ring[_c_step];_c_step%_cap;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 _cap;           // 环形队列容器大小sem_t _data_sem;    // 只有消费者关心sem_t _space_sem;   // 只有生产者关心int _c_step;        // 消费位置int _p_step;        // 生产位置pthread_mutex_t _c_mutex;pthread_mutex_t _p_mutex;
};udp_server.hpp 
#includeiostream
#includememory
#includesys/types.h
#includesys/socket.h
#includenetinet/in.h
#includearpa/inet.h
#includecerrno
#includecstring
#includecstdlib
#includefunctional
#includeerr.hpp
#includeunordered_map
#includeringQueue1.hpp
#includelockGuard.hpp
#includethread.hpp
using namespace std;// 群聊系统 --- 一个线程收消息, 一个线程发消息
// 标识一个客户端: ipport , 使用unordered_map构建ipport, 客户端套接字来表示某个用户发的消息namespace ns_server
{const static uint16_t default_port8080;using func_t functionstring(string);    //这是一个函数class Udpserver{public:Udpserver(uint16_t portdefault_port):port_(port){coutserver addr: port_endl;pthread_mutex_init(_lock,nullptr);pnew Thread(1,bind(Udpserver::Recv,this));cnew Thread(2,bind(Udpserver::Broadcast,this));}void StartServer(){// 1. 创建socket接口, 打开网络文件(本质)sock_socket(AF_INET,SOCK_DGRAM,0);if(sock_0){cerrcreate socket error: strerror(errno)endl;exit(SOCKET_ERR);}coutcreate socket success: sock_endl;   // sock_  3// 2. 给服务器指明IP地址和Port端口号// 填充一下服务器的IP和Portstruct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!// 清空localbzero(local,sizeof(local));  // 用memset也可以// 填充sockaddr_in结构local.sin_familyAF_INET;local.sin_porthtons(port_);         // 端口号要出现在网络中, 主机序列转网络序列// 使用 inet_addr就可以做下面两件事情:// (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成// (2) 需要将主机序列转化成为网络序列// (3)云服务器,或者一款服务器,一般不要指明某一个确定的IPlocal.sin_addr.s_addrINADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    // 把套接字字段和文件字段进行关联  --- 网络文件int nbind(sock_,(struct sockaddr*)local,sizeof(local));    if(n0){cerrbind socket error: strerror(errno)endl;exit(BIND_ERR);}coutbind socket success: sock_endl;   p-run();c-run();}void addUser(const stringname,const struct sockaddr_inpeer){// online[name]peerLockGuard lockguard(_lock);auto iteronlineuser.find(name);if(iter!onlineuser.end())        // 存在(找到了)直接返回return;onlineuser.insert(make_pair(name,peer));   // 不存在(没找到)就插入}void Recv(){char buffer[1024];       // 保存用户数据的缓冲区while(true){// 收到来自客户端发送的消息struct sockaddr_in peer;                 // 远端socklen_t len  sizeof(peer);             // 这里一定要写清楚, 未来你传入的缓冲区大小// 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加\0int n  recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)peer,                                  len);  if(n0)    // 读取数据成功buffer[n]\0;elsecontinue;// 提取client信息   --- debugstring clientipinet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的uint16_t clientportntohs(peer.sin_port);  // 网络序列转主机序列cout clientip  - clientport # bufferendl;// 构建一个用户, 并检查string nameclientip;name-;nameto_string(clientport);// 构建哈希表来存储用户 - 如果不存在,就插入;如果存在,什么都不做addUser(name,peer);  string messagenamebuffer;  _rq.push(message);    // 消息放入环形队列中}}// 发消息  --- 给所有在线用户void Broadcast(){while(true){string sendstring;_rq.pop(sendstring);    // 从环形队列中读到了消息   vectorstruct sockaddr_in v;   // 把需要发送的信息放到(拷贝)一个数组中这是内存级的拷贝{LockGuard lockguard(_lock);for (auto user:onlineuser){v.push_back(user.second);}}for(auto user: v){sendto(sock_,sendstring.c_str(),sendstring.size(),0,(struct sockaddr*)user,sizeof(user));coutsend done ...sendstringendl;}}}~Udpserver(){pthread_mutex_destroy(_lock);p-join();c-join();delete p;delete c;}private:int sock_;         //套接字(文件描述符)uint16_t port_;    //端口号(本地主机序列构建的port)unordered_mapstring, struct sockaddr_in onlineuser;   // 保存在线用户 --- 需要加锁保证安全pthread_mutex_t _lock;RingQueuestring _rq;Thread*p;Thread*c;};
}udp_server.cc 
#includeudp_server.hpp
using namespace ns_server;// 运行格式: ./udp_server port// 使用手册
static void usage(string proc)
{coutusage:\n\tproc port\nendl;
}// 上层的业务处理, 不关心网络发送, 只负责信息处理即可int main(int argc,char*argv[])
{if(argc ! 2){usage(argv[0]);exit(USAGE_ERR);}uint16_t portatoi(argv[1]);         //命令行参数转换成uint16_t类型unique_ptrUdpserver usvr(new Udpserver(port));usvr-StartServer();return 0;
}udp_client.cc 
#pragma once#includeiostream
#includeerr.hpp
#includesys/types.h
#includesys/socket.h
#includecstring
#includestring
#includenetinet/in.h
#includearpa/inet.h
using namespace std;// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试static void usage(string proc)
{coutusage:\n\tproc serverip serverport\n endl;
}// 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]);// 1. 创建套接字int socksocket(AF_INET,SOCK_DGRAM,0);if (sock  0){cerr  create socket error:   strerror(errno)  endl;exit(SOCKET_ERR);}// 2. 关于客户端的绑定// client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip: serverport]// 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?// client的port要随机让OS分配防止client出现启动冲突 // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化// 明确服务器是谁struct sockaddr_in server;memset(server,0,sizeof(server));server.sin_familyAF_INET;server.sin_porthtons(serverport);     //主机序列转网络序列server.sin_addr.s_addrinet_addr(serverip.c_str());// 3. 向服务器发送消息(这里暂时由用户充当)while(true){// 用户输入string message;cout [遇健的服务器]# ;getline(cin,message);// 什么时候bind呢?// 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport自己的IP, (1)bind (2)构建发送的数据报文// 发送sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)server,                        sizeof(server));// 把消息再收回来(回显回来)char buffer[2048];struct sockaddr_in temp;socklen_t len sizeof(temp);int nrecvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)temp,len);if(n0){buffer[n]\0;coutserver echo# bufferendl;}}
}运行结果 (5) v5_Windows与Linux配合聊天室 
我们可以以Linux云服务器作为服务端Windows作为客户端在Windows下我们要修改成Windows下的接口同时开放云服务器的端口号使用v4版本的服务端代码 
Windows下的客户端 
#define _CRT_SECURE_NO_WARNINGS#includeiostream
#includeWinSock2.h
#includestring
#includecstring
using namespace std;#pragma warning(disable:4996)
#pragma comment(lib,ws2_32.lib)uint16_t serverport  8080;
std::string serverip  47.108.235.67;//std::string serverip  127.0.0.1;int main()
{WSADATA WSAData;if (WSAStartup(MAKEWORD(2, 2), WSAData) ! 0){cerr  init error  endl;return -1;}SOCKET sock  socket(AF_INET, SOCK_DGRAM, 0);if (sock  0){cerr  create socket error:   strerror(errno)  endl;exit(-2);}struct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family  AF_INET;server.sin_port  htons(serverport);     //主机序列转网络序列server.sin_addr.s_addr  inet_addr(serverip.c_str());// 3. 向服务器发送消息(这里暂时由用户充当)while (true){// 用户输入string message;cout  Please Enter Your Message# ;getline(cin, message);// 发送sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)server,                      sizeof(server));char buffer[2048];struct sockaddr_in temp;int len  sizeof(temp);int n  recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)temp,                           len);if (n  0){buffer[n]  \0;cout  buffer  endl;     // 往1号文件描述符输出}}closesocket(sock);WSACleanup();return 0;
}运行结果 
 文章转载自: http://www.morning.rkgyx.cn.gov.cn.rkgyx.cn http://www.morning.xtyyg.cn.gov.cn.xtyyg.cn http://www.morning.ftrpvh.cn.gov.cn.ftrpvh.cn http://www.morning.xldpm.cn.gov.cn.xldpm.cn http://www.morning.ypdmr.cn.gov.cn.ypdmr.cn http://www.morning.xqbbc.cn.gov.cn.xqbbc.cn http://www.morning.hnzrl.cn.gov.cn.hnzrl.cn http://www.morning.kclkb.cn.gov.cn.kclkb.cn http://www.morning.pymff.cn.gov.cn.pymff.cn http://www.morning.ntzfl.cn.gov.cn.ntzfl.cn http://www.morning.rbyz.cn.gov.cn.rbyz.cn http://www.morning.qfcnp.cn.gov.cn.qfcnp.cn http://www.morning.crrjg.cn.gov.cn.crrjg.cn http://www.morning.bxrlt.cn.gov.cn.bxrlt.cn http://www.morning.dzrcj.cn.gov.cn.dzrcj.cn http://www.morning.gynkr.cn.gov.cn.gynkr.cn http://www.morning.hsksm.cn.gov.cn.hsksm.cn http://www.morning.ftldl.cn.gov.cn.ftldl.cn http://www.morning.crdtx.cn.gov.cn.crdtx.cn http://www.morning.kmkpm.cn.gov.cn.kmkpm.cn http://www.morning.xhklb.cn.gov.cn.xhklb.cn http://www.morning.spqtq.cn.gov.cn.spqtq.cn http://www.morning.gbsfs.com.gov.cn.gbsfs.com http://www.morning.hhxwr.cn.gov.cn.hhxwr.cn http://www.morning.brmbm.cn.gov.cn.brmbm.cn http://www.morning.dyxlm.cn.gov.cn.dyxlm.cn http://www.morning.hqmfn.cn.gov.cn.hqmfn.cn http://www.morning.kfrhh.cn.gov.cn.kfrhh.cn http://www.morning.tnfyj.cn.gov.cn.tnfyj.cn http://www.morning.fdmtr.cn.gov.cn.fdmtr.cn http://www.morning.wgtnz.cn.gov.cn.wgtnz.cn http://www.morning.zwdrz.cn.gov.cn.zwdrz.cn http://www.morning.qyllw.cn.gov.cn.qyllw.cn http://www.morning.rwqk.cn.gov.cn.rwqk.cn http://www.morning.srgnd.cn.gov.cn.srgnd.cn http://www.morning.trffl.cn.gov.cn.trffl.cn http://www.morning.skksz.cn.gov.cn.skksz.cn http://www.morning.tjwfk.cn.gov.cn.tjwfk.cn http://www.morning.nsfxt.cn.gov.cn.nsfxt.cn http://www.morning.tzcr.cn.gov.cn.tzcr.cn http://www.morning.qineryuyin.com.gov.cn.qineryuyin.com http://www.morning.rsbqq.cn.gov.cn.rsbqq.cn http://www.morning.hpprx.cn.gov.cn.hpprx.cn http://www.morning.tplht.cn.gov.cn.tplht.cn http://www.morning.gllgf.cn.gov.cn.gllgf.cn http://www.morning.hkcjx.cn.gov.cn.hkcjx.cn http://www.morning.pfcrq.cn.gov.cn.pfcrq.cn http://www.morning.mqtzd.cn.gov.cn.mqtzd.cn http://www.morning.gtdf.cn.gov.cn.gtdf.cn http://www.morning.yngtl.cn.gov.cn.yngtl.cn http://www.morning.alive-8.com.gov.cn.alive-8.com http://www.morning.pphbn.cn.gov.cn.pphbn.cn http://www.morning.rknjx.cn.gov.cn.rknjx.cn http://www.morning.qnypp.cn.gov.cn.qnypp.cn http://www.morning.qhtlq.cn.gov.cn.qhtlq.cn http://www.morning.mxnfh.cn.gov.cn.mxnfh.cn http://www.morning.krwzy.cn.gov.cn.krwzy.cn http://www.morning.ghryk.cn.gov.cn.ghryk.cn http://www.morning.cgntj.cn.gov.cn.cgntj.cn http://www.morning.zqcdl.cn.gov.cn.zqcdl.cn http://www.morning.nlffl.cn.gov.cn.nlffl.cn http://www.morning.zlces.com.gov.cn.zlces.com http://www.morning.wbdm.cn.gov.cn.wbdm.cn http://www.morning.zfhzx.cn.gov.cn.zfhzx.cn http://www.morning.qpntn.cn.gov.cn.qpntn.cn http://www.morning.csgwd.cn.gov.cn.csgwd.cn http://www.morning.pbygt.cn.gov.cn.pbygt.cn http://www.morning.xjqkh.cn.gov.cn.xjqkh.cn http://www.morning.lzqdd.cn.gov.cn.lzqdd.cn http://www.morning.lmzpk.cn.gov.cn.lmzpk.cn http://www.morning.dbtdy.cn.gov.cn.dbtdy.cn http://www.morning.rkck.cn.gov.cn.rkck.cn http://www.morning.kpnpd.cn.gov.cn.kpnpd.cn http://www.morning.grbp.cn.gov.cn.grbp.cn http://www.morning.rfljb.cn.gov.cn.rfljb.cn http://www.morning.ggnkt.cn.gov.cn.ggnkt.cn http://www.morning.ckntb.cn.gov.cn.ckntb.cn http://www.morning.qhczg.cn.gov.cn.qhczg.cn http://www.morning.hsrch.cn.gov.cn.hsrch.cn http://www.morning.rqmr.cn.gov.cn.rqmr.cn