1. 网络协议初识
- 所谓的协议就是人们为了通信的一种约定
- 操作系统要进行协议管理,必然会先描述,再组织
- 协议本质就是软件,软件是可以"分层"
- 协议在设计的时候,就是被层状的划分的,
为什么要划分成为层状结构
- 因为有的场景复杂,需要把功能解耦(便于人们进行各种维护)
常见OSI的5层模型
- a.应用层,b. 传输层,c.网络层,d.数据链路层,e.物理层
2. OSI七层模型
2.1 局域网中两台主机是可以直接通信的
- 每层都有自己的协议定制方案,
- 每层协议都要有自己的协议报头
- 从上到下交付数据的时候,要添加报头
- 从下到上递交数据的时候,要去掉报头
2.2 理解报头
- 报头就像快递的快递单号一样,虽然我们不看,但是必须有
- 封装的本质: 添加报头
- 解包: 去掉报头&&展开分析
- 在使用TCP/IP协议的网络中,IP及其向上的协议,看到的报文都是一样的
- 报文是要被封装的,如何解包?
- 决定我们的有效载荷交付给上层的哪一个协议的问题?
- 每一个协议都要考虑,都要有一定的方式解决这两个公共问题
2.3 理解源IP地址和目的IP地址
- 把数据哦送到对方的机器不是目的,真正的网络通信: 其实是进程间通信
- IP地址(公网IP),标定了主机的唯一性
- 源IP是固定不变的,目的IP是可以变的
2.4 理解端口号
- 端口号是标识特定主机上的网络进程的唯一性
- 端口号是一个2字节16位的整数
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用
3. socket编程接口
常见的套接字:
- 域间socket
- 原始socket
- 网络socket
理论上,是三种应用场景,对应的应该是三套接口,不过将所有的接口进行统一
3.1 使用网络在两台主机之间进行通信(之间互发信息)
log.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>
// 日志是有日志级别的
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
const char *gLevelMap[] = {
"DEBUG",
"NORMAL",
"WARNING",
"ERROR",
"FATAL"
};
// #define LOGFILE "./threadpool.log"
// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
if(level== DEBUG) return;
#endif
char stdBuffer[1024]; //标准部分
time_t timestamp = time(nullptr);
snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);
char logBuffer[1024]; //自定义部分
va_list args;
va_start(args, format);
vsnprintf(logBuffer, sizeof logBuffer, format, args);
va_end(args);
printf("%s%s\n", stdBuffer, logBuffer);
}
Makefile
.PHONY:all
all:udp_client udp_server
udp_client:udp_client.cc
g++ -o $@ $^ -std=c++11
udp_server:udp_server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f udp_client udp_server
udp_client.cc
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <memory>
uint16_t serverport = 0;
std::string serverip;
static void usage(std::string proc)
{
std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
<< std::endl;
}
// ./udp_client 127.0.0.1 8080
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(1);
}
// 1.创建套接字
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "socket error" << std::endl;
exit(2);
}
serverport = atoi(argv[2]);
serverip = argv[1];
// client一般不需要显示的bind指定port,而是让OS自动随机选择(什么时候做的呢?)
std::string message;
struct sockaddr_in server;
memset(&server, 0, sizeof(server));// 清0
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
char buffer[1024];
while(true)
{
//多线程
std::cout << "请输入你的信息# ";
std::getline(std::cin, message);
if(message == "quit") break;
// 当client首次发送消息给服务器的时候,OS会自动给client bind他的IP和PORT
sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof server);
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr*)&temp, &len);
if(s > 0)
{
buffer[s] = 0;
std::cout << "server echo# " << buffer << std::endl;
}
}
return 0;
}
udp_server.cc
#include "udp_server.hpp"
#include <memory>
#include <cstdlib>
static void usage(std::string proc)
{
std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}
// ./udp_server ip port //云服务器的问题 bug??
int main(int argc, char *argv[])
{
if(argc != 2)
{
usage(argv[0]);
exit(1);
}
// std::string ip = argv[1];
uint16_t port = atoi(argv[1]);// 字符串->整数
// unique_ptr智能指针
std::unique_ptr<UdpServer> svr(new UdpServer(port));
svr->initServer();
svr->Start();
return 0;
}
udp_server.hpp
#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP
#include "log.hpp"
#include <iostream>
#include <unordered_map>
#include <cstdio>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <queue>
// 网络的常用的四个接口
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SIZE 1024
class UdpServer
{
public:
UdpServer(uint16_t port, std::string ip = "") : _port(port), _ip(ip), _sock(-1)
{
}
// 初始化
bool initServer()
{
// 1. 创建套接字
_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (_sock < 0)
{
logMessage(FATAL, "%d:%s", errno, strerror(errno));
exit(2);
}
// 2. bind: 将用户设置的ip和port在内核中和我们当前的进程强关联
struct sockaddr_in local;// sockaddr_in域间套接字
bzero(&local, sizeof(local));// 清0
local.sin_family = AF_INET;
// 服务器的IP和端口未来也是要发送给对方主机的 -> 先要将数据发送到网络!
// 主机序列->网络序列
local.sin_port = htons(_port);
//INADDR_ANY表示让服务器在工作过程中,可以从任意IP中获取数据
//inet_add表示将点分十进制字符串(192.168.110.132)风格的IP地址->4字节主机序列->网络序列
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "%d:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "init udp server done ... %s", strerror(errno));
// done
return true;
}
void Start()
{
// 服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了!
// echo server: client给我们发送消息,我们原封不动返回
char buffer[SIZE];
for (;;)
{
// 注意:peer是纯输出型参数
struct sockaddr_in peer;
bzero(&peer, sizeof(peer));
// 输入: peer缓冲区大小
// 输出: 实际读到的peer
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
buffer[s] = 0; // 我们目前数据当做字符串
uint16_t cli_port = ntohs(peer.sin_port); // 从网络中来的!
std::string cli_ip = inet_ntoa(peer.sin_addr); // 4字节的网络序列的IP->本主机的字符串风格的IP,方便显示
printf("[%s:%d]# %s\n", cli_ip.c_str(), cli_port, buffer);
}
// 分析和处理数据,TODO
// end. 写回数据
sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
}
}
~UdpServer()
{
if (_sock >= 0)
close(_sock);
}
private:
// 一个服务器,一般必须需要ip地址和port(16位的整数)
uint16_t _port;// 端口号
std::string _ip;// ip地址
int _sock;// 套接字
};
#endif
3.2 使用网络在两台主机之间进行通信(交互式的命令)
void Start()
{
// 服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了!
// echo server: client给我们发送消息,我们原封不动返回
char buffer[SIZE];
for (;;)
{
// 注意:peer是纯输出型参数
struct sockaddr_in peer;
bzero(&peer, sizeof(peer));
// 输入: peer缓冲区大小
// 输出: 实际读到的peer
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
char result[256];
char key[64];
std::string cmd_echo;
if (s > 0)
{
buffer[s] = 0; // 我们目前数据当做字符串
uint16_t cli_port = ntohs(peer.sin_port); // 从网络中来的!
std::string cli_ip = inet_ntoa(peer.sin_addr); // 4字节的网络序列的IP->本主机的字符串风格的IP,方便显示
// 你发过来的字符串是指令 ls -a -l, rm -rm ~
if(strcasestr(buffer, "rm") != nullptr || strcasestr(buffer, "rmdir") != nullptr)
{
std::string err_message = "坏人.... ";
std::cout << err_message << buffer << std::endl;
sendto(_sock, err_message.c_str(), err_message.size(), 0, (struct sockaddr *)&peer, len);
continue;
}
FILE *fp = popen(buffer, "r");
if (nullptr == fp)
{
logMessage(ERROR, "popen: %d:%s", errno, strerror(errno));
continue;
}
while (fgets(result, sizeof(result), fp) != nullptr)
{
cmd_echo += result;
}
fclose(fp);
// printf("[%s:%d]# %s\n", cli_ip.c_str(), cli_port, buffer);
}
// 分析和处理数据,TODO
// end. 写回数据
sendto(_sock, cmd_echo.c_str(), cmd_echo.size(), 0, (struct sockaddr *)&peer, len);
}
3.3 使用网络在两台主机之间进行通信(多进程)
makeflie
.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
.PHONY:clean
clean:
rm -f udp_client udp_server
log.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>
// 日志是有日志级别的
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
const char *gLevelMap[] = {
"DEBUG",
"NORMAL",
"WARNING",
"ERROR",
"FATAL"
};
// #define LOGFILE "./threadpool.log"
// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
if(level== DEBUG) return;
#endif
char stdBuffer[1024]; //标准部分
time_t timestamp = time(nullptr);
snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);
char logBuffer[1024]; //自定义部分
va_list args;
va_start(args, format);
vsnprintf(logBuffer, sizeof logBuffer, format, args);
va_end(args);
printf("%s%s\n", stdBuffer, logBuffer);
}
thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstdio>
// typedef std::function<void* (void*)> fun_t;
typedef void *(*fun_t)(void *);
class ThreadData
{
public:
void *args_;
std::string name_;
};
class Thread
{
public:
Thread(int num, fun_t callback, void *args) : func_(callback)
{
char nameBuffer[64];
snprintf(nameBuffer, sizeof nameBuffer, "Thread-%d", num);
name_ = nameBuffer;
tdata_.args_ = args;
tdata_.name_ = name_;
}
void start()
{
pthread_create(&tid_, nullptr, func_, (void*)&tdata_);
}
void join()
{
pthread_join(tid_, nullptr);
}
std::string name()
{
return name_;
}
~Thread()
{
}
private:
std::string name_;
fun_t func_;
ThreadData tdata_;
pthread_t tid_;
};
udp_client.cc
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <memory>
#include "thread.hpp"
uint16_t serverport = 0;
std::string serverip;
static void usage(std::string proc)
{
std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
<< std::endl;
}
static void *udpSend(void *args)
{
int sock = *(int *)((ThreadData *)args)->args_;
std::string name = ((ThreadData *)args)->name_;
std::string message;
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());
while (true)
{
std::cerr << "请输入你的信息# "; //标准错误 2打印
std::getline(std::cin, message);
if (message == "quit")
break;
// 当client首次发送消息给服务器的时候,OS会自动给client bind他的IP和PORT
// sendto表示发送报文
sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof server);
}
return nullptr;
}
static void *udpRecv(void *args)
{
int sock = *(int *)((ThreadData *)args)->args_;
std::string name = ((ThreadData *)args)->name_;
char buffer[1024];
while (true)
{
memset(buffer, 0, sizeof(buffer));
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
// recvfrom接收解决报文
ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr *)&temp, &len);
if (s > 0)
{
buffer[s] = 0;
std::cout << buffer << std::endl;
}
}
}
// ./udp_client 127.0.0.1 8080
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(1);
}
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "socket error" << std::endl;
exit(2);
}
serverport = atoi(argv[2]);
serverip = argv[1];
std::unique_ptr<Thread> sender(new Thread(1, udpSend, (void *)&sock));
std::unique_ptr<Thread> recver(new Thread(2, udpRecv, (void *)&sock));
// sender->name();
sender->start();
recver->start();
sender->join();
recver->join();
close(sock);
return 0;
}
udp_server.cc
#include "udp_server.hpp"
#include <memory>
#include <cstdlib>
static void usage(std::string proc)
{
std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
usage(argv[0]);
exit(1);
}
uint16_t port = atoi(argv[1]);
std::unique_ptr<UdpServer> svr(new UdpServer(port));
svr->initServer();
svr->Start();
return 0;
}
udp_server.hpp
#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP
#include "log.hpp"
#include <iostream>
#include <unordered_map>
#include <cstdio>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <queue>
#define SIZE 1024
class UdpServer
{
public:
UdpServer(uint16_t port, std::string ip = "") : _port(port), _ip(ip), _sock(-1)
{
}
bool initServer()
{
// 1. 创建套接字
_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (_sock < 0)
{
logMessage(FATAL, "%d:%s", errno, strerror(errno));
exit(2);
}
// 2. bind: 将用户设置的ip和port在内核中和我们当前的进程强关联
struct sockaddr_in local;
bzero(&local, sizeof(local));// 清0
local.sin_family = AF_INET;
local.sin_port = htons(_port);// 转换
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());// 转换
if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "%d:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "init udp server done ... %s", strerror(errno));
return true;
}
void Start()
{
char buffer[SIZE];
for (;;)
{
// 注意:
// peer,纯输出型参数
struct sockaddr_in peer;// 输出型参数
bzero(&peer, sizeof(peer));
socklen_t len = sizeof(peer);
char result[256];
char key[64];
std::string cmd_echo;
// recvfrom读取报文
ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
buffer[s] = 0; // 我们目前数据当做字符串
uint16_t cli_port = ntohs(peer.sin_port);// 转换
std::string cli_ip = inet_ntoa(peer.sin_addr);// 转换
snprintf(key, sizeof(key), "%s-%u", cli_ip.c_str(), cli_port); // 127.0.0.1-8080
logMessage(NORMAL, "key: %s", key);
auto it = _users.find(key);
if (it == _users.end())
{
// 不存在插入
logMessage(NORMAL, "add new user : %s", key);
_users.insert({key, peer});
}
}
for (auto &iter : _users)
{
std::string sendMessage = key;
sendMessage += "# ";
sendMessage += buffer; // 127.0.0.1-1234# 你好
logMessage(NORMAL, "push message to %s", iter.first.c_str());
sendto(_sock, sendMessage.c_str(), sendMessage.size(), 0, (struct sockaddr *)&(iter.second), sizeof(iter.second));
}
}
}
~UdpServer()
{
if (_sock >= 0)
close(_sock);
}
private:
// 一个服务器,一般必须需要ip地址和port(16位的整数)
uint16_t _port;
std::string _ip;
int _sock;
std::unordered_map<std::string, struct sockaddr_in> _users;
std::queue<std::string> messageQueue;
};
#endif
- netstat -antp// 查看网络进程服务
- 无论是多线程读还是写,用的sock都是一个,
- sock代表的就是文件,udp是全双工的,可以同时进行收发而不受干扰
3.4 使用网络在两台主机之间进行通信(多进程-父子进程):tcp实现
tcp_server.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
static void service(int sock, const std::string &clientip, const uint16_t &clientport)
{
//echo server
char buffer[1024];
while(true)
{
// read && write 可以直接被使用!
ssize_t s = read(sock, buffer, sizeof(buffer)-1);
if(s > 0)
{
buffer[s] = 0; //将发过来的数据当做字符串
std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
}
else if(s == 0) //对端关闭连接
{
logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
break;
}
else{ //
logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
break;
}
write(sock, buffer, strlen(buffer));
}
}
class TcpServer
{
private:
const static int gbacklog = 20;
public:
TcpServer(uint16_t port, std::string ip=""):listensock(-1), _port(port), _ip(ip)
{}
void initServer()
{
// 1. 创建socket -- 进程和文件
listensock = socket(AF_INET, SOCK_STREAM, 0);
if(listensock < 0)
{
logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "create socket success, listensock: %d", listensock); // 3
// 2. bind -- 文件 + 网络
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
if(bind(listensock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
// 3. 因为TCP是面向连接的,当我们正式通信的时候,需要先建立连接
if(listen(listensock, gbacklog) < 0)
{
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "init server success");
}
void start()
{
// 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
signal(SIGCHLD, SIG_IGN);
while(true)
{
// sleep(1);
// 4. 获取连接
struct sockaddr_in src;
socklen_t len = sizeof(src);
int servicesock = accept(listensock, (struct sockaddr*)&src, &len);
if(servicesock < 0)
{
logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
continue;
}
// 获取连接成功了
uint16_t client_port = ntohs(src.sin_port);
std::string client_ip = inet_ntoa(src.sin_addr);
logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",\
servicesock, client_ip.c_str(), client_port);
// 开始进行通信服务啦
// 多进程版 --- 创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
// 子进程是来进行提供服务的,需不需要知道监听socket呢?
close(listensock);
service(servicesock, client_ip, client_port);
exit(0); // 僵尸状态
}
close(servicesock); // 如果父进程关闭servicesock,会不会影响子进程??
}
}
~TcpServer(){}
private:
uint16_t _port;
std::string _ip;
int listensock;// 监听套接字
};
tcp_server.cc
#include "tcp_server.hpp"
#include <memory>
static void usage(std::string proc)
{
std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}
// ./tcp_server port
int main(int argc, char *argv[])
{
if(argc != 2)
{
usage(argv[0]);
exit(1);
}
uint16_t port = atoi(argv[1]);
std::unique_ptr<TcpServer> svr(new TcpServer(port));
svr->initServer();
svr->start();
return 0;
}
- telnet ip 端口号// 创建临时客户端
- while :; do ps ajx | grep tcp-server;echo "####";sleep 1;done
tcp_client.cc
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void usage(std::string proc)
{
std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
<< std::endl;
}
// ./tcp_client targetIp targetPort
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(1);
}
std::string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
bool alive = false;
int sock = 0;
std::string line;
while (true) // TODO
{
if (!alive)
{
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
std::cerr << "socket error" << std::endl;
exit(2);
}
// client 要不要bind呢?不需要显示的bind,但是一定是需要port
// 需要让os自动进行port选择
// 连接别人的能力!
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());
if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
{
std::cerr << "connect error" << std::endl;
exit(3); // TODO
}
std::cout << "connect success" << std::endl;
alive = true;
}
std::cout << "请输入# ";
std::getline(std::cin, line);
if (line == "quit")
break;
ssize_t s = send(sock, line.c_str(), line.size(), 0);
if (s > 0)
{
char buffer[1024];
ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (s > 0)
{
buffer[s] = 0;
std::cout << "server 回显# " << buffer << std::endl;
}
else if (s == 0)
{
alive = false;
close(sock);
}
}
else
{
alive = false;
close(sock);
}
}
return 0;
}
- 客户端需要具有连接能力,是通过connect实现的
4. 模拟实现网络版计算器
- 序列化反序列化
- 定制自己的协议
- 将我们的服务守护进程化,让他变成一个网络服务
CalClient.cc
#include <iostream>
#include "Sock.hpp"
#include "Protocol.hpp"
using namespace ns_protocol;
static void Usage(const std::string &process)
{
std::cout << "\nUsage: " << process << " serverIp serverPort\n"
<< std::endl;
}
// ./client server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string server_ip = argv[1];
uint16_t server_port = atoi(argv[2]);
Sock sock;
int sockfd = sock.Socket();
if (!sock.Connect(sockfd, server_ip, server_port))
{
std::cerr << "Connect error" << std::endl;
exit(2);
}
bool quit = false;
std::string buffer;
while (!quit)
{
// 1. 获取需求
Request req;
std::cout << "Please Enter # ";
std::cin >> req.x_ >> req.op_ >> req.y_;
// 2. 序列化
std::string s = req.Serialize();
// std::string temp = s;
// 3. 添加长度报头
s = Encode(s);
// 4. 发送给服务端
Send(sockfd, s);
// 5. 正常读取
while (true)
{
bool res = Recv(sockfd, &buffer);
if (!res)
{
quit = true;
break;
}
std::string package = Decode(buffer);
if (package.empty())
continue;
Response resp;
resp.Deserialized(package);
std::string err;
switch (resp.code_)
{
case 1:
err = "除0错误";
break;
case 2:
err = "模0错误";
break;
case 3:
err = "非法操作";
break;
default:
std::cout << resp.x_ << resp.op_ << resp.y_ << " = " << resp.result_ << " [success]" << std::endl;
break;
}
if(!err.empty()) std::cerr << err << std::endl;
// sleep(1);
break;
}
}
close(sockfd);
return 0;
}
CalServer.cc
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Daemon.hpp"
#include <memory>
#include <signal.h>
using namespace ns_tcpserver;
using namespace ns_protocol;
static void Usage(const std::string &process)
{
std::cout << "\nUsage: " << process << " port\n"
<< std::endl;
}
static Response calculatorHelper(const Request &req)
{
Response resp(0, 0, req.x_, req.y_, req.op_);
switch (req.op_)
{
case '+':
resp.result_ = req.x_ + req.y_;
break;
case '-':
resp.result_ = req.x_ - req.y_;
break;
case '*':
resp.result_ = req.x_ * req.y_;
break;
case '/':
if (0 == req.y_)
resp.code_ = 1;
else
resp.result_ = req.x_ / req.y_;
break;
case '%':
if (0 == req.y_)
resp.code_ = 2;
else
resp.result_ = req.x_ % req.y_;
break;
default:
resp.code_ = 3;
break;
}
return resp;
}
void calculator(int sock)
{
std::string inbuffer;
while (true)
{
// 1. 读取成功
bool res = Recv(sock, &inbuffer); // 在这里我们读到了一个请求?
if (!res)
break;
// std::cout << "begin: inbuffer: " << inbuffer << std::endl;
// 2. 协议解析,保证得到一个完整的报文
std::string package = Decode(inbuffer);
if (package.empty())
continue;
// std::cout << "end: inbuffer: " << inbuffer << std::endl;
// std::cout << "packge: " << package << std::endl;
logMessage(NORMAL, "%s", package.c_str());
// 3. 保证该报文是一个完整的报文
Request req;
// 4. 反序列化,字节流 -> 结构化
req.Deserialized(package); // 反序列化
// 5. 业务逻辑
Response resp = calculatorHelper(req);
// 6. 序列化
std::string respString = resp.Serialize(); // 对计算结果进行序列化
// 7. 添加长度信息,形成一个完整的报文
// "length\r\ncode result\r\n"
// std::cout << "respString: " << respString << std::endl;
respString = Encode(respString);
// std::cout << "encode: respString: " << respString << std::endl;
// 8. send这里我们暂时先这样写,多路转接的时候,我们再来谈发送的问题
Send(sock, respString);
}
}
// ./CalServer port
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(1);
}
// 一般经验:server在编写的时候,要有较为严谨性的判断逻辑
// 一般服务器,都是要忽略SIGPIPE信号的,防止在运行中出现非法写入的问题!
// signal(SIGPIPE, SIG_IGN);
MyDaemon();
std::unique_ptr<TcpServer> server(new TcpServer(atoi(argv[1])));
server->BindService(calculator);
server->Start();
// Request req(123, 456, '+');
// std::string s = req.Serialize();
// std::cout << s << std::endl;
// Request temp;
// temp.Deserialized(s);
// std::cout << temp.x_ << std::endl;
// std::cout << temp.op_ << std::endl;
// std::cout << temp.y_ << std::endl;
return 0;
}
Daemon.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 设置守护进程
void MyDaemon()
{
// 1. 忽略信号,SIGPIPE,SIGCHLD
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
// 2. 不要让自己成为组长
if (fork() > 0)
exit(0);
// 3. 调用setsid
setsid();
// 4. 标准输入,标准输出,标准错误的重定向,守护进程不能直接向显示器打印消息
int devnull = open("/dev/null", O_RDONLY | O_WRONLY);
if(devnull > 0)
{
dup2(0, devnull);
dup2(1, devnull);
dup2(2, devnull);
close(devnull);
}
}
Log.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>
// 日志是有日志级别的
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
const char *gLevelMap[] = {
"DEBUG",
"NORMAL",
"WARNING",
"ERROR",
"FATAL"
};
#define LOGFILE "./calculator.log"
// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
if(level== DEBUG) return;
#endif
char stdBuffer[1024]; //标准部分
time_t timestamp = time(nullptr);
snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);
char logBuffer[1024]; //自定义部分
va_list args;
va_start(args, format);
vsnprintf(logBuffer, sizeof logBuffer, format, args);
va_end(args);
FILE *fp = fopen(LOGFILE, "a");
fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
fclose(fp);
}
Makefile
.PHONY:all
all:client CalServer
client:CalClient.cc
g++ -o $@ $^ -std=c++11
CalServer:CalServer.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f client CalServer
Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
// #include <jsoncpp/json/json.h>
namespace ns_protocol
{
#define MYSELF 0
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define SEP "\r\n"
#define SEP_LEN strlen(SEP) // 不能是sizeof!
class Request
{
public:
// 1. 自主实现 "length\r\nx_ op_ y_\r\n"
// 2. 使用现成的方案
// 序列化
std::string Serialize()
{
#ifdef MYSELF
// 使用自己定义的方式
std::string str;
str = std::to_string(x_);
str += SPACE;
str += op_; // TODO
str += SPACE;
str += std::to_string(y_);
return str;
#else
Json::Value root;
root["x"] = x_;
root["y"] = y_;
root["op"] = op_;
Json::FastWriter writer;
return writer.write(root);
#endif
}
// "x_ op_ y_"
// "1234 + 5678"
// 反序列化
bool Deserialized(const std::string &str)
{
#ifdef MYSELF
std::size_t left = str.find(SPACE);
if (left == std::string::npos)
return false;
std::size_t right = str.rfind(SPACE);
if (right == std::string::npos)
return false;
x_ = atoi(str.substr(0, left).c_str());
y_ = atoi(str.substr(right + SPACE_LEN).c_str());
if (left + SPACE_LEN > str.size())
return false;
else
op_ = str[left + SPACE_LEN];
return true;
#else
Json::Value root;
Json::Reader reader;
reader.parse(str, root);
x_ = root["x"].asInt();
y_ = root["y"].asInt();
op_ = root["op"].asInt();
return true;
#endif
}
public:
Request()
{
}
Request(int x, int y, char op) : x_(x), y_(y), op_(op)
{
}
~Request() {}
public:
int x_; // 是什么?
int y_; // 是什么?
char op_; // '+' '-' '*' '/' '%'
};
class Response
{
public:
// "code_ result_"
// 结果序列化
std::string Serialize()
{
#ifdef MYSELF
std::string s;
s = std::to_string(code_);
s += SPACE;
s += std::to_string(result_);
return s;
#else
Json::Value root;
root["code"] = code_;
root["result"] = result_;
root["xx"] = x_;
root["yy"] = y_;
root["zz"] = op_;
Json::FastWriter writer;
return writer.write(root);
#endif
}
// "111 100"
// 结果反序列化
bool Deserialized(const std::string &s)
{
#ifdef MYSELF
std::size_t pos = s.find(SPACE);
if (pos == std::string::npos)
return false;
code_ = atoi(s.substr(0, pos).c_str());
result_ = atoi(s.substr(pos + SPACE_LEN).c_str());
return true;
#else
Json::Value root;
Json::Reader reader;
reader.parse(s, root);
code_ = root["code"].asInt();
result_ = root["result"].asInt();
x_ = root["xx"].asInt();
y_ = root["yy"].asInt();
op_ = root["zz"].asInt();
return true;
#endif
}
public:
Response()
{
}
Response(int result, int code, int x, int y, char op)
: result_(result), code_(code), x_(x), y_(y), op_(op)
{
}
~Response() {}
public:
int result_; // 计算结果
int code_; // 计算结果的状态码
int x_;
int y_;
char op_;
};
// 临时方案
// 调整方案2: 我们期望,你必须给我返回一个完整的报文
bool Recv(int sock, std::string *out)
{
// UDP是面向数据报:
// TCP 面向字节流的:
// recv : 你怎么保证,你读到的inbuffer,是一个完整完善的请求呢?不能保证
// "1234 + 5678" : 1234 +
// "1234 + 5678" : 1234 + 5678 123+99
// "1234 "
// 必须是:"1234 + 5678"
// 单纯的recv是无法解决这个问题的,需要对协议进一步定制!
char buffer[1024];
ssize_t s = recv(sock, buffer, sizeof(buffer)-1, 0); // 9\r\n123+789\r\n
if (s > 0)
{
buffer[s] = 0;
*out += buffer;
}
else if (s == 0)
{
// std::cout << "client quit" << std::endl;
return false;
}
else
{
// std::cout << "recv error" << std::endl;
return false;
}
return true;
}
void Send(int sock, const std::string str)
{
// std::cout << "sent in" << std::endl;
int n = send(sock, str.c_str(), str.size(), 0);
if (n < 0)
std::cout << "send error" << std::endl;
}
// "length\r\nx_ op_ y_\r\n..." // 10\r\nabc
// "x_ op_ y_\r\n length\r\nXXX\r\n"
// 反序列化(进一步)
std::string Decode(std::string &buffer)
{
std::size_t pos = buffer.find(SEP);
if(pos == std::string::npos) return "";
int size = atoi(buffer.substr(0, pos).c_str());
int surplus = buffer.size() - pos - 2*SEP_LEN;
if(surplus >= size)
{
//至少具有一个合法完整的报文, 可以动手提取了
buffer.erase(0, pos+SEP_LEN);
std::string s = buffer.substr(0, size);
buffer.erase(0, size + SEP_LEN);
return s;
}
else
{
return "";
}
}
// "XXXXXX"
// "123\r\nXXXXXX\r\n"
// 序列化(进一步)
std::string Encode(std::string &s)
{
std::string new_package = std::to_string(s.size());
new_package += SEP;
new_package += s;
new_package += SEP;
return new_package;
}
}
Sock.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include "Log.hpp"
// 封装一下套接字 TCP
class Sock
{
private:
const static int gbacklog = 20;
public:
Sock() {}
int Socket()
{
int listensock = socket(AF_INET, SOCK_STREAM, 0);// 监听套接字
if (listensock < 0)
{
logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "create socket success, listensock: %d", listensock);
return listensock;
}
void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
{
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(port);// 端口转换
inet_pton(AF_INET, ip.c_str(), &local.sin_addr);// 端口转换
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
}
void Listen(int sock)
{
if (listen(sock, gbacklog) < 0)
{
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "init server success");
}
// 一般经验
// const std::string &: 输入型参数
// std::string *: 输出型参数
// std::string &: 输入输出型参数
int Accept(int listensock, std::string *ip, uint16_t *port)
{
struct sockaddr_in src;
socklen_t len = sizeof(src);
int servicesock = accept(listensock, (struct sockaddr *)&src, &len);// 转移
if (servicesock < 0)
{
logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
return -1;
}
if(port) *port = ntohs(src.sin_port);
if(ip) *ip = inet_ntoa(src.sin_addr);
return servicesock;
}
bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
else return false;
}
~Sock() {}
};
TcpServer.hpp
#pragma once
#include "Sock.hpp"
#include <vector>
#include <functional>
#include <pthread.h>
namespace ns_tcpserver
{
using func_t = std::function<void(int)>;// 包装器 - 包装的是一个函数
class TcpServer;
// 这个更像是一个结构体
class ThreadData
{
public:
ThreadData(int sock, TcpServer *server):sock_(sock), server_(server)
{}
~ThreadData() {}
public:
int sock_;
TcpServer *server_;
};
class TcpServer
{
private:
static void *ThreadRoutine(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
td->server_->Excute(td->sock_);
close(td->sock_);
return nullptr;
}
public:
TcpServer(const uint16_t &port, const std::string &ip = "0.0.0.0")
{
// 初始化
listensock_ = sock_.Socket();
sock_.Bind(listensock_, port, ip);
sock_.Listen(listensock_);
}
// 新增
void BindService(func_t func)
{
func_.push_back(func);
}
// 执行
void Excute(int sock)
{
for(auto &f : func_)
{
f(sock);
}
}
void Start()
{
for (;;)
{
std::string clientip;
uint16_t clientport;
int sock = sock_.Accept(listensock_, &clientip, &clientport);
if (sock == -1)
continue;
logMessage(NORMAL, "create new link success, sock: %d", sock);
// 创建进程
pthread_t tid;
ThreadData *td = new ThreadData(sock, this);
pthread_create(&tid, nullptr, ThreadRoutine, td);
}
}
~TcpServer()
{
if (listensock_ >= 0)
close(listensock_);// 就像关闭文件描述符一样
}
private:
int listensock_;
Sock sock_;
std::vector<func_t> func_;
};
}
4.1 关于守护进程
全都是在前台运行的
- 前台进程: 和终端关联的进程,前台进程
- 任何xshell登录,只允许一个前台进程和多个后台进程
- 进程除了有自己的pid,ppid,还有一个组ID
- 在命令行中,同时有管道启动多个进程,多个进程是兄弟关系,父进程都是bash->可以用来匿名管道来通信
- 而被创建的多个进程可一个成为一个进程组的概念,组长一般是第一个进程
- 任何一次登陆,登陆的用户,需要有多个进程(组),来给这个用户提供服务的(bash),用户自己可以启动很多进程,或者进程组,
- 我们把给用户提供服务的进程或者用户自己启动的所有的进程或者服务,整体都是要属于一个叫做会话的机制中的
- 通过setsid()可以将自己变成自成会话
- 通过fork()可以保证自己不是进程组的组长,
- 守护进程不能直接向显示器打印消息,一旦打印,就会被暂停,终止