网络发展
最开始,计算机是独立的个体,因为需求需要计算机之间交换数据,由局域网(私网)–>广域网(公网),网络就逐渐发展起来了。
初识协议
-
协议就是一种约定
-
网络协议就是众多协议中的一种,又称TCP/IP协议,由ISO(国际标准组织)制定
-
协议的本质也是软件,而软件横向模块,纵向分层
-
软件分层的好处:
解耦的有效方式,可维护性好(对于其他人阅读容易,改变该部分的代码不会影响其他部分)
封装继承和多态就是分层的。
任何问题的解决都可以通过增加一层软件层来解决 -
看待协议两个视角:
- 小白视角:同层协议,直接通信
- 工程师:同层协议,没有直接通信
OSI七层模型(标准)
这是标准
TCP/IP五层(四层)模型
OSI与TCP/IP的关系
TCP/IP协议族
为什么?
冯诺依曼体系就是网络,不过就是硬件与硬件之间的距离很短。
主机A与主机B距离变长,就会存在一些问题?
- 数据怎么给路由器的? 物理层和链路层
- 怎么定位主机C的? 网络层
- 数据丢失怎么办? 传输层
- 发数据不是目的,而是手段,用数据才是目的。也就是说主机发出的数据,怎么被处理? 应用层
所以,是为什么呢?,就是因为通信距离变长而引起的一些问题
是什么?
这些问题,种类不同,性质不同–>协议分层
TCP/IP是这些问题的解决方案
怎么办?
网络与OS
网络是OS中的一个模块
协议本质是结构体,先描述,再组织。
- TCP/IP网络,是os的一个模块,os使用c语言写的,网络也是用c语言写的
- win&&Linux网络代码一定是一样的
结构体类型一样吗?
一样
–>这样就实现了,不同操作系统之间的网络通信
总结:协议就是双方都认识的结构化类型
网络传输的基本流程
局域网(以太网为例)
原理:就是所有主机都能收到以太网上的信息
mac地址
ifconfig //命令查看mac地址
mac地址可以唯一标识局域网中的主机。48比特位,6字节。
- 如何判断报文是否是给自己的呢?
在数据链路层完成,通过mac地址
数据碰撞
- 以太网一段时间内,只允许有一份报文。
- 如果有多份就会发生碰撞,导致数据不一致,这就是数据碰撞。
- 碰撞域:局域网本身就是一个碰撞域
- 以太网是公共资源,也就是临界资源–>互斥–>碰撞检测和碰撞避免
怎么检测碰撞?
电压,多份报文的电压会比一份报文的电压大。
主机越多,发生碰撞的概率越大。
流程
- 报文 = 报头+有效载荷
- 有效载荷:
报头的共性:
- 报头和有效载荷分离的问题
- 报头内部,必须包含一个字段,叫做交给上层谁的字段—分用
解包:就是分离报头和有效载荷
分用:
- 为什么要封装?
网卡是硬件,由os管理,所以必须贯穿协议帧 - 网卡有数据,怎么办?
网卡有数据,就会给os发送中断信号,继而执行相应的处理方法。
跨网络传输
- 就是局域网与局域网之间的数据传输
- IP地址 4字节 4个部分 每个部分0~255
- IP协议有IPV4和IPV6,但现在常用的是IPV4
- IP地址可以标识唯一的主机
MAC VS IP
在进行跨网络传输时,有两套地址
- 不变,源IP地址—>目的IP地址
- 变化,源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的种类多一些
- 网络socket
- 本地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接收消息
- sockfd:文件描述符
- flags:0 (默认)阻塞的等待接收消息
- src_addr 和 addrlen都是输出型参数,表示发送端的网络信息
sendto
- flags: 0
- 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服务器的端口号必须稳定!必须众所周知且不能轻易改变
- 云服务器可能有多个IP,禁止用户bind公网IP
- 虚拟机可以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;
};
- 需要给服务器添加路由,增删用户的功能
- 将路由当做任务,交给线程池来处理
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;
}
相较于以前收消息交给另一个线程来完成