目录
[1 -> V3版本-实现简单聊天室](#1 -> V3版本-实现简单聊天室)
1 -> V3版本-实现简单聊天室
UdpServer.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <pthread.h>
// #include <mutex>
// #include <condition_variable>
#include "nocopy.hpp"
#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
const static uint16_t defaultport = 8888;
const static int defaultfd = -1;
const static int defaultsize = 1024;
using task_t = std::function<void()>;
// using cb_t = std::function<std::string(std::string)>; // 定义了一个函数类型
// 聚焦在 IO 上
class UdpServer : public nocopy
{
public:
// UdpServer(cb_t OnMessage, uint16_t port = defaultport)
// : _port(port), _sockfd(defaultfd),
_OnMessage(OnMessage)
UdpServer(uint16_t port = defaultport) : _port(port),
_sockfd(defaultfd)
{
pthread_mutex_init(&_user_mutex, nullptr);
}
void Init()
{
// 1. 创建 socket,就是创建了文件细节
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
lg.LogMessage(Fatal, "socket errr, %d : %s\n", errno,
strerror(errno));
exit(Socket_Err);
}
lg.LogMessage(Info, "socket success, sockfd: %d\n",
_sockfd);
// 2. 绑定,指定网络信息
struct sockaddr_in local;
bzero(&local, sizeof(local)); // memset
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY; // 0
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. 4字节 IP 2. 变成网络序列
// 结构体填完,设置到内核中了吗??没有
int n = ::bind(_sockfd, (struct sockaddr*)&local,
sizeof(local));
if (n != 0)
{
lg.LogMessage(Fatal, "bind errr, %d : %s\n", errno,
strerror(errno));
exit(Bind_Err);
}
ThreadPool<task_t>::GetInstance()->Start();
}
void AddOnlineUser(InetAddr addr)
{
LockGuard lockguard(&_user_mutex);
for (auto& user : _online_user)
{
if (addr == user)
return;
}
_online_user.push_back(addr);
lg.LogMessage(Debug, "%s:%d is add to onlineuser
list...\n", addr.Ip().c_str(), addr.Port());
}
void Route(int sock, const std::string& message)
{
LockGuard lockguard(&_user_mutex);
for (auto& user : _online_user)
{
sendto(sock, message.c_str(), message.size(), 0,
(struct sockaddr*)&user.GetAddr(), sizeof(user.GetAddr()));
lg.LogMessage(Debug, "server send message to %s:%d,
message: % s\n", user.Ip().c_str(), user.Port(), message.c_str());
}
}
void Start()
{
// 服务器永远不退出
char buffer[defaultsize];
for (;;)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 不能乱写
ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) -
1, 0, (struct sockaddr*)&peer, &len);
if (n > 0)
{
InetAddr addr(peer);
AddOnlineUser(addr);
buffer[n] = 0;
std::string message = "[";
message += addr.Ip();
message += ":";
message += std::to_string(addr.Port());
message += "]# ";
message += buffer;
task_t task = std::bind(&UdpServer::Route, this,
_sockfd, message);
ThreadPool<task_t>::GetInstance()->Push(task);
// 处理消息
// std::string response = _OnMessage(buffer);
// std::cout << "[" << addr.PrintDebug() << "]# "<< buffer << std::endl;
// sendto(_sockfd, response.c_str(),
response.size(), 0, (struct sockaddr*)&peer, len);
}
}
}
~UdpServer()
{
pthread_mutex_destroy(&_user_mutex);
}
private:
// std::string _ip; // 后面要调整
uint16_t _port;
int _sockfd;
std::vector<InetAddr> _online_user; // 会被多个线程同时访问的
pthread_mutex_t _user_mutex;
// cb_t _OnMessage; // 回调
};
引入线程池
InetAddr.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class InetAddr
{
public:
InetAddr(struct sockaddr_in& addr) :_addr(addr)
{
_port = ntohs(_addr.sin_port);
_ip = inet_ntoa(_addr.sin_addr);
}
std::string Ip()
{
return _ip;
}
uint16_t Port()
{
return _port;
};
std::string PrintDebug()
{
std::string info = _ip;
info += ":";
info += std::to_string(_port); // "127.0.0.1:4444"
return info;
}
const struct sockaddr_in& GetAddr()
{
return _addr;
}
bool operator == (const InetAddr& addr)
{
//other code
return this->_ip == addr._ip && this->_port == addr._port;
}
~InetAddr() {}
private:
std::string _ip;
uint16_t _port;
struct sockaddr_in _addr;
};
在InetAddr中,重载一下==方便对用户是否是同一个进行比较。
UdpClient.hpp
cpp
#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Thread.hpp"
#include "InetAddr.hpp"
void Usage(const std::string& process)
{
std::cout << "Usage: " << process << " server_ip server_port"
<< std::endl;
}
class ThreadData
{
public:
ThreadData(int sock, struct sockaddr_in& server) :
_sockfd(sock), _serveraddr(server)
{
}
~ThreadData()
{
}
public:
int _sockfd;
InetAddr _serveraddr;
};
void RecverRoutine(ThreadData& td)
{
char buffer[4096];
while (true)
{
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t n = recvfrom(td._sockfd, buffer, sizeof(buffer) -
1, 0, (struct sockaddr*)&temp, &len); // 一般建议都是要填的.
if (n > 0)
{
buffer[n] = 0;
std::cerr << buffer << std::endl; // 方便一会查看效果
}
else
break;
}
}
// 该线程只负责发消息
void SenderRoutine(ThreadData& td)
{
while (true)
{
// 我们要发的数据
std::string inbuffer;
std::cout << "Please Enter# ";
std::getline(std::cin, inbuffer);
auto server = td._serveraddr.GetAddr();
// 我们要发给谁呀?server
ssize_t n = sendto(td._sockfd, inbuffer.c_str(),
inbuffer.size(), 0, (struct sockaddr*)&server, sizeof(server));
if (n <= 0)
std::cout << "send error" << std::endl;
}
}
// ./udp_client server_ip server_port
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 1;
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 1. 创建 socket
// udp 是全双工的。既可以读,也可以写,可以同时读写,不会多线程读写的问题
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "socket error: " << strerror(errno) <<
std::endl;
return 2;
}
std::cout << "create socket success: " << sock << std::endl;
// 2. client 要不要进行 bind? 一定要 bind 的!!但是,不需要显示bind,client 会在首次发送数据的时候会自动进行 bind
// 为什么?server 端的端口号,一定是众所周知,不可改变的,client 需要 port,bind 随机端口.
// 为什么?client 会非常多.
// client 需要 bind,但是不需要显示 bind,让本地 OS 自动随机 bind,选择随机端口号
// 2.1 填充一下 server 信息
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());
ThreadData td(sock, server);
Thread<ThreadData> recver("recver", RecverRoutine, td);
Thread<ThreadData> sender("sender", SenderRoutine, td);
recver.Start();
sender.Start();
recver.Join();
sender.Join();
close(sock);
return 0;
}
- UDP协议支持全双工,一个sockfd,既可以读取,又可以写入,对于客户端和服务端同样如此。
- 多线程客户端,同时读取和写入。
- 测试的时候,使用管道进行演示。
感谢各位大佬支持!!!
互三啦!!!