网络基础和UDP函数的简单使用

网络发展

最开始,计算机是独立的个体,因为需求需要计算机之间交换数据,由局域网(私网)–>广域网(公网),网络就逐渐发展起来了。

初识协议

  • 协议就是一种约定

  • 网络协议就是众多协议中的一种,又称TCP/IP协议,由ISO(国际标准组织)制定

  • 协议的本质也是软件,而软件横向模块纵向分层

  • 软件分层的好处:
    解耦的有效方式,可维护性好(对于其他人阅读容易,改变该部分的代码不会影响其他部分)
    封装继承和多态就是分层的。
    任何问题的解决都可以通过增加一层软件层来解决

  • 看待协议两个视角:

  1. 小白视角:同层协议,直接通信
  2. 工程师:同层协议,没有直接通信

OSI七层模型(标准)

这是标准
在这里插入图片描述

TCP/IP五层(四层)模型

OSI与TCP/IP的关系
在这里插入图片描述

TCP/IP协议族

为什么?

冯诺依曼体系就是网络,不过就是硬件与硬件之间的距离很短。
主机A与主机B距离变长,就会存在一些问题?

  1. 数据怎么给路由器的? 物理层和链路层
  2. 怎么定位主机C的? 网络层
  3. 数据丢失怎么办? 传输层
  4. 发数据不是目的,而是手段,用数据才是目的。也就是说主机发出的数据,怎么被处理? 应用层

所以,是为什么呢?,就是因为通信距离变长而引起的一些问题

是什么?

这些问题,种类不同,性质不同–>协议分层
TCP/IP是这些问题的解决方案

怎么办?

网络与OS

在这里插入图片描述
网络是OS中的一个模块

协议本质是结构体,先描述,再组织。

  1. TCP/IP网络,是os的一个模块,os使用c语言写的,网络也是用c语言写的
  2. win&&Linux网络代码一定是一样的
    结构体类型一样吗?
    一样
    –>这样就实现了,不同操作系统之间的网络通信
    总结:协议就是双方都认识的结构化类型

网络传输的基本流程

局域网(以太网为例)

在这里插入图片描述
原理:就是所有主机都能收到以太网上的信息

mac地址

在这里插入图片描述

ifconfig //命令查看mac地址

mac地址可以唯一标识局域网中的主机。48比特位,6字节。

  • 如何判断报文是否是给自己的呢?
    在数据链路层完成,通过mac地址

数据碰撞

  • 以太网一段时间内,只允许有一份报文。
  • 如果有多份就会发生碰撞,导致数据不一致,这就是数据碰撞。
  • 碰撞域:局域网本身就是一个碰撞域
  • 以太网是公共资源,也就是临界资源–>互斥–>碰撞检测和碰撞避免
    怎么检测碰撞?
    电压,多份报文的电压会比一份报文的电压大。
    主机越多,发生碰撞的概率越大。

流程

  • 报文 = 报头+有效载荷
  • 有效载荷:
    在这里插入图片描述
    在这里插入图片描述
    报头的共性:
  1. 报头和有效载荷分离的问题
  2. 报头内部,必须包含一个字段,叫做交给上层谁的字段—分用
    解包:就是分离报头和有效载荷
    分用:
  • 为什么要封装?
    网卡是硬件,由os管理,所以必须贯穿协议帧
  • 网卡有数据,怎么办?
    网卡有数据,就会给os发送中断信号,继而执行相应的处理方法。

跨网络传输

  • 就是局域网与局域网之间的数据传输
  • IP地址 4字节 4个部分 每个部分0~255
    在这里插入图片描述
  • IP协议有IPV4和IPV6,但现在常用的是IPV4
  • IP地址可以标识唯一的主机

MAC VS IP

在进行跨网络传输时,有两套地址

  1. 不变,源IP地址—>目的IP地址
  2. 变化,源MAC地址–>目的MAC地址
    而局域网通信需要MAC地址–>只有局域网中有效
  • 跨网络通信,路由器–>路由算法 是根据目的IP地址进行路由(路径选择)的
    IP几乎不变

大概过程

在这里插入图片描述

  • 以太网
  • 令牌环:就是谁持有令牌,谁就可以在该局域网中通信,类似于锁
  • 路由器:主机认为路由器也是主机,工作在网络层,有两套驱动程序

为什么要把数据交给路由器?

首先自己的主机是由网络层的,也是有路由功能的
目的IP与主机IP不同,同一个局域网中IP类似,也就是不与主机在同一个局域网中,把数据交给路由器。
将数据交给路由器本质就是进行一次局域网通信
在这里插入图片描述

