网络发展
最开始,计算机是独立的个体,因为需求需要计算机之间交换数据,由局域网(私网)–>广域网(公网),网络就逐渐发展起来了。
初识协议
协议就是一种约定
网络协议就是众多协议中的一种,又称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自动调用bind
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());
// 刚登上就上线,不需要发完消息后才能上线
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;
}
相较于以前收消息交给另一个线程来完成