一、Server端的实现
1.1、服务端的初始化
①、创建套接字:
创建套接字接口:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//1. 这是一个创建套接字的接口
//2. domain: 协议家族类型,其中包括ipv4、ipv6、等等,通常设置为ipv4就行,即AF_INET
//3. type: 套接字指定类型,其中SOCK_DGRAM是UDP类型,SOCK_STREAM是TCP类型
//4. 返回值int: 创建失败返回值小于0并设置错误码
②、绑定前的准备工作:
③、绑定套接字:
绑定接口:
//1. 这是一个绑定套接字的接口
//2. socket: 需要绑定的套接字
//3. address: sockaddr结构体地址
//4. address_len: sockaddr结构体大小
//5. 返回值int,绑定成功返回0,失败返回-1并设置错误码
1.2、服务端运行
①、接收信息
接收信息接口:
//1. socket: 从那个套接字接收信息
//2. buffer:接收的信息存放在哪个缓冲区; length: 缓冲区的大小
//3. flags: 控制接收行为的标志,通常设为0
//4. address: sockaddr结构体的地址(输入型参数,用来获取对方信息)
//5. address_len: sockaddr结构体大小
//6. 返回值ssize_t 成功返回接收到的字节数,如果没有数据可读或者套接字关闭返回0,失败返回-1,并设置errno错误码
②、接收信息的同时获取对方信息
③、实现两个方法,一个用来检测用户登陆;一个用来把用户发来的消息转发给每一个用户
到这里,服务端的基本框架完成,然后我们在实现以下客户端吧!
二、客户端的实现
2.1、创建套接字
2.2、准备数据并绑定(客户端不需要自己绑定,OS自动绑定)
2.3、用两个线程来执行收发任务
2.4、收发信息的实现
OK,以上客户端跟服务端都已搭建完成,那我们来测试一下吧
三、测试
3.1、本地测试
客户端:
这里我把内容重定向到另一个终端上,避免它输入到同一个终端上
服务端:
3.2、跨平台测试
四、最终代码
4.1 server
#pragma once
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string>
#include"log.hpp"
#include<unordered_map>
Log lg;
const uint16_t defaultport=8808;
const std::string defaultip="0.0.0.0";
enum{
SOCK_ERR=1,
BIND_ERR
};
class UdpServer
{
private:
int sock_fd;//网络文件描述符
uint16_t port_;//端口
std::string ip_;//ip
std::unordered_map<std::string, sockaddr_in> online_user;
public:
UdpServer(const uint16_t&port=defaultport,const std::string &ip=defaultip)
:port_(port),ip_(ip)
{
}
void Initialize()
{
//1.创建套接字
sock_fd=socket(AF_INET,SOCK_DGRAM,0);
if(sock_fd<0)
{
lg(FATAL,"Sock create fail!! errno is: %d, errstring is: %s",errno,strerror(errno));
exit(SOCK_ERR);
}
lg(INFO,"Sock create sucess!! sockfd is: %d",sock_fd);
//2.数据准备
struct sockaddr_in server;
memset(&server,0,sizeof(server));//初始化结构体
inet_aton(ip_.c_str(),&server.sin_addr);//字符串转地址
server.sin_family=AF_INET;
server.sin_port=htons(port_);//主机转网络字节序
//3.bind
if(bind(sock_fd,(sockaddr*)&server,sizeof(server))<0)
{
lg(FATAL,"Bind fail!! errno is: %d,errstring is: %s",errno,strerror(errno));
exit(BIND_ERR);
}
lg(INFO,"Bind sucess!! sockfd is: %d",sock_fd);
}
void CheckUser(const uint16_t&port,const std::string&ip,sockaddr_in&client)
{
auto iter=online_user.find(ip);
if(iter!=online_user.end())return;
online_user.insert({ip,client});
std::cout<<"["<<port<<":"<<ip<<"]"<<"# user login...."<<std::endl;
}
void Broadcast(const uint16_t&port,const std::string&ip,const std::string& info)
{
std::string message="[";
message+=std::to_string(port);
message+=":";
message+=ip;
message+="]#:";
std::string echo_message=message+info;
for(auto &e:online_user)
{
sendto(sock_fd,echo_message.c_str(),echo_message.size(),0,(sockaddr*)(&e.second),sizeof(e.second));
}
}
void Start()
{
char buffer[1024];//读入的缓冲区
while(true)
{
//1.读
struct sockaddr_in client;//用来获取客户端信息
socklen_t len=sizeof(client);
ssize_t n=recvfrom(sock_fd,buffer,sizeof(buffer),0,(sockaddr*)&client,&len);
if(n<0)
{
continue;//读取失败继续读
}
else if(n>0)
{
buffer[n]='\0';
std::string info=buffer;
uint16_t clientport=ntohs(client.sin_port);//网络转主机字节序
char ipstr[32];
inet_ntop(AF_INET,&client.sin_addr,ipstr,sizeof(ipstr));//地址转字符串
std::string clientip=ipstr;
CheckUser(clientport,clientip,client);//检查用户是否存在
Broadcast(clientport,clientip,info);//转发客户端的信息给每个用户
}
}
}
~UdpServer()
{
if(sock_fd>0)close(sock_fd);
}
};
#include<iostream>
#include"UdpServer.hpp"
#include<memory>
void Usage(std::string proc)
{
std::cout<<"\n\rUsage:"<<proc<<" serverport[1024+]"<<std::endl;
}
// server serverport
int main(int argc,char*argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(1);
}
uint16_t serverport=std::stoi(argv[1]);
std::unique_ptr<UdpServer> ptr(new UdpServer(serverport));
ptr->Initialize();
ptr->Start();
return 0;
}
4.2 client
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<cstring>
struct ThreadData
{
int sock_fd;
std::string serverip_;
struct sockaddr_in server;
};
void Usage(std::string proc)
{
std::cout<<"\n\rUsage:"<<proc<<" serverip serverport"<<std::endl;
}
void* send_message(void*args)
{
ThreadData* td=static_cast<ThreadData*>(args);
std::string line;
while(true)
{
std::cout<<"Please enter@"<<std::endl;
std::getline(std::cin,line);
sendto(td->sock_fd,line.c_str(),line.size(),0,(sockaddr*)&td->server,sizeof(td->server));
}
return nullptr;
}
void* recv_message(void*args)
{
ThreadData* td=static_cast<ThreadData*>(args);
char buffer[1024];
while(true)
{
memset(buffer,0,sizeof(buffer));
struct sockaddr_in temp;
socklen_t templen=sizeof(temp);
ssize_t n=recvfrom(td->sock_fd,buffer,sizeof(buffer)-1,0,(sockaddr*)&temp,&templen);
if(n>0)
{
buffer[n]='\0';
std::cerr<<buffer<<std::endl;
}
}
return nullptr;
}
//udpclient srverip server port
int main(int argc,char*argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(1);
}
ThreadData td;
td.serverip_=argv[1];
uint16_t serverport=std::stoi(argv[2]);
//1.创建套接字
td.sock_fd= socket(AF_INET,SOCK_DGRAM,0);
if(td.sock_fd<0)
{
return 2;
}
//2.准备数据
memset(&td.server,0,sizeof(td.server));//初始化
inet_pton(AF_INET,td.serverip_.c_str(),&td.server.sin_addr);//串转地址
td.server.sin_family=AF_INET;
td.server.sin_port=htons(serverport);//主机转网络字节序
//3.需要绑定,OS自动绑定
pthread_t send,recv;
pthread_create(&send,nullptr,send_message,&td);
pthread_create(&recv,nullptr,recv_message,&td);
pthread_join(send,nullptr);
pthread_join(recv,nullptr);
return 0;
}
4.3 makefile
.PHONY:all
all: udpserver udpclient
udpclient:UdpClient.cpp
g++ -o $@ $^ -std=c++11 -lpthread
udpserver:Main.cpp
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f udpclient udpserver