在这里插入图片描述
源IP与目的IP不变,源MAC和目的MAC改变
网络层全网同一层,拿到的报文是一样的,统一的。
IP以下不同,IP以上相同
这样就屏蔽了网络的底层差异。
即使局域网的实现类型多样,也能实现不同的局域网之间的通信。
在这里插入图片描述

Socket变成预备

源IP与目的IP的理解

就是用于两端主机的通信
在这里插入图片描述

数据到达主机不是目的,而是手段,而把数据交给进程才是目的
进程(用户)+网络(os)/主机—>网络(os)/主机+用户(对端用户)

端口号

在这里插入图片描述

  • 可以唯一标识某一主机的某一进程
    为2字节

  • 0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的
    端口号都是固定的.

  • 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作
    系统从这个范围分配的

  • IP+端口号–>标识全网的唯一 一个进程–>叫做socket套接字

  • 那么主机是如何知道IP和端口号的呢?
    IP:可以通过域名解析知道
    端口号:是内置的

  • PID和端口号都可以唯一标识一个进程,那么为什么要引入端口号呢?
    为了解耦

传输层

在这里插入图片描述

  • TCP协议
    • 传输层协议
    • 有连接
    • 可靠传输
    • 面向字节流
  • UDP 协议
    • 无连接
    • 不可靠传输
    • 面向数据报

面相字节序

每个机器的可能是大端的也可能是小端的。

  • 网路规定,所有发送到网络上的数据,都必须是大端的。
  • 先发出的数据是高位的,后发出的数据是低位的.
    原因:可能是高位是报头

字节序转换的系统调用

在这里插入图片描述
h:host
n: net
l: long
s: short

Socket的系统调用

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

bind , accept,connect都有struct sockaddr这个结构体。
在这里插入图片描述
in:inet 用于网络
un:unix 用于本地
socket的种类多一些

  1. 网络socket
  2. 本地socket(unix域间socket)–>本地(类似于管道)
    OS提供系统调用
    socket接口统一–>区分是本地还是网络–>有结构体最开始的16个公共字段来确定–>继承和多态

实现一个EchoServer的网络通信案例

用udp实现

Server_Udp.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <cstring>  //strerror
#include <cerrno> //errno
#include <unistd.h>
#include "Log.hpp"
#include "Comm.hpp"static const int default_sockfd = -1;
std::string default_ip = "127.0.0.1";
uint16_t default_port = 8080;
using namespace LogModule;
class Server
{public:Server(std::string ip = default_ip, uint16_t port = default_port): _sockfd(default_sockfd),_ip(ip),_port(port),_is_running(false){// 1.创建socket套接字// 返回值是文件描述符_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error";std::cerr << strerror(errno);Die(SOCKETERR);}LOG(LogLevel::INFO) << "socket success sockfd:" << _sockfd;// 2.填充网络信息并绑定// struct sockaddr_in local;// 清零bzero(&_local, sizeof(struct sockaddr_in));_local.sin_family = AF_INET;_local.sin_port = htons(_port);// local.sin_addr这是一个结构体,结构体只能整体初始化,不能整体赋值//_local.sin_addr.s_addr = inet_addr(_ip.c_str());//任意绑定_local.sin_addr.s_addr = INADDR_ANY;// 填充网络信息成功,但没有填充进内核// 设置进内核int n = bind(_sockfd, CONV(&_local), sizeof(struct sockaddr_in));if (n < 0){LOG(LogLevel::FATAL) << "bind error";std::cerr << strerror(errno);Die(BINDERR);}LOG(LogLevel::INFO) << "bind success ip:" << _ip << " port:" << std::to_string(_port);}~Server(){if(_sockfd>default_sockfd){close(_sockfd);}}void Start(){_is_running = true;while (true){char buffer[1024];struct sockaddr_in peer;socklen_t peer_len = sizeof(struct sockaddr_in);// flags 为 0(默认)阻塞int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&peer), &peer_len);if (n >= 0){buffer[n] = 0;char peer_ip[64];inet_ntop(AF_INET,&peer.sin_addr.s_addr,peer_ip,sizeof(peer_ip));uint16_t peer_port = ntohs(peer.sin_port);LOG(LogLevel::INFO) << "-" <<peer_ip<<":"<<peer_port  <<"#"<< buffer;std::string info = "echo client#";info += buffer;sendto(_sockfd, info.c_str(), info.size(), 0, CONV(&peer), peer_len);}else{std::cout<<"server error:";std::cerr<<strerror(errno);Die(SEV_RECV_ERR);}}}private:struct sockaddr_in _local;int _sockfd;std::string _ip;uint16_t _port;bool _is_running;
};

socket 创建套接字

在这里插入图片描述

在这里插入图片描述

