郑州二七区网站建设,广告设计免费软件,免费网站怎么注册,wordpress怎么建栏目目录
预备知识
基本思路
服务端设计
重要接口详解
服务端核心代码
服务端运行代码
客户端设计 预备知识
UDP协议#xff08;User Datagram Protocal用户数据报协议#xff09;
传输层协议无连接不可靠传输面向数据报
基本思路
如下是我们设计的一个简单的“聊天室…目录
预备知识
基本思路
服务端设计
重要接口详解
服务端核心代码
服务端运行代码
客户端设计 预备知识
UDP协议User Datagram Protocal用户数据报协议
传输层协议无连接不可靠传输面向数据报
基本思路
如下是我们设计的一个简单的“聊天室”的大致框架图 “聊天室”分为两个角色一个是客户端即参与聊天的用户另一个是提供服务的服务端负责接收来自客户端对接收到的信息加工处理显示发送方的ip和端口号再转发给已经加入服务端所创建的用户列表中的所有用户即已经在该聊天室的用户。
服务端设计
重要接口详解 服务端设计只要有以下几个步骤 //第一步 创建套接字socket sockfdsocket (int domain, int type, int protocol) 1.domain指明使用的协议族常用有AF_INET 、AF_INET6、AF_UNIX 、AF_ROUTE 2. type指明socket类型 有三种SOCK_STREAMTCP、SOCK_DGRAMUDP、 SOCK_RAW原始类型允许对底层协议如IP或ICMP进行直接访问不太常用 3.protocol 通常赋值为0 --成功返回非负值的socket描述符失败返回-1 //第二步 将创建的socket绑定到指定的IP地址和端口上 bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen) --成功返回0失败返回1 PS: 1.uint16_t需要头文件 #include unistd.h 2.sockaddr_in在头文件#includenetinet/in.h或#include arpa/inet.h中定义 3.bzero函数头文件是string.hC语言或 cstringC 4.void bzero(void *s, size_t n); 5.bzero函数将指定内存块的前n个字节设置为0。 6.服务器提供服务的端口一般选择大于1023因为【01023】是系统内定的端口号 服务端核心代码
#pragma once
#include iostream
#include string
#include cstring
#include strings.h
#include sys/types.h
#include sys/socket.h
#include unistd.h
#include netinet/in.h
#include arpa/inet.h
#include unordered_map
#include Log.hpp
Log lg;enum{SOCKET_ERR1,BIND_ERR
};
uint16_t defaultport8080;
std::string defaultip0.0.0.0;
const int size1024;
class UdpServer
{
public:UdpServer(const uint16_t portdefaultport,const std::stringipdefaultip):_sockfd(0),_port(port),_ip(ip){} //初始化void Init(){//1.创建 udp socket//udp的socket是全双工的允许被同时读写//AF_INET表示使用IPv4地址族 SOCK_DGRAM表示创建一个数据报套接字0表示以阻塞的方式_sockfdsocket(AF_INET,SOCK_DGRAM,0);//创建套接字失败if(_sockfd0){lg(Fatal,socket create error,sockfd:%d,_sockfd);exit(SOCKET_ERR);}//创建套接字成功lg(Info,socket create success,sockfd:%d,_sockfd);//2.bind socketstruct sockaddr_in local;bzero(local,sizeof(local));local.sin_familyAF_INET;local.sin_porthtons(_port);//需要保证我的端口号是网络字节序列因为该端口号是要给对方发送的local.sin_addr.s_addrinet_addr(_ip.c_str()); //string-uint32_t必须是网络序列if(bind(_sockfd,(const struct sockaddr*)local,sizeof(local))0){//绑定失败lg(Fatal,bind error,errno:%d,err string:%s,errno,strerror(errno));exit(BIND_ERR);}//绑定成功lg(Info,bind success,errno:%d,err string:%s,errno,strerror(errno));}void CheckUser(const struct sockaddr_in client,const std::string clientip,uint16_t clientport){auto iter_online_user.find(clientip);if(iter_online_user.end()){_online_user.insert({clientip,client});std::cout[clientip:clientport] add to online user.std::endl;}}//对存在用户列表的所有用户进行转发void Broadcast(const std::stringinfo,const std::string clientip,uint16_t clientport){for(const auto user:_online_user){std::string message[;message clientip;message :;message std::to_string(clientport);message ]# ;message info;socklen_t len sizeof(user.second);sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)(user.second),len);}}//开始运行void Run(){isrunning true;char inbuffer[size];while(isrunning){struct sockaddr_in client;socklen_t lensizeof(client);//接收客户端ssize_t nrecvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)client,len);if(n0){//未收到lg(Warning,recvfrom error,errno:%d,err string:%s,errno,strerror(errno));continue;}uint16_t clientportntohs(client.sin_port);std::string clientipinet_ntoa(client.sin_addr);//网络字节序列转换 string//检查该用户是否已在聊天室CheckUser(client,clientip,clientport);std::string infoinbuffer;//向其他成员转发Broadcast(info,clientip,clientport);}}
private:int _sockfd;uint16_t _port;std::string _ip;bool isrunning; //服务器是否开始运行std::unordered_mapstd::string,struct sockaddr_in _online_user; //用户列表
};
服务端运行代码
#include UdpServer.hpp
#include memory
#include cstdio
#include vector
void Usage(std::string proc)
{std::cout \n\rUsage: proc port[1024]\n std::endl;
}
// ./udpserver port
int main(int argc, char *argv[])
{if(argc ! 2){Usage(argv[0]);exit(0);}uint16_t port std::stoi(argv[1]);std::unique_ptrUdpServer svr(new UdpServer(port));svr-Init(/**/);svr-Run();return 0;
}
客户端设计 客户端也是需要绑定端口的但是不需要用户显示绑定一般由os自由随机选择在首次发消息的时候绑定。不同于服务端的是服务端端口号必须是唯一确定的客户端可变。 #include iostream
#include string
#include unistd.h
#include cstring
#include pthread.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include Terminal.hpp
using namespace std;struct ThreadDate
{struct sockaddr_in server;int sockfd;std::string serverip;
};void Usage(std::string proc)
{std::cout\n\rUsage:procserverip serverport\nstd::endl;
}
//收信息
void* recv_message(void* args)
{OpenTerminal();ThreadDate* td static_castThreadDate*(args);char buffer[1024];while(true){memset(buffer,0,sizeof(buffer));struct sockaddr_in tmp;socklen_t lensizeof(tmp);ssize_t nrecvfrom(td-sockfd,buffer,1023,0,(struct sockaddr*)tmp,len);if(n0){buffer[n]\0;cerrbufferendl;}}
}
//发信息
void* send_message(void* args)
{ThreadDate* tdstatic_castThreadDate*(args);string message;socklen_t lensizeof(td-server);string welcome td-serverip;welcome coming...;sendto(td-sockfd,message.c_str(),message.size(),0,(struct sockaddr*)(td-server),len);while(true){coutPlease Enter ;getline(cin,message);sendto(td-sockfd,message.c_str(),message.size(),0,(struct sockaddr*)(td-server),len);}
}
//多线程
//./udpclient serverip serverporta
int main(int argc,char* argv[])
{if(argc!3){Usage(argv[0]);exit(0);}std::string serveripargv[1];uint16_t serverportstd::stoi(argv[2]);struct ThreadDate td;bzero(td.server,sizeof(td.server));td.server.sin_familyAF_INET;td.server.sin_porthtons(serverport);td.server.sin_addr.s_addrinet_addr(serverip.c_str());td.sockfdsocket(AF_INET,SOCK_DGRAM,0);if(td.sockfd0){coutscoket errorendl;return 1;}td.serveripserverip;pthread_t recver,sender;pthread_create(recver,nullptr,recv_message,td);pthread_create(sender,nullptr,send_message,td);pthread_join(recver,nullptr);pthread_join(sender,nullptr);close(td.sockfd);return 0;
} 上述客户端为了用户交互友好我们打开两个终端模拟一个终端负责发信息一个终端负责收信息显示我们重定向客户端收到消息后往第二个终端打印。 ls -l /dev/pts //查看我们有哪些终端文件显示它们的详细信息 例如 重定向输出信息
#include iostream
#include string
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
//原来的终端
std::string terminal /dev/pts/2;int OpenTerminal()
{int fd open(terminal.c_str(), O_WRONLY);if(fd 0){std::cerr open terminal error std::endl;return 1;}修改到要显示的终端dup2(fd, 0);// printf(hello world\n);// close(fd);return 0;
}