一、介绍
前面我们一步步模拟实现了一个简单的udp服务器和客户端,通过这个服务器,我们简单实现一个群聊的功能,本篇是专门用来记录代码的,详细的实现思路可以去参考我其他两篇,Socket编程(一)和Socket编程(二)
二、代码
udp_server.cc
#include"udp_server.hpp"
#include<string>
#include<memory>
#include<cstdio>
#include<ctype.h>
using namespace std;
using namespace chk;
// 我们最终希望以 ./udp_server port 的形式去启动
static void usage(string proc)//使用手册
{
std::cout << "Usage:\n\t" << proc << " port\n" << std::endl;
}
int main(int argc,char* argv[])
{
if(argc != 2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr<UdpServer> usvr(new UdpServer(port));
usvr->start();
return 0;
}
udp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include "errno.hpp"
#include <cstring>
#include <functional>
#include <unordered_map>
#include "RingQueue.hpp"
#include "Thread.hpp"
#include <vector>
#include "mylock.hpp"
namespace chk
{
const static uint16_t default_port = 8080;
using func_t = std::function<std::string(std::string)>;//定义函数指针
class UdpServer
{
public:
// 对成员变量完成初始化
UdpServer(uint16_t port = default_port) : _port(port)
{
std::cout << "server port: " << _port << std::endl;
pthread_mutex_init(&lock,nullptr);
p = new Thread(1,std::bind(&UdpServer::Rev,this));
c = new Thread(2,std::bind(&UdpServer::Broadcast,this));
}
void start() // 创建出套接字,并绑定端口号和ip
{
// 1. 创建套接字
_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (_sock < 0)
{
std::cerr << "create socket error: " << strerror(errno) << std::endl;
exit(SOCKET_ERR);
}
std::cout << "bind socket success: " << _sock << std::endl; // 3
// 2. 构建struct sockaddr_in结构体
struct sockaddr_in local;
bzero(&local, sizeof(local)); // 初始化
local.sin_family = AF_INET; // IPv4
local.sin_port = htons(_port); // 端口号
local.sin_addr.s_addr = INADDR_ANY; // 服务器下的ip
// 3. 绑定套接字和sockaddr_in
int n = bind(_sock, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
std::cerr << "bind error: " << strerror(errno) << std::endl;
exit(BIND_ERR);
}
std::cout << "bind socket success: " << _sock << std::endl;
p->run();
c->run();
}
void AddUser(const std::string &name, const struct sockaddr_in &peer)
{
LockGuard lockgurad(&lock);
auto iter = onlineuser.find(name);
if(iter != onlineuser.end())
return;//找到了则不需要作为新用户添加
onlineuser.insert(std::pair<const std::string,const struct sockaddr_in>(name,peer));
}
void Rev() // 接受信息,并且创建和添加在线用户信息
{
char buffer[1024];
while(true)
{
//1. 收数据
struct sockaddr_in peer; // 客户端信息
socklen_t len = sizeof(peer);
int n = recvfrom(_sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(n>0) buffer[n] = '\0';
else continue;
//2. 收到信息后打印出来:对方ip+端口号+内容
std::cout << inet_ntoa(peer.sin_addr) << " - " << ntohs(peer.sin_port) << " : " << buffer << std::endl;
//3. 对用户的个人信息构建和添加到在线列表中
std::string name = inet_ntoa(peer.sin_addr);
name += " - ";
name += std::to_string(ntohs(peer.sin_port));
AddUser(name,peer);
std::string msg = name + " >> " + buffer;
rq.push(msg);
//sendto(_sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));
}
}
void Broadcast()
{
while(true)
{
std::string msg;
rq.pop(&msg);
std::vector<struct sockaddr_in> v;//每次广播先将要发送的用户列表进行导出
{
LockGuard lockgurad(&lock);
for(auto user: onlineuser)
{
v.push_back(user.second);
}
}
for(auto user:v) // 将信息广播
{
sendto(_sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&user,sizeof(user));
}
}
}
~UdpServer() // 析构
{
pthread_mutex_destroy(&lock);
c->join();
p->join();
delete c;
delete p;
}
private:
int _sock;
uint16_t _port;
func_t _service; // 信息处理方法
std::unordered_map<std::string,struct sockaddr_in> onlineuser;//在线用户信息
RingQueue<std::string> rq;//消息队列
pthread_mutex_t lock; // 保护用户在线列表的互斥锁
Thread* c;
Thread* p;
};
}
udp_cilent.hpp
#pragma once
#include<iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<cstring>
#include<string>
#include"errno.hpp"
udp_cilent.cc
#include"udp_client.hpp"//基本要用到的头文件
using namespace std;
#include<pthread.h>
static void usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " serverip serverport\n" << std::endl;
}
void* recver(void *args)
{
int sock = *(static_cast<int*>(args));
while(true)
{
// 接受返回的信息
char buffer[1024];
struct sockaddr_in temp;
socklen_t 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;
}
}
}
// ./udp_client server_ip server_port
int main(int argc,char* argv[])
{
if(argc != 3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
string server_ip = argv[1];
uint16_t server_port = atoi(argv[2]);
//1. 创建套接字
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0)
{
cerr << "client : create socket error" << endl;
exit(SOCKET_ERR);
}
//2. 创建server端的struct sockaddr
struct sockaddr_in server;
memset(&server,0,sizeof(server));//初始化方案2
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
server.sin_port = htons(server_port);
//3. 客户端测试
// 这里需要多线程进行,一个线程发消息,一个线程进行实时的收广播消息
pthread_t tid;
pthread_create(&tid,nullptr,recver,&sock);
while(true)
{
// 用户发送消息
string messages;
cerr << "client : " ;
getline(cin,messages);
// 发送到sock
sendto(sock,messages.c_str(),messages.size(),0,(struct sockaddr*)&server,sizeof(server));
}
return 0;
}#include"udp_client.hpp"//基本要用到的头文件
using namespace std;
#include<pthread.h>
static void usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " serverip serverport\n" << std::endl;
}
void* recver(void *args)
{
int sock = *(static_cast<int*>(args));
while(true)
{
// 接受返回的信息
char buffer[1024];
struct sockaddr_in temp;
socklen_t 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;
}
}
}
// ./udp_client server_ip server_port
int main(int argc,char* argv[])
{
if(argc != 3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
string server_ip = argv[1];
uint16_t server_port = atoi(argv[2]);
//1. 创建套接字
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0)
{
cerr << "client : create socket error" << endl;
exit(SOCKET_ERR);
}
//2. 创建server端的struct sockaddr
struct sockaddr_in server;
memset(&server,0,sizeof(server));//初始化方案2
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
server.sin_port = htons(server_port);
//3. 客户端测试
// 这里需要多线程进行,一个线程发消息,一个线程进行实时的收广播消息
pthread_t tid;
pthread_create(&tid,nullptr,recver,&sock);
while(true)
{
// 用户发送消息
string messages;
cerr << "client : " ;
getline(cin,messages);
// 发送到sock
sendto(sock,messages.c_str(),messages.size(),0,(struct sockaddr*)&server,sizeof(server));
}
return 0;
}
errno.hpp
#pragma once
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR
};
mylock.hpp
#pragma once
#include<pthread.h>
#include<iostream>
class Mutex
{
public:
Mutex(pthread_mutex_t* pm):_pm(pm)
{}
void lock()
{
pthread_mutex_lock(_pm);
}
void unlock()
{
pthread_mutex_unlock(_pm);
}
~Mutex()
{}
private:
pthread_mutex_t* _pm;
};
class LockGuard
{
public:
LockGuard(pthread_mutex_t* pm):_mutex(pm)
{
_mutex.lock();
}
~LockGuard()
{
_mutex.unlock();
}
private:
Mutex _mutex;
};
RingQueue.hpp
#pragma once
#include<iostream>
#include<vector>
#include<semaphore.h>
#include<pthread.h>
static int N = 5;
template<class T>
class RingQueue
{
private:
//封装一下PV操作
void P(sem_t& sem)
{
sem_wait(&sem);
}
void V(sem_t& sem)
{
sem_post(&sem);
}
void Lock(pthread_mutex_t& m)
{
pthread_mutex_lock(&m);
}
void Unlock(pthread_mutex_t& m)
{
pthread_mutex_unlock(&m);
}
public:
RingQueue(int num = N):_cap(num),_ring(num)
{
sem_init(&_sem_consumer,0,0);
sem_init(&_sem_producer,0,num);
pthread_mutex_init(&_mutex_consumer,nullptr);
pthread_mutex_init(&_mutex_producer,nullptr);
_p_step = _c_step = 0;
}
void push(const T& in)
{
//生产
P(_sem_producer);
//先申请再锁,能够更好的提高效率
Lock(_mutex_producer);
//一定有对应的空间资源给我
_ring[_p_step++] = in;
_p_step %= _cap;
V(_sem_consumer);
Unlock(_mutex_producer);
}
void pop(T* out)
{
//消费
P(_sem_consumer);
Lock(_mutex_consumer);
*out = _ring[_c_step++];
_c_step %= _cap;
V(_sem_producer);
Unlock(_mutex_consumer);
}
~RingQueue()
{
sem_destroy(&_sem_consumer);
sem_destroy(&_sem_producer);
pthread_mutex_destroy(&_mutex_consumer);
pthread_mutex_destroy(&_mutex_producer);
}
private:
std::vector<T> _ring;
int _cap;
sem_t _sem_consumer;
sem_t _sem_producer;
int _c_step;
int _p_step;//生产者下标
//维护多生产者多消费者的互斥关系,生产和生产的互斥,消费和消费的互斥
pthread_mutex_t _mutex_consumer;
pthread_mutex_t _mutex_producer;
};
makefile
.PHONY:all
all: udp_client udp_server
udp_client:udp_client.cc
g++ -o $@ $^ -std=c++11 -lpthread
udp_server:udp_server.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f udp_client udp_server.PHONY:all
all: udp_client udp_server
udp_client:udp_client.cc
g++ -o $@ $^ -std=c++11 -lpthread
udp_server:udp_server.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f udp_client udp_server
Thread.hpp
#pragma once
#include<iostream>
#include<pthread.h>
#include<string>
#include<stdlib.h>
using namespace std;
class Thread
{
public:
typedef enum//线程状态
{
NEW = 0,
RUNNING,
EXITED
}ThreadStatu;
//typedef void (*func_t)(void*);//函数指针
using func_t = std::function<void ()>;
public:
Thread(int num,func_t func):_tid(0),_status(NEW),_func(func)
{
char name[128];
snprintf(name,sizeof(name),"thread-%d",num);
_name = name;
}
int status() { return _status; }
string threadname() { return _name; }
pthread_t threadid()
{
if(_status == RUNNING)
{
return _tid;
}
else
{
return 0;
}
}
void operator()()// 仿函数,让线程执行任务的
{
if(_func != nullptr) _func();
}
static void* runHelper(void* args)//注意,这里的args和用户传入的args不一样,这里是this指针,用于调用函数的
{
Thread* pt = (Thread*)args;
(*pt)();
return nullptr;
}
void run()//启动线程
{
int n = pthread_create(&_tid,nullptr,runHelper,this);
if(n != 0) exit(1);
_status = RUNNING;
}
void join()//这里设计简单一点,默认不需要获取函数的返回值
{
int n = pthread_join(_tid,nullptr);
if(n!=0)
{
cerr << "join error" << endl;
return;
}
_status = EXITED;
}
~Thread()
{}
private:
pthread_t _tid;//线程id
string _name;
func_t _func; //线程未来要执行的回调
ThreadStatu _status;
void* _args;
};