  • socket用来创建套接字

  • domain:域
    在这里插入图片描述

  • type:
    在这里插入图片描述

  • protocol:0,这里不使用协议

  • 返回值:int是一个文件描述符
    在这里插入图片描述

填充网络信息,bind 绑定

在这里插入图片描述

  • struct sockaddr_in 的结构
/* Structure describing an Internet socket address.  */
struct sockaddr_in{__SOCKADDR_COMMON (sin_);in_port_t sin_port;			/* Port number.  */struct in_addr sin_addr;		/* Internet address.  *//* Pad to size of `struct sockaddr'.  */unsigned char sin_zero[sizeof (struct sockaddr)- __SOCKADDR_COMMON_SIZE- sizeof (in_port_t)- sizeof (struct in_addr)];};
#define	__SOCKADDR_COMMON(sa_prefix) \sa_family_t sa_prefix##family

##是连接的作用,结果就是 sin_family
sin_addr 是结构体,结构体只能整体初始化,不能整体赋值,

struct in_addr{in_addr_t s_addr;};

所以,赋值(填充)的是sin_addr中的s_addr.

inet_addr

在这里插入图片描述
将字符串风格的ip转为网络格式的ip
1.std::string—>4byte
2.network order(网络格式)

  • 命令
netstat -uapn

u:udp
a:all
p:进程
n:将LocalAdress转为数字形式

  • 固定绑定
    在这里插入图片描述
    在这里插入图片描述

  • 任意绑定
    在这里插入图片描述
    在这里插入图片描述

接收和发送消息

recvfrom和sendto用的是一个文件描述符,是全双工的,既可以发送,也可以接收。
在这里插入图片描述

recvfrom接收消息

在这里插入图片描述

  1. sockfd:文件描述符
  2. flags:0 (默认)阻塞的等待接收消息
  3. src_addr 和 addrlen都是输出型参数,表示发送端的网络信息
sendto

在这里插入图片描述

  1. flags: 0
  2. dest_addr和addr_len,表示要将信息发送到的主机的进程的网络信息。
CONV() 和Die()

是自己宏定义的一个强制类型转换和进程退出

#define CONV(ptr) ((struct sockaddr *)(ptr))#define Die(code)   \do              \{               \exit(code); \}               \while (0)       \;
inet_ntop

在这里插入图片描述
将网络格式的IP转为字符数组的形式

Server_Udp.cpp

#include "Server_Udp.hpp"
int main()
{Server s;s.Start();return 0;
}

Comm.hpp

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>#define Die(code)   \do              \{               \exit(code); \}               \while (0)       \;#define CONV(ptr) ((struct sockaddr *)(ptr))
enum
{SOCKETERR = 1,BINDERR,USAGEERR,SEV_RECV_ERR
};

Client_Udp.cpp

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <cerrno>
#include "Comm.hpp"
#include "Log.hpp"
using namespace LogModule;
int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Usage Error:" << "id port" << std::endl;Die(USAGEERR);}std::string ip = argv[1];uint16_t port = std::atoi(argv[2]);// 1.创建socket套接字int sockfd;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(LogLevel::FATAL) << "socket error";std::cerr << strerror(errno);Die(SOCKETERR);}LOG(LogLevel::INFO) << "socket success sockfd:" << sockfd;// 客户端不需要绑定?// client必须要有自己的ip和port//不需要显示调用bind,而是客户端首次sendto,由os自动调用bind//client自动随机bind  port     server显示bind//client一个端口号只能被一个进程bind,如果显示bind有可能port冲突//server服务器的端口号必须稳定!必须众所周知且不能轻易改变struct sockaddr_in local;socklen_t len = sizeof(struct sockaddr_in);local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = inet_addr(ip.c_str());while(true){std::cout<<"Please Enter# ";struct sockaddr_in peer;socklen_t peer_len;std::string s;std::cin>>s;sendto(sockfd,s.c_str(),s.size(),0,CONV(&local),len);std::cout<<"sento success"<<std::endl;char buffer[1024];int n = recvfrom(sockfd,buffer,sizeof(buffer),0,CONV(&peer),&peer_len);if(n>0){buffer[n] = 0;std::cout<<buffer<<std::endl;}}return 0;
}
  • 客户端不需要绑定?
    client必须要有自己的ip和port
    不需要显示调用bind,而是客户端首次sendto,由os自动调用bind
    client自动随机bind port server显示bind
    client一个端口号只能被一个进程bind,如果显示bind有可能port冲突
    server服务器的端口号必须稳定!必须众所周知且不能轻易改变
  1. 云服务器可能有多个IP,禁止用户bind公网IP
  2. 虚拟机可以bind你的任何IP

预期结果

在这里插入图片描述

Server_Udp.hpp的封装

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h> 
#include <memory.h>
#include <cstring>
#include <cerrno>
#include "Comm.hpp"
#include "Mutex.hpp"
#include "Log.hpp"
using namespace LogModule;
class InetAddr
{
private:void Ntohs(){_port = ntohs(_local.sin_port);}void Ntoa(){// _ip = inet_ntoa(&(_local.sin_addr));char buffer[64];inet_ntop(AF_INET,&(_local.sin_addr.s_addr),buffer,sizeof(buffer));_ip = buffer;}
public:InetAddr(){}InetAddr(uint16_t port):_port(port),_ip("0.0.0.0"){_local.sin_family = AF_INET;_local.sin_port = htons(_port);_local.sin_addr.s_addr = INADDR_ANY;//任意绑定}InetAddr(const struct sockaddr_in& addr):_local(addr){Ntohs();Ntoa();}~InetAddr(){}const struct sockaddr* Addr(){return CONV(&_local);}int In_Size(){return sizeof(struct sockaddr_in);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}
private:struct sockaddr_in _local;std::string _ip;uint16_t _port;
};

将网络信息和string风格的ip和host的port封装成一个InetAddr类,这样用起来就很方便了。

实现一个翻译的服务DictServer

基于上面EchoServer的代码,只需要为server添加一个业务就可以,这里添加业务的方式是通过回调

这是简单的字典
在这里插入图片描述

Dict.hpp

字典类

#pragma once
#include <iostream>
#include <fstream>
#include <unordered_map>
#include <cstring>
#include "Log.hpp"
std::string gpath = "./";
std::string gfilename = "Dictionary.txt";
std::string gseprator = ": ";
using namespace LogModule;
class Dictionary
{//分割单词和翻译bool Split(std::string &s){int pos = s.find(_seprator);if(pos==std::string::npos)  return false;std::string key = s.substr(0,pos);std::string val = s.substr(pos+_seprator.size());if(key.empty()||val.empty()){return false;}_dictionary[key] = val;return true;}bool LoadDictionary(){std::string file = _path + _filename;std::ifstream in(file);if (!in.is_open()){LOG(LogLevel::ERROR) << "open file " << file << "error";exit(1);}char inbuffer[64] = {0};while(in.getline(inbuffer,sizeof(inbuffer))){std::string line = inbuffer;//分割单词和翻译Split(line);memset(inbuffer, 0, sizeof(inbuffer));}in.close();return true;}public:Dictionary(std::string &path = gpath, std::string &filename = gfilename,std::string& seprator = gseprator): _path(path),_filename(filename),_seprator(seprator){//加载字典LoadDictionary();}//翻译std::string Translate(std::string& word){std::unordered_map<std::string, std::string>::iterator it = _dictionary.find(word);if(it==_dictionary.end()){return "None";}return it->second;}~Dictionary(){}private:std::string _path;std::string _filename;std::string _seprator;std::unordered_map<std::string, std::string> _dictionary;
};

Server_Udp.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <cstring>  //strerror
#include <cerrno> //errno
#include <unistd.h>
#include <functional>
#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"using func_t = std::function<std::string(std::string)>;
static const int default_sockfd = -1;
std::string default_ip = "127.0.0.1";
uint16_t default_port = 8080;
using namespace LogModule;
class Server
{public:Server(func_t func,uint16_t port = default_port): _sockfd(default_sockfd),_inet_addr(port),_is_running(false),_func(func){// 1.创建socket套接字// 返回值是文件描述符_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error";std::cerr << strerror(errno);Die(SOCKETERR);}LOG(LogLevel::INFO) << "socket success sockfd:" << _sockfd;bind(_sockfd,_inet_addr.Addr(),_inet_addr.In_Size());LOG(LogLevel::INFO) << "bind success ip:" << _inet_addr.Ip() << " port:" << _inet_addr.Port();// 2.填充网络信息并绑定// struct sockaddr_in local;// 清零// bzero(&_local, sizeof(struct sockaddr_in));// _local.sin_family = AF_INET;// _local.sin_port = htons(_port);// // local.sin_addr这是一个结构体,结构体只能整体初始化,不能整体赋值// //_local.sin_addr.s_addr = inet_addr(_ip.c_str());// //任意绑定// _local.sin_addr.s_addr = INADDR_ANY;// 填充网络信息成功,但没有填充进内核// 设置进内核//int n = bind(_sockfd, CONV(&_local), sizeof(struct sockaddr_in));// if (n < 0)// {//     LOG(LogLevel::FATAL) << "bind error";//     std::cerr << strerror(errno);//     Die(BINDERR);// }// LOG(LogLevel::INFO) << "bind success ip:" << _ip << " port:" << std::to_string(_port);}~Server(){if(_sockfd>default_sockfd){close(_sockfd);}}void Start(){_is_running = true;while (true){char buffer[1024];struct sockaddr_in peer;socklen_t peer_len = sizeof(struct sockaddr_in);// flags 为 0(默认)阻塞int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&peer), &peer_len);if (n >= 0){InetAddr cli(peer);buffer[n] = 0;std::string word = buffer;//char peer_ip[64];//inet_ntop(AF_INET,&peer.sin_addr.s_addr,peer_ip,sizeof(peer_ip));//uint16_t peer_port = ntohs(peer.sin_port);std::string trans = _func(word);//翻译LOG(LogLevel::INFO) << "-" <<cli.Ip()<<":"<<cli.Port()  <<"#"<< buffer;// std::string info = "echo client#";// info += buffer;sendto(_sockfd, trans.c_str(), trans.size(), 0, CONV(&peer), peer_len);}else{std::cout<<"server error:";std::cerr<<strerror(errno);Die(SEV_RECV_ERR);}}}private:InetAddr _inet_addr;//struct sockaddr_in _local;int _sockfd;//std::string _ip;//uint16_t _port;bool _is_running;func_t _func;//翻译
};

在这里插入图片描述

  • 需要一个传一个函数类,这个类的调用需要完成相应的翻译任务
    在这里插入图片描述
  • 收到消息后翻译
    在这里插入图片描述

Server_Udp.cpp

#include "Server_Udp.hpp"
#include "Dict.hpp"
int main()
{Dictionary dict;Server s([&dict](std::string word){return dict.Translate(word);});s.Start();return 0;
}

预期效果

在这里插入图片描述

简单实现一个聊天室服务ChatServer

在这里插入图片描述

User.hp

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <list>
#include <algorithm>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
using namespace LogModule;
class UserInterface
{
public:virtual ~UserInterface() = default;virtual void SendTo(int sockfd,std::string& message) = 0;
};
//一个用户
class User : public UserInterface
{
public:User(InetAddr &id):_id(id){}~User(){}//将信息发送给这个用户void SendTo(int sockfd,std::string& message) override{sendto(sockfd, message.c_str(),message.size(),0,_id.Addr(),_id.In_Size());}bool operator==(InetAddr& id){if(Sock()==id.Sock())return true;return false;}std::string Sock(){return _id.Sock();}
private:InetAddr _id;
};//管理所有用户
class UserManager
{
public:UserManager(){}//添加用户void AddUser(InetAddr& id){//list是临界资源需要加锁LockGuard lockguard(_mutex);for(auto& e:_list){if(e==id){        LOG(LogLevel::INFO)<<"用户已存在 "<<id.Sock();return;}}User user(id);LOG(LogLevel::INFO)<<"添加新用户 "<<id.Sock();_list.push_front(user);}//路由 就是把消息转发给所有用户//路由的本质就是遍历list并将信息发给每个用户void Router(int sockfd,std::string& message){for(auto& u:_list){LOG(LogLevel::INFO)<<"message send to "<<u.Sock();u.SendTo(sockfd,message);}}//删除用户void DelUser(InetAddr& id){auto pos = remove_if(_list.begin(),_list.end(),[&id](User& user){return user==id;});LOG(LogLevel::INFO)<<"删除用户"<<id.Sock();_list.erase(pos,_list.end());}~UserManager(){}
private:std::list<User> _list;//用链表管理所有用户Mutex _mutex;
};

具体思想:就是先描述再组织
描述一个用户,然后再用一个结构去管理用户

Server_Udp.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <cstring> //strerror
#include <cerrno>  //errno
#include <unistd.h>
#include <functional>
#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
static const int default_sockfd = -1;
std::string default_ip = "127.0.0.1";
uint16_t default_port = 8080;using add_t = std::function<void(InetAddr &)>;
using router_t = std::function<void(int sockfd, std::string &message)>;
using del_t = std::function<void(InetAddr &)>;
using namespace LogModule;
using namespace ThreadPoolModule;
using task_t = std::function<void()>;
class Server
{public:Server(add_t adduser, router_t router, del_t deluser, uint16_t port = default_port): _sockfd(default_sockfd),_inet_addr(port),_is_running(false),_adduser(adduser),_router(router),_deluser(deluser){// 1.创建socket套接字// 返回值是文件描述符_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error";std::cerr << strerror(errno);Die(SOCKETERR);}LOG(LogLevel::INFO) << "socket success sockfd:" << _sockfd;bind(_sockfd, _inet_addr.Addr(), _inet_addr.In_Size());LOG(LogLevel::INFO) << "bind success ip:" << _inet_addr.Ip() << " port:" << _inet_addr.Port();// 2.填充网络信息并绑定// struct sockaddr_in local;// 清零// bzero(&_local, sizeof(struct sockaddr_in));// _local.sin_family = AF_INET;// _local.sin_port = htons(_port);// // local.sin_addr这是一个结构体,结构体只能整体初始化,不能整体赋值// //_local.sin_addr.s_addr = inet_addr(_ip.c_str());// //任意绑定// _local.sin_addr.s_addr = INADDR_ANY;// 填充网络信息成功,但没有填充进内核// 设置进内核// int n = bind(_sockfd, CONV(&_local), sizeof(struct sockaddr_in));// if (n < 0)// {//     LOG(LogLevel::FATAL) << "bind error";//     std::cerr << strerror(errno);//     Die(BINDERR);// }// LOG(LogLevel::INFO) << "bind success ip:" << _ip << " port:" << std::to_string(_port);}~Server(){if (_sockfd > default_sockfd){close(_sockfd);}}void Start(){_is_running = true;while (true){char buffer[1024];struct sockaddr_in peer;socklen_t peer_len = sizeof(struct sockaddr_in);// flags 为 0(默认)阻塞int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&peer), &peer_len);if (n >= 0){InetAddr cli(peer);buffer[n] = 0;LOG(LogLevel::INFO)<<cli.Sock()<<"# "<<buffer;std::string message = buffer;if (strcmp(buffer, "quit") == 0){_deluser(cli); // 删除用户message = cli.Sock() + "....走了,你们聊";//_router(_sockfd, message);}else{_adduser(cli);            // 添加用户//_router(_sockfd, message); // 路由给所有在线用户}task_t task = std::bind(_router,_sockfd,message);ThreadPool<task_t>::GetInstance()->Equeue(task);// char peer_ip[64];// inet_ntop(AF_INET,&peer.sin_addr.s_addr,peer_ip,sizeof(peer_ip));// uint16_t peer_port = ntohs(peer.sin_port);//  LOG(LogLevel::INFO) << "-" <<cli.Ip()<<":"<<cli.Port()  <<"#"<< buffer;//  std::string info = "echo client#";//  info += buffer;//  sendto(_sockfd, info.c_str(), info.size(), 0, CONV(&peer), peer_len);}else{std::cout << "server error:";std::cerr << strerror(errno);Die(SEV_RECV_ERR);}}}private:InetAddr _inet_addr;// struct sockaddr_in _local;int _sockfd;// std::string _ip;// uint16_t _port;bool _is_running;add_t _adduser;router_t _router;del_t _deluser;
};
  1. 需要给服务器添加路由,增删用户的功能
    在这里插入图片描述
  2. 将路由当做任务,交给线程池来处理
    在这里插入图片描述
    在这里插入图片描述

Server_Udp.cpp

#include "Server_Udp.hpp"
#include "User.hpp"
using namespace ThreadPoolModule;
int main()
{ThreadPool<task_t>::GetInstance()->Start();UserManager um;//添加用户Server s([&um](InetAddr& id){um.AddUser(id);},[&um](int sockfd,std::string& message){um.Router(sockfd,message);},[&um](InetAddr& id){um.DelUser(id);});s.Start();return 0;
}

Client_Udp.cpp

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <cerrno>
#include <pthread.h>
#include <unistd.h>
#include "Comm.hpp"
#include "Log.hpp"
using namespace LogModule;
int sockfd;
void *Recver(void *args)
{while (true){struct sockaddr_in peer;socklen_t peer_len;char buffer[1024];int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, CONV(&peer), &peer_len);if (n > 0){buffer[n] = 0;std::cerr << buffer << std::endl;}}
}
int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Usage Error:" << "id port" << std::endl;Die(USAGEERR);}std::string ip = argv[1];uint16_t port = std::atoi(argv[2]);pthread_t tid;pthread_create(&tid, nullptr, Recver, nullptr);// 1.创建socket套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(LogLevel::FATAL) << "socket error";std::cerr << strerror(errno);Die(SOCKETERR);}// LOG(LogLevel::INFO) << "socket success sockfd:" << sockfd;//  客户端不需要绑定?//  必须要有自己的ip和port// 不需要显示调用bind,而是客户端首次sendto,由os自动调用bindstruct sockaddr_in local;socklen_t len = sizeof(struct sockaddr_in);local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = inet_addr(ip.c_str());// 刚登上就上线,不需要发完消息后才能上线std::string message = "...来了";sendto(sockfd, message.c_str(), message.size(), 0,CONV(&local), len);while (true){std::cout << "Please Enter# ";std::string s;std::cin >> s;int n = sendto(sockfd, s.c_str(), s.size(), 0, CONV(&local), len);if (n < 0){std::cout << "send error" << std::endl;std::cerr << strerror(errno) << std::endl;}if (s == "quit"){exit(0);}// std::cout<<"sento success"<<std::endl;//  char buffer[1024];//  int n = recvfrom(sockfd,buffer,sizeof(buffer),0,CONV(&peer),&peer_len);//  if(n>0)//  {//      buffer[n] = 0;//      std::cout<<buffer<<std::endl;//  }}return 0;
}

相较于以前收消息交给另一个线程来完成
在这里插入图片描述

预期结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/35764.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Netty入门教程——认识Netty

Netty入门教程——认识Netty 什么是Netty&#xff1f; Netty 是一个利用 Java 的高级网络的能力&#xff0c;隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。 Netty 是一个广泛使用的 Java 网络编程框架&#xff08;Netty 在 2011 年获得了Duke’s Choice …

调用大模型api 批量处理图像 保存到excel

最近需要调用大模型&#xff0c;并将结果保存到excel中&#xff0c;效果如下&#xff1a; 代码&#xff1a; import base64 from zhipuai import ZhipuAI import os import pandas as pd from openpyxl import Workbook from openpyxl.drawing.image import Image from io i…

Python基于TensorFlow实现BP和LSTM神经网络的空气质量预测并使用SHAP解释模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后关注获取。 1.项目背景 随着工业化进程的加速和城市化的扩展&#xff0c;空气污染成为全球面临的主要环境问题之一。空气质…

高效查找秘密武器一:位图

有这样的一个问题&#xff1a; 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数 中。 那么我们一般会想到这样做的 1.遍历&#xff0c;时间复杂度O(n) 2.排序&#xff08;N*logN&#xff09;&#xff0c…

对于小型企业,独立站和电商平台哪个更经济?

对于小型企业来说&#xff0c;选择独立站还是电商平台&#xff0c;需要根据各自的成本优势来决定。以下是一些关键点的比较&#xff1a; 平台费用&#xff1a; 电商平台&#xff1a;通常需要缴纳一定比例的交易佣金或年费&#xff0c;例如天猫、京东等平台的保证金和佣金费用相…

带权并查集和扩展域并查集的一些整理和理解(上)

请读者在有一定并查集基础上再阅读&#xff08;至少要知道什么是带权和扩展域并查集&#xff09; 在最近做题时候&#xff0c;我遇到了一些带权并查集和扩展域并查集的题目。我觉得它们都很难写也很难想&#xff0c;尤其是带权并查集我几乎第一时间无法想到是怎么做的&#xf…

json+Tomact项目报错怎么办?

在响应请求的时候&#xff0c;如果http响应没有指定响应数据的content-type&#xff0c;浏览器就不知道按照什么格式解析响应体的数据&#xff0c;因为浏览器只知道怎样解析http的行和头&#xff0c;再从头里获取响应体的字节长度和类型&#xff0c;按照你给的长度去截流&#…

极限激光雷达点云数据集

https://arxiv.org/pdf/2307.07607v5 ‎ - AirLab 他们的数据集里面有这么多极限场景 点云数据转换 他们的激光用的velodyne,录制的格式是【velodyne_msgs/VelodyneScan】 需要把【velodyne_msgs/VelodyneScan】转化成【sensor_msgs/PointCloud2】 我编译https://github.co…

信奥常考点:二叉树的构建(已知中序和 前序或后序 的情况下)

一、题目引入 这是来自CCF-GESP C七级认证 2024年9月的题目。 我们在此不解题&#xff0c;只把树画出来。 CCF-GESP 编程能力认证 C 七级 2024年9月份详细解析-CSDN博客 二、解题过程 我们可以根据先序遍历得出根节点是A&#xff0c;然后我们得到了A的左子树[B D]&#xff08;橙…

电容的概念和基本参数

电容基本概念 电容最简单的结构可由两个相互靠近的导体平面中间夹一层绝缘介质组成&#xff0c;当在电容两个极板间加上电压时&#xff0c;电容就会储存电荷&#xff0c;所以电容是一个充放电荷的电子元器件。电容量是电容储存电荷多少的一个量值&#xff0c;平板电容的电容量…

【js逆向专题】13.jsvmp补环境篇一

目录 一.了解jsvmp技术1. js虚拟机保护方案2.jsvmp实现原理3. 模拟jsvmp执行过程 二.环境检测1. 什么是环境检测2.案例讲解 三. 项目实战1. 案例11.逆向目标2. 项目分析1.补第一个referrer2. 调试技巧13. 调试技巧24. 补充sign5. 补 length6. 参数长短补充 3. 逆向结果 2. 案例…

高质量翻译在美国推广移动应用中的重要性

美国的移动应用市场是世界上竞争最激烈、利润最高的市场之一&#xff0c;为开发者提供了接触数百万潜在用户的机会。然而&#xff0c;进入这个市场需要的不仅仅是创新技术或令人信服的想法&#xff1b;它要求与目标受众进行有效地沟通和文化契合。在这个过程中&#xff0c;高质…

[Redis#17] 主从复制 | 拓扑结构 | 复制原理 | 数据同步 | psync

目录 主从模式 主从复制作用 建立主从复制 主节点信息 从节点信息 断开主从复制关系 主从拓扑结构 主从复制原理 1. 复制过程 2. 数据同步&#xff08;PSYNC&#xff09; 3. 三种复制方式 一、全量复制 二、部分复制 三、实时复制 四、主从复制模式存在的问题 在…

【Unity高级】如何动态调整物体透明度

本文介绍了如何设置及动态调整物体的透明度。 一、手动设置的方法 我们先来看下如何手动设置物体的透明度。 物体的透明与否是通过材质来设置的。只有我们把具有透明度的材质指给物体的渲染器&#xff08;Render&#xff09;&#xff0c;物体就被设置成相应的透明度了。 看一…

相机动态/在线标定

图1 图2 基本原理 【原理1】平行线在射影变换后会交于一点。如图所示,A为相机光心,蓝色矩形框为归一化平面,O为平面中心。地面四条黄色直线为平行且等距的车道线。HI交其中两条车道线于H、I, 过G作HI的平行线GM交车道线于M。HI、GM在归一化平面上的投影分别为JK、PN,二者会…

通俗易懂理解:网络安全恶意节点的检测与哨兵节点的激活【论文+代码】

以下资料参考来自本文末尾的参考资料与代码&#xff1a; 在网络安全中&#xff0c;恶意节点检测和哨兵节点激活是确保网络稳定性、可靠性和安全性的关键技术&#xff0c;尤其是在分布式系统、物联网 (IoT)、区块链网络等环境中。下面将详细介绍这两个概念及其应用。 一、恶意…

python作业

1.D 2.B 3.D 4.C 5.B 6.D 7.D 8.B 9.D 10. A 11.D 12.C 13.√ 14.√ 16.√ 17.√ 18.None 19.([1,3],[2]) 20. 列表思维导图

Redis(上)

Redis 基础 什么是 Redis&#xff1f; Redis &#xff08;REmote DIctionary Server&#xff09;是一个基于 C 语言开发的开源 NoSQL 数据库&#xff08;BSD 许可&#xff09;。与传统数据库不同的是&#xff0c;Redis 的数据是保存在内存中的&#xff08;内存数据库&#xf…

LabVIEW气缸摩擦力测试系统

基于LabVIEW的气缸摩擦力测试系统实现了气缸在不同工作状态下摩擦力的快速、准确测试。系统由硬件平台和软件两大部分组成&#xff0c;具有高自动化、精确测量和用户友好等特点&#xff0c;可广泛应用于精密机械和自动化领域。 ​ 项目背景&#xff1a; 气缸作为舵机关键部件…

CentOS7.X 安装RustDesk自建服务器实现远程桌面控制

参照文章CentOS安装RustDesk自建服务器中间总有几个位置出错&#xff0c;经实践做个记录防止遗忘 一 环境&工具准备 1.1 阿里云轻量服务器、Centos7系统、目前最高1.1.11版本rustdesk-server-linux-amd64.zip 1.2 阿里云轻量服务器–安全组–开放端口&#xff1a;TCP(21…