计算机网络背景
网络发展
独立模式:计算机之间相互独立
网络互联:多台计算机连接在一起,完成数据共享
局域网(LAN)Local Area NetWork
广域网(WAN)Wide Area NetWork
所谓局域网和广域网只是一个相对的概念
认识“协议”
“协议”是一种约定
计算机之间的传输媒介是光信号和电信号,通过“频率”和“强弱”来表示0和1这样的信息,要想传递各种不同的信息,就需要约定好双方的数据格式
协议分层
打电话例子
分层最大好处在于“封装”
OSI七层模型
- OSI(Open System Interconnection,开放系统互连)七层网络模型称为开放式系统互联参考模型,是一个逻辑上的定义和规范;
- 把网络从逻辑上分为了7层. 每一层都有相关、相对应的物理设备,比如路由器,交换机;
- OSI 七层模型是一种框架性的设计方法,其最主要的功能使就是帮助不同类型的主机实现数据传输;
- 它的最大优点是将服务、接口和协议这三个概念明确地区分开来,概念清楚,理论也比较完整. 通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯;
- 但是, 它既复杂又不实用; 所以我们按照TCP/IP四层模型来讲解
层级 | 层 | 英文全称 | 常用协议 |
---|---|---|---|
7 | 应用层 | Application Layer | HTTP、FTP、POP3、SMTP、TELNET、NNTP、IMAP4、FINGER |
6 | 表示层 | Presentation Layer | LPP、NBSSP |
5 | 会话层 | Session Layer | SSL、TLS、DAP、LDAP |
4 | 传输层 | Transport Layer | TCP、UDP |
3 | 网络层 | NetWork Layer | IP、ICMP、RIP、IGMP、OSPF |
2 | 数据链路层 | Data Link Layer | 以太网、网卡、交换机、PPTP、L2TP、ARP、ATMP |
1 | 物理层 | Physical Layer | 物理线路、光纤、中继器、集线器、双绞线 |
TCP/IP模型
TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇.
TCP/IP通讯协议采用了5层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求.
- 物理层: 负责光/电信号的传递方式. 比如现在以太网通用的网线(双绞线)、早期以太网采用的的同轴电缆(现在主要用于有线电视)、光纤, 现在的wifi无线网使用电磁波等都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗干扰性等. 集线器(Hub)工作在物理层.
- 数据链路层: 负责设备之间的数据帧的传送和识别. 例如网卡设备的驱动、帧同步(就是说从网线上检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作. 有以太网、令牌环网, 无线LAN等标准. 交换机(Switch)工作在数据链路层.
- 网络层: 负责地址管理和路由选择. 例如在IP协议中, 通过IP地址来标识一台主机, 并通过路由表的方式规划出两台主机之间的数据传输的线路(路由). 路由器(Router)工作在网路层.
- 传输层: 负责两台主机之间的数据传输. 如传输控制协议 (TCP), 能够确保数据可靠的从源主机发送到目标主机
- 应用层: 负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等. 我们的网络编程主要就是针对应用层.
一般而言
- 对于一台主机,它的操作系统内核实现了从传输层到物理层的内容
- 对于一台路由器,它实现了从物理层到网络层
- 对于一台交换机,它实现了从物理层到数据链路层
- 对于集线器它只实现了物理层
但是并不绝对,很多交换机也实现了网络层1的转发;很多路由器也实现了部分传输层的内容(比如端口转发)
网络传输基本流程
- 同一个局域网(子网)的两台主机进行文件传输
-跨网段的主机的文件传输
数据包装和分用
- 不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报 (datagram),在链路层叫做帧(frame).
- 应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装(Encapsulation).
- 首部信息中包含了一些类似于首部有多长, 载荷(payload)有多长, 上层协议是什么等信息.
- 数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部, 根据首部中的 “上层协议字段” 将数据交给对应的上层协议处理
- 每一层,都会把上层交付给自己的数据作为自己的有效载荷
- 每一层,都有自己的协议报头
- 对应的层,报头+有效载荷=自己要发送的报文
- 在逻辑上,同层协议,都认为自己在和对方的同层协议在通信
- 同层协议,能够互相认识对方的报头
- 几乎每层协议都能做到 a、将报头和有效载荷分离 b、将有效载荷交付给上层具体的协议
网络中的地址管理
认识IP地址
任何人要通信,需要有唯一的标识符,对于机器也是如此。对于计算机,每台计算机都配有网卡,网卡在出厂的时候,就在网卡内部写入了网卡的sn号,MAC地址(全球唯一)
- IP协议有两个版本, IPv4和IPv6. 我们整个的课程, 凡是提到IP协议, 没有特殊说明的, 默认都是指IPv4
- IP地址是在IP协议中, 用来标识网络中不同主机的地址;
- 对于IPv4来说, IP地址是一个4字节, 32位的整数;
- 我们通常也使用 “点分十进制” 的字符串表示IP地址, 例如 192.168.0.1 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255
认识MAC地址
- MAC地址用来识别数据链路层中相连的节点;
- 长度为48位, 及6个字节. 一般用16进制数字加上冒号的形式来表示(例如: 08:00:27:03:fb:19)
- 在网卡出厂时就确定了, 不能修改. mac地址通常是唯一的(虚拟机中的mac地址不是真实的mac地址, 可能会冲突; 也有些网卡支持用户配置mac地址)
认识端口号
端口号(port)是传输层协议的内容
- 端口号是一个2字节16位的整数
- 端口号是用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理
- IP地址+端口号能够标识网络上的某一台主机的某一个进程
- 一个端口号只能被一个进程占用
端口号和pid
进程ID(PID)
- 用途:进程ID是操作系统用来唯一标识每个正在运行的进程的数字标识符
- 特点:每个正在运行的进程都有其唯一的PID
- 作用:用于操作系统内部识别、管理和控制进程,例如:启动、停止、暂停、恢复、终止等操作
端口号
- 用途:端口号是在计算机网络中用于标识不同服务或进程的数字标识符
- 特点:端口号是一个16位的数字,用于标识网络上的不同服务
- 作用:用于在主机间区分不同的网络服务,在客户端-服务器模型中,客户端程序使用端口号来与特定端口上运行的服务器程序通信
关系
虽然进程ID和端口号都是用于标识资源的标识符,但他们指代的资源类型不同,而且在操作系统和网络层面上有着不同的作用。在网络通信中,一台计算机上的进程可能会监听或绑定到一个或多个特定的端口号上,从而允许其他计算机上的程序通过网络与这些进程进行通信,但是一个端口号不能被多个进程绑定
认识TCP协议
Transmission Control Protocol
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
认识UDP协议
User Datagram Protocol
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
网络字节序
- 发送主机通常将发送给缓冲区中的数据按内存地址从低到高的顺序发出
- 接收主机从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
为使网络程序具有可移植性,使同样的c代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换
#include<<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint32_t htons(uint32_t hostshort);
uint32_t ntohl(uint32_t hostlong);
uint32_t ntohs(uint32_t hostshort);
- 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
- 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
- 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
sokect编程接口
socket常见API
1、创建socket文件描述符(TCP/UDP,客户端+服务器)
-
函数原型:int socket(int domain,int type, int protocol);
-
参数:
domain:协议族/域,通常AF_INET(IPv4)、AF_INET6(IPv6)
type:是套接口类型,主要SOCK_STREAM(TCP协议)、SOCK_DGRAM(UDP协议)
protocol:一般为0 -
返回:成功时返回非负整数。
2、绑定端口号(TCP/UDP,服务器)
- 函数原型:int bind(int sockfd, const struct sockaddr *addr,
socklen_t addr_len) - 参数
sockfd:即socket描述字,它是socket()函数创建的唯一标识。
addr:一个const struct sockaddr*指针,指向要绑定给sockfd的协议地址。
addrlen:对应的结构的长度 - 返回值
成功返回0,错误返回SOCKET ERROR。
3、开始监听socket(TCP,服务器)
-
函数原型:int listen(int sockfd ,int backlog);
-
参数
第一个参数即为要监听的socket描述字
第二个参数为未完成队列的大小:正在等待完成相应的TCP三路握手过程的队列。这些套接口处于SYN_RCVD -
返回值
该函数如果调用成功就返回0,否则返回SOCKET_ERROR。可以调用WSAGetLastError()函数获取错误代码
4、接收请求(TCP、服务器) -
函数原型:int accept (int socket,struct sockaddr* address,socklen_t* address_len);
-
参数
第一个参数即为要监听的socket描述字
第二个参数为指向struct sockaddr*的指针,用于返回客户端的协议地址
第三个参数为协议地址的长度。 -
返回值
成功返回由内核自动生成的一个全新的socket,代表与返回客户的TCP连接。
失败,则返回INVALID SOCKET。
5、建立连接(TCP、客户端)
- 函数原型:int connect (int sockfd,const struct sockaddr* addr,socklen_t addrlen);
- 参数
sockfd:即socket描述字
sockaddr:一个const struct sockaddr*指针,指向要绑定给sockfd的协议地址。
addrlen:sockaddr的长度 - 返回值
如果连接或绑定成功,则返回0。发生错误返回-1,并设置errno。
sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4,IPv6,以及UNIX Domain Socket,然而,各种网络协议的地址格式各不相同
- IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址
- IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6,这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容
- socket API可以都用struct sockaddr*类型来表示,在使用的时候需要强制转化成sockaddr_in;这样的好处是程序的通用性,可以接收IPv4、IPv6,以及UNIX Domain Socket各种类型的sockaddr结构体指针作为参数
sockaddr结构
struct sockaddr
{
__SOCKADDR_COMMON(sa_);//地址族(sin_family)
char sa_data[14];
};
sockaddr_in结构
struct sockaddr_in
{
__SOCKADDR_COMMON(sin_);//地址族(sin_family)
int port_t sin_port;//port number
struct in_addr sin_addr;//Internet address
unsigned char sin_zero[sizeof(struct sockaddr)-__SOCKADDR_COMMON_SIZE-sizeof(in_port_t)sizeof(struct in_addr)];//不使用
};
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主
要有三部分信息: 地址类型, 端口号, IP地址.
in_addr结构
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
in_addr用来表示一个IPv4的地址,其实就是一个32位的整数
地址转换函数
以下函数可以在字符串和in_addr之间转换
字符串转in_addr
#include<arpa/inet.h>
int inet_aton(const char* strptr,struct in_addr *addrptr);
in_addr_t inet_addr(const char* strptr);
int inet_pton(int family,const *strptr,void* addrptr);
in_addr转字符串
char *inet_ntoa(struct in_addr inaddr);
const char* inet_ntop(int family,const void* addrptr,char* strptr,size_t len);
其中inet_pton和inet_ntop不仅可以转换IPV4的in_addr,还可以转换IPV6的in6_addr,因此函数接口是void* addrptr
#include<stdio.h>
#include<sys/socket.h>
#include<netinet.h>
#include<arpa/inet.h>
int main()
{
struct sockaddr_in addr;
inet_aton("127.0.0.1",&addr.sin_addr);
uint32_t* ptr = (uint2_t*)(&addr.sin_addr);
printf("addr:%x\n",*ptr);
printf("addr_str:%s\n",inet_ntoa(addr.sin_addr));
return 0;
}
关于inet_ntoa
inet_ntoa这个函数返回了一个char*,很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果,那么是否需要手动释放呢?
man手册上说,inet_ntoa函数,是把这个返回结果放到了静态存储区,这个时候不需要我们进行手动释放。但是当我们多次调用这个函数时,后续调用结果会覆盖掉上一次的结果。也就是说inet_ntoa这个函数不是线程安全的函数
简单udp程序
---------------------------udp_server.hpp----------------------------------
#pragma once
#include <iostream>
#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<functional>
#include"err.hpp"
namespace ns_server
{
const static uint16_t default_port = 8080;
using func_t = std::function<std::string(std::string)>;
class UdpServer
{
public:
UdpServer(uint16_t port = default_port): _port(port)
{
std::cout << "server addr: " << _port << std::endl;
}
void InitServer()
{
// 1. 创建socket接口,打开网络文件
_sock = socket(AF_INET, SOCK_DGRAM, 0);
if(_sock < 0)
{
std::cerr << "create socket error: " << strerror(errno) << std::endl;
exit(SOCKET_ERR);
}
std::cout << "create socket success: " << _sock << std::endl; //3
// 2. 给服务器指明IP地址(??)和Port
struct sockaddr_in local; // 这个 local 在哪里定义呢?用户空间的特定函数的栈帧上,不在内核中!
bzero(&local, sizeof(local));
local.sin_family = AF_INET; //PF_INET
local.sin_port = htons(_port);
// inet_addr: 1,2
// 1. 字符串风格的IP地址,转换成为4字节int, "1.1.1.1" -> uint32_t -> 能不能强制类型转换呢?不能,这里要转化
// 2. 需要将主机序列转化成为网络序列
local.sin_addr.s_addr = INADDR_ANY;
if(bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr << "bind socket error: " << strerror(errno) << std::endl;
exit(BIND_ERR);
}
std::cout << "bind socket success: " << _sock << std::endl; //3
}
void Start()
{
char buffer[1024];
while(true)
{
//收
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int n = recvfrom(_sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(n>0) buffer[n] = '\0';
else continue;
//提取client信息
std::string clientip = inet_ntoa(peer.sin_addr);
uint16_t clientport = ntohs(peer.sin_port);
std::cout<<clientip<<"-"<<clientport<<" # "<<buffer<<std::endl;
//发
sendto(_sock,buffer,strlen(buffer),0,(struct sockaddr*)&peer,sizeof(peer));
}
}
~UdpServer() {}
private:
int _sock;
uint16_t _port;
};
} // end NS_SERVER
#pragma once
enum{
USAGE_ERR=1,
SOCKET_ERR,
BIND_ERR
};
-----------------------udp_server.cc-----------------------------
#include"udp_server.hpp"
#include<memory>
using namespace std;
using namespace ns_server;
static void usage(string proc)
{
std::cout << "Usage:\n\t" << proc << " port\n"
<< std::endl;
}
// int main()
// {
// unique_ptr<UdpServer> usvr(new UdpServer("1.1.1.1"));
// usvr->InitServer(); //服务器的初始化
// usvr->Start();
// return 0;
// }
// int main()
// {
// unique_ptr<UdpServer> usvr (new UdpServer());
// usvr->InitServer();//服务器初始化
// usvr->Start();
// std::cout<<"hello server"<<std::endl;
// return 0;
// }
int main(int argc, char *argv[])
{
if (argc != 2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr<UdpServer> usvr(new UdpServer(port));
usvr->InitServer(); //服务器的初始化
usvr->Start();
return 0;
}
----------------------udp_client.hpp-------------------------------------
#pragma once
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string>
#include<iostream>
#include"err.hpp"
------------------------udp_client-------------------------------
#include"udp_client.hpp"
#include<cstring>
//127.0.0.1本地环回
static void usage(std::string proc)
{
std::cout<<"Usage:\n\t"<<proc<<"serverip serverport\n"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock<0)
{
std::cerr<<"creat socket error"<<std::endl;
exit(SOCKET_ERR);
}
//client端不需要自己绑定:操作系统会自动进行绑定,随机分配端口号,保证每个进行网络通信的进程都有唯一的端口号
//server端要自己绑定:1、server端port不能随便更改 2、同一家公司的port号需要统一管理
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;//IPv4
server.sin_port = htons(serverport);
server.sin_addr.s_addr=inet_addr(serverip.c_str());
while(true)
{
//用户输入
std::string message;
std::cout<<"please enter #";
std::cin>>message;
//什么时候bind的?在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP,1、bind 2、构建发送的数据报文
//发送
sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
//接收
char buffer[1024];
struct sockaddr_in temp;
socklen_t len =sizeof(temp);
int n = recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
if(n>0)
{
buffer[n]=0;//"/0"
std::cout<<" server echo# "<<buffer<<std::endl;
}
}
return 0;
}
-----------------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_server.hpp--------------------------
#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unordered_map>
#include "err.hpp"
#include "ringQueue.hpp"
#include "lockGuard.hpp"
#include "thread.hpp"
namespace ns_server
{
const static uint16_t default_port = 8080;
using func_t = std::function<std::string(std::string)>;
class UdpServer
{
public:
UdpServer(uint16_t port = default_port) : port_(port)
{
std::cout << "server addr: " << port_ << std::endl;
pthread_mutex_init(&lock, nullptr);
p = new Thread(1, std::bind(&UdpServer::Recv, this));
c = new Thread(2, std::bind(&UdpServer::Broadcast, this));
}
void start()
{
// 1. 创建socket接口,打开网络文件
sock_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_ < 0)
{
std::cerr << "create socket error: " << strerror(errno) << std::endl;
exit(SOCKET_ERR);
}
std::cout << "create socket success: " << sock_ << std::endl; // 3
// 2. 给服务器指明IP地址(??)和Port
struct sockaddr_in local; // 这个 local 在哪里定义呢?用户空间的特定函数的栈帧上,不在内核中!
bzero(&local, sizeof(local));
local.sin_family = AF_INET; // PF_INET
local.sin_port = htons(port_);
// inet_addr: 1,2
// 1. 字符串风格的IP地址,转换成为4字节int, "1.1.1.1" -> uint32_t -> 能不能强制类型转换呢?不能,这里要转化
// 2. 需要将主机序列转化成为网络序列
// 3. 云服务器,或者一款服务器,一般不要指明某一个确定的IP
local.sin_addr.s_addr = INADDR_ANY; // 让我们的udpserver在启动的时候,bind本主机上的任意IP
if (bind(sock_, (struct sockaddr *)&local, sizeof(local)) < 0)
{
std::cerr << "bind socket error: " << strerror(errno) << std::endl;
exit(BIND_ERR);
}
std::cout << "bind socket success: " << sock_ << std::endl; // 3
p->run();
c->run();
}
void addUser(const std::string &name, const struct sockaddr_in &peer)
{
//?
// onlineuserp[name] = peer;
LockGuard lockguard(&lock);
auto iter = onlineuser.find(name);
if (iter != onlineuser.end())
return;
// onlineuser.insert(std::make_pair<const std::string, const struct sockaddr_in>(name, peer));
onlineuser.insert(std::pair<const std::string, const struct sockaddr_in>(name, peer));
}
void Recv()
{
char buffer[1024];
while (true)
{
// 收
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 这里一定要写清楚,未来你传入的缓冲区大小
int n = recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (n > 0)
buffer[n] = '\0';
else
continue;
std::cout << "recv done ..." << std::endl;
// 提取client信息 -- debug
std::string clientip = inet_ntoa(peer.sin_addr);
uint16_t clientport = ntohs(peer.sin_port);
std::cout << clientip << "-" << clientport << "# " << buffer << std::endl;
// 构建一个用户,并检查
std::string name = clientip;
name += "-";
name += std::to_string(clientport);
// 如果不存在,就插入,如果存在,什么都不做
addUser(name, peer);
std::string message = name + ">> " + buffer;
rq.push(message);
// // 做业务处理
// std::string message = service_(buffer);
// 发
// sendto(sock_, message.c_str(), message.size(), 0, (struct sockaddr*)&peer, sizeof(peer));
}
}
void Broadcast()
{
while (true)
{
std::string sendstring;
rq.pop(&sendstring);
std::vector<struct sockaddr_in> v;
{
LockGuard lockguard(&lock); // 这个地方不是最优方式
for (auto user : onlineuser)
{
v.push_back(user.second);
}
}
for (auto user : v)
{
// std::cout << "Broadcast message to " << user.first << sendstring << std::endl;
sendto(sock_, sendstring.c_str(), sendstring.size(), 0, (struct sockaddr *)&(user), sizeof(user));
std::cout << "send done ..." << sendstring << std::endl;
}
}
}
~UdpServer()
{
pthread_mutex_destroy(&lock);
c->join();
p->join();
delete c;
delete p;
}
private:
int sock_;
uint16_t port_;
// func_t service_; // 我们的网络服务器刚刚解决的是网络IO的问题,要进行业务处理
std::unordered_map<std::string, struct sockaddr_in> onlineuser;
pthread_mutex_t lock;
RingQueue<std::string> rq;
Thread *c;
Thread *p;
// std::string ip_; // 后面要专门研究一下,后面要去掉这个ip
};
} // end NS_SERVER
--------------------------------udp_server.cc-----------------------
#include "udp_server.hpp"
#include <memory>
#include <string>
#include <cstdio>
using namespace ns_server;
using namespace std;
static void usage(string proc)
{
std::cout << "Usage:\n\t" << proc << " port\n"
<< std::endl;
}
// 上层的业务处理,不关心网络发送,只负责信息处理即可
std::string transactionString(std::string request) // request 就是一个string
{
std::string result;
char c;
for (auto &r : request)
{
if (islower(r))
{
c = toupper(r);
result.push_back(c);
}
else
{
result.push_back(r);
}
}
return result;
}
static bool isPass(const std::string &command)
{
bool pass = true;
auto pos = command.find("rm");
if(pos != std::string::npos) pass=false;
pos = command.find("mv");
if(pos != std::string::npos) pass=false;
pos = command.find("while");
if(pos != std::string::npos) pass=false;
pos = command.find("kill");
if(pos != std::string::npos) pass=false;
return pass;
}
// 让同学们,在你的本地把命令给我,server再把结果给你!
// ls -a -l
std::string excuteCommand(std::string command) // command就是一个命名
{
// 1. 安全检查
if(!isPass(command)) return "you are bad man!";
// 2. 业务逻辑处理
FILE *fp = popen(command.c_str(), "r");
if(fp == nullptr) return "None";
// 3. 获取结果了
char line[1024];
std::string result;
while(fgets(line, sizeof(line), fp) != NULL)
{
result += line;
}
pclose(fp);
return result;
}
// ./udp_server port
int main(int argc, char *argv[])
{
if (argc != 2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
// unique_ptr<UdpServer> usvr(new UdpServer("120.78.126.148", 8082));
// unique_ptr<UdpServer> usvr(new UdpServer(transactionString, port));
// unique_ptr<UdpServer> usvr(new UdpServer(excuteCommand, port));
unique_ptr<UdpServer> usvr(new UdpServer(port));
// usvr->InitServer(); // 服务器的初始化
usvr->start();
return 0;
}
---------------------------lockGuard.hpp-------------------------------------
#pragma once
#include <iostream>
#include <pthread.h>
class Mutex // 自己不维护锁,有外部传入
{
public:
Mutex(pthread_mutex_t *mutex):_pmutex(mutex)
{}
void lock()
{
pthread_mutex_lock(_pmutex);
}
void unlock()
{
pthread_mutex_unlock(_pmutex);
}
~Mutex()
{}
private:
pthread_mutex_t *_pmutex;
};
class LockGuard // 自己不维护锁,有外部传入
{
public:
LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
{
_mutex.lock();
}
~LockGuard()
{
_mutex.unlock();
}
private:
Mutex _mutex;
};
----------------------------thread.hpp------------------------------------
#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <pthread.h>
#include <functional>
class Thread
{
public:
typedef enum
{
NEW = 0,
RUNNING,
EXITED
} ThreadStatus;
// typedef void (*func_t)(void *);
using func_t = std::function<void ()>;
public:
Thread(int num, func_t func) : _tid(0), _status(NEW), _func(func)
{
char name[128];
snprintf(name, sizeof(name), "thread-%d", num);
_name = name;
}
int status() { return _status; }
std::string threadname() { return _name; }
pthread_t threadid()
{
if (_status == RUNNING)
return _tid;
else
{
return 0;
}
}
// 是不是类的成员函数,而类的成员函数,具有默认参数this,需要static
// 但是会有新的问题:static成员函数,无法直接访问类属性和其他成员函数
static void *runHelper(void *args)
{
Thread *ts = (Thread*)args; // 就拿到了当前对象
// _func(_args);
(*ts)();
return nullptr;
}
void operator ()() //仿函数
{
if(_func != nullptr) _func();
}
void run()
{
int n = pthread_create(&_tid, nullptr, runHelper, this);
if(n != 0) exit(1);
_status = RUNNING;
}
void join()
{
int n = pthread_join(_tid, nullptr);
if( n != 0)
{
std::cerr << "main thread join thread " << _name << " error" << std::endl;
return;
}
_status = EXITED;
}
~Thread()
{
}
private:
pthread_t _tid;
std::string _name;
func_t _func; // 线程未来要执行的回调
ThreadStatus _status;
};
------------------------------ringQueue.hpp----------------------------------
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
static const int N = 50;
template <class T>
class RingQueue
{
private:
void P(sem_t &s)
{
sem_wait(&s);
}
void V(sem_t &s)
{
sem_post(&s);
}
void Lock(pthread_mutex_t &m)
{
pthread_mutex_lock(&m);
}
void Unlock(pthread_mutex_t &m)
{
pthread_mutex_unlock(&m);
}
public:
RingQueue(int num = N) : _ring(num), _cap(num)
{
sem_init(&_data_sem, 0, 0);
sem_init(&_space_sem, 0, num);
_c_step = _p_step = 0;
pthread_mutex_init(&_c_mutex, nullptr);
pthread_mutex_init(&_p_mutex, nullptr);
}
// 生产
void push(const T &in)
{
// 1. 可以不用在临界区内部做判断,就可以知道临界资源的使用情况
// 2. 什么时候用锁,什么时候用sem?你对应的临界资源,是否被整体使用!
P(_space_sem); // P()
Lock(_p_mutex); //? 1
// 一定有对应的空间资源给我!不用做判断,是哪一个呢?
_ring[_p_step++] = in;
_p_step %= _cap;
Unlock(_p_mutex);
V(_data_sem);
}
// 消费
void pop(T *out)
{
P(_data_sem);
Lock(_c_mutex); //?
*out = _ring[_c_step++];
_c_step %= _cap;
Unlock(_c_mutex);
V(_space_sem);
}
~RingQueue()
{
sem_destroy(&_data_sem);
sem_destroy(&_space_sem);
pthread_mutex_destroy(&_c_mutex);
pthread_mutex_destroy(&_p_mutex);
}
private:
std::vector<T> _ring;
int _cap; // 环形队列容器大小
sem_t _data_sem; // 只有消费者关心
sem_t _space_sem; // 只有生产者关心
int _c_step; // 消费位置
int _p_step; // 生产位置
pthread_mutex_t _c_mutex;
pthread_mutex_t _p_mutex;
};
-----------------------------err.hpp-----------------------------------
------------------------------udp_client.cc----------------------------------
#include "udp_client.hpp"
#include <cstring>
#include <pthread.h>
// 127.0.0.1: 本地环回,就表示的就是当前主机,通常用来进行本地通信或者测试
static void usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
<< std::endl;
}
void *recver(void *args)
{
int sock = *(static_cast<int *>(args));
while (true)
{
// 接受
char buffer[2048];
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int n = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &len);
if (n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl; //1
}
}
}
// ./udp_client serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "create socket error" << std::endl;
exit(SOCKET_ERR);
}
// client 这里要不要bind呢?要的!socket通信的本质[clientip:clientport, serverip:serverport]
// 要不要自己bind呢?不需要自己bind,也不要自己bind,OS自动给我们进行bind -- 为什么?client的port要随机让OS分配防止client出现
// 启动冲突 -- server 为什么要自己bind?1. server的端口不能随意改变,众所周知且不能随意改变的 2. 同一家公司的port号
// 需要统一规范化
// 明确server是谁
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
pthread_t tid;
pthread_create(&tid, nullptr, recver, &sock);
while (true)
{
// 多线程化??
// 用户输入
std::string message;
std::cerr << "Please Enter Your Message# "; // 2
// std::cin >> message;
std::getline(std::cin, message);
// 什么时候bind的?在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP,1. bind 2. 构建发送的数据报文
// 发送
sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
}
return 0;
}
--------------------------------udp_client.hpp-----------------------
#pragma once
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string>
#include<iostream>
#include"err.hpp"
-------------------------Makefile---------------------------------
.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 -lpthread
.PHONY:clean
clean:
rm -f udp_client udp_server
首先使用mkfifo创建一个管道如下图
然后运行udp_client将结果定向到管道,读取管道内的内容(相当于聊天窗口)
简单的TCP网络程序
------------------------err.hpp---------------------------------------
#pragma once
enum{
USAGE_ERR=1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR
};
--------------------------Makefile-------------------------------------
.PHONY:all
all:tcp_client tcp_server
tcp_client:tcp_client.cc
g++ -o $@ $^ -std=c++11 -lpthread
tcp_server:tcp_server.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f tcp_client tcp_server
-----------------------tcp_client.cc----------------------------------------
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<functional>
#include"err.hpp"
static void usage(std::string proc)
{
std::cout<<"Usage:\n\t"<<proc<<"port\n"<<std::endl;
}
//tcp_client
int main(int argc,char* argv[])
{
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport= atoi(argv[2]);
//1、创建socket
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
std::cerr<<"socket error"<<strerror(errno)<<std::endl;
exit(SOCKET_ERR);
}
//客户端要绑定,但是不需要自己绑定;不需要listen;也不需要accept
//2、connect
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
inet_aton(serverip.c_str(),&server.sin_addr);
int cnt = 5;
while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)
{
sleep(1);
std::cout<<"正在尝试重连,重连次数还有:"<<cnt--<<std::endl;
if(cnt<=0) break;
}
//连接成功or连接失败
if(cnt<=0)
{
//连接失败
std::cerr<<"连接失败..."<<std::endl;
exit(CONNECT_ERR);
}
//3、连接成功
char buffer[1024];
while(true)
{
std::string line;
std::cout<<"Enter>> ";
std::getline(std::cin,line);
write(sock,line.c_str(),line.size());
ssize_t s = read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s] = 0;
std::cout<<"server echo "<<buffer<<std::endl;
}
else if(s==0)
{
std::cerr<<"server quit"<<std::endl;
break;
}
else
{
std::cerr<<"read error:"<<strerror(errno)<<std::endl;
break;
}
}
close(sock);
return 0;
}
-------------------------tcp_server.cc--------------------------------------
#include"tcp_server.hpp"
#include<memory>
using namespace std;
using namespace ns_server;
static void usage(string proc)
{
std::cout<<"Usage:\n\t"<<proc<<"port\n"<<std::endl;
}
std::string echo(const std::string& message)
{
return message;;
}
//tcp_server port
int main(int argc,char* argv[])
{
if(argc!=2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port =atoi(argv[1]);
unique_ptr<TcpServer> tsvr(new TcpServer(echo,port));
tsvr->initServer();
tsvr->start();
return 0;
}
---------------------------tcp_server.hpp------------------------------------
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<functional>
#include"err.hpp"
namespace ns_server
{
static const uint16_t defaultport = 8080;
static int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;
class TcpServer
{
public:
TcpServer(func_t func,uint16_t port = defaultport)
:_func(func),_port(port),_quit(true)
{}
void initServer()
{
//1、创建socket
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
std::cerr<<"create socket error"<<std::endl;
exit(SOCKET_ERR);
}
//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 = htonl(INADDR_ANY);
if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cerr<<"bind socket error"<<std::endl;
exit(BIND_ERR);
}
//3、监听
if(listen(_listensock,backlog)<0)
{
std::cerr<<"bind socket error"<<std::endl;
exit(LISTEN_ERR);
}
}
void start()
{
_quit = false;
while(!_quit)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
//4、获取连接、accept
int sock = accept(_listensock,(struct sockaddr*)&client,&len) ;
if(sock<0)
{
std::cerr<<"accept error"<<std::endl;
continue;
}
//提取client信息
std::string clientip = inet_ntoa(client.sin_addr);
uint16_t clientport = ntohs(client.sin_port);
//5、获取新连接成功
std::cout<<"获取新连接成功:"<<sock<<" from "<<_listensock<<" , "<<clientip<<" - "<<clientport<<std::endl;
service(sock,clientip,clientport);
}
}
void service(int sock,const std::string &clientip,const uint16_t &clientport)
{
std::string who = clientip + '-' + std::to_string(clientport);
char buffer[1024];
while(true)
{
ssize_t s = read(sock, buffer,sizeof(buffer)-1);//recv TODO
if(s>0)
{
//read成功
buffer[s] = 0;
std::string res = _func(buffer);//进行回调
std::cout<<who<<">>>"<<res<<std::endl;
write(sock,res.c_str(),res.size());
}
else if(s==0)
{
//对方将连接关闭
close(sock);
std::cout<<who<<" quit,me too"<<std::endl;
}
else
{
//read失败
std::cout<<"read error "<<strerror(errno)<<std::endl;
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensock;
bool _quit;
func_t _func;
};
}
运行结果:可以看到我们的程序在面对多线程(多客户端)的情况时无法应对,对客户端程序的连接呈串行方式。接下来我们会解决多客户端不能同时访问服务器这个问题
TCP简单程序多进程版本
---------------------------tcp_server.hpp------------------------------------
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<wait.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<functional>
#include<signal.h>
#include"err.hpp"
namespace ns_server
{
static const uint16_t defaultport = 8080;
static int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;
class TcpServer
{
public:
TcpServer(func_t func,uint16_t port = defaultport)
:_func(func),_port(port),_quit(true)
{}
void initServer()
{
//1、创建socket
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
std::cerr<<"create socket error"<<std::endl;
exit(SOCKET_ERR);
}
//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 = htonl(INADDR_ANY);
if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cerr<<"bind socket error"<<std::endl;
exit(BIND_ERR);
}
//3、监听
if(listen(_listensock,backlog)<0)
{
std::cerr<<"bind socket error"<<std::endl;
exit(LISTEN_ERR);
}
}
void start()
{
signal(SIGCHLD,SIG_IGN);//保证子进程退出之后,资源被回收
_quit = false;
while(!_quit)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
//4、获取连接、accept
int sock = accept(_listensock,(struct sockaddr*)&client,&len) ;
if(sock<0)
{
std::cerr<<"accept error"<<std::endl;
continue;
}
//提取client信息
std::string clientip = inet_ntoa(client.sin_addr);
uint16_t clientport = ntohs(client.sin_port);
//5、获取新连接成功
std::cout<<"获取新连接成功:"<<sock<<" from "<<_listensock<<" , "<<clientip<<" - "<<clientport<<std::endl;
// v1 service(sock,clientip,clientport);
//v2 多进程
pid_t id = fork();
if(id<0)
{
close(sock);
continue;
}
else if(id == 0)//子进程,父子不会用同一张文件描述符表,但是父进程的fd会被子进程继承
{
//建议关闭掉不需要的fd
close(_listensock);
service(sock, clientip,clientport);
exit(0);
}
//父进程一定要关闭不需要的fd,不关闭会导致fd泄漏
close(sock);
// pid_t ret = waitpid(id,nullptr,0);//阻塞的
// if(ret == id)
// {
// std::cout<<"wait child"<<id<<"success"<<std::endl;
// }
}
}
void service(int sock,const std::string &clientip,const uint16_t &clientport)
{
std::string who = clientip + '-' + std::to_string(clientport);
char buffer[1024];
while(true)
{
ssize_t s = read(sock, buffer,sizeof(buffer)-1);//recv TODO
if(s>0)
{
//read成功
buffer[s] = 0;
std::string res = _func(buffer);//进行回调
std::cout<<who<<">>>"<<res<<std::endl;
write(sock,res.c_str(),res.size());
}
else if(s==0)
{
//对方将连接关闭
close(sock);
std::cout<<who<<" quit,me too"<<std::endl;
break;
}
else
{
//read失败
close(sock);
std::cout<<"read error:"<<strerror(errno)<<std::endl;
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensock;
bool _quit;
func_t _func;
};
}
运行结果:
TCP简单程序多线程版本
---------------------------tcp_server.hpp------------------------------------
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<wait.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<functional>
#include<signal.h>
#include"err.hpp"
namespace ns_server
{
static const uint16_t defaultport = 8080;
static int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;
class TcpServer;
class ThreadData
{
public:
ThreadData(int fd,const std::string &ip,const uint16_t &port, TcpServer *current)
:_sock(fd),_clientip(ip),_clientport(port),_current(current)
{}
public:
int _sock;
std::string _clientip;
uint16_t _clientport;
TcpServer *_current;
};
class TcpServer
{
public:
TcpServer(func_t func,uint16_t port = defaultport)
:_func(func),_port(port),_quit(true)
{}
void initServer()
{
//1、创建socket
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
std::cerr<<"create socket error"<<std::endl;
exit(SOCKET_ERR);
}
//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 = htonl(INADDR_ANY);
if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cerr<<"bind socket error"<<std::endl;
exit(BIND_ERR);
}
//3、监听
if(listen(_listensock,backlog)<0)
{
std::cerr<<"bind socket error"<<std::endl;
exit(LISTEN_ERR);
}
}
void start()
{
signal(SIGCHLD,SIG_IGN);//保证子进程退出之后,资源被回收
_quit = false;
while(!_quit)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
//4、获取连接、accept
int sock = accept(_listensock,(struct sockaddr*)&client,&len) ;
if(sock<0)
{
std::cerr<<"accept error"<<std::endl;
continue;
}
//提取client信息
std::string clientip = inet_ntoa(client.sin_addr);
uint16_t clientport = ntohs(client.sin_port);
//5、获取新连接成功
std::cout<<"获取新连接成功:"<<sock<<" from "<<_listensock<<" , "<<clientip<<" - "<<clientport<<std::endl;
// v1 service(sock,clientip,clientport);
//v2 多进程
// pid_t id = fork();
// if(id<0)
// {
// close(sock);
// continue;
// }
// else if(id == 0)//子进程,父子不会用同一张文件描述符表,但是父进程的fd会被子进程继承
// {
// //建议关闭掉不需要的fd
// close(_listensock);
// service(sock, clientip,clientport);
// exit(0);
// }
// //父进程一定要关闭不需要的fd,不关闭会导致fd泄漏
// close(sock);
// // pid_t ret = waitpid(id,nullptr,0);//阻塞的
// // if(ret == id)
// // {
// // std::cout<<"wait child"<<id<<"success"<<std::endl;
// // }
//v3 多线程
pthread_t tid;
ThreadData *td = new ThreadData(sock,clientip,clientport,this);
pthread_create(&tid,nullptr,threadRoutine,td);
}
}
static void* threadRoutine(void* args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
td->_current->service(td->_sock,td->_clientip,td->_clientport);
delete td;
return nullptr;
}
void service(int sock,const std::string &clientip,const uint16_t &clientport)
{
std::string who = clientip + '-' + std::to_string(clientport);
char buffer[1024];
while(true)
{
ssize_t s = read(sock, buffer,sizeof(buffer)-1);//recv TODO
if(s>0)
{
//read成功
buffer[s] = 0;
std::string res = _func(buffer);//进行回调
std::cout<<who<<">>>"<<res<<std::endl;
write(sock,res.c_str(),res.size());
}
else if(s==0)
{
//对方将连接关闭
close(sock);
std::cout<<who<<" quit,me too"<<std::endl;
break;
}
else
{
//read失败
close(sock);
std::cout<<"read error:"<<strerror(errno)<<std::endl;
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensock;
bool _quit;
func_t _func;
};
}
运行结果:
TCP通信程序守护进程+线程池+日志版本
守护进程( Daemon)
- 1、定义
守护进程是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或循环等待处理某些事件的发生;它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。
守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机才随之一起停止运行;
守护进程一般都以root用户权限运行,因为要使用某些特殊的端口(1-1024)或者资源;
守护进程的父进程一般都是init进程,因为它真正的父进程在fork出守护进程后就直接退出了,所以守护进程都是孤儿进程,由init接管;
守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。
守护进程的名称通常以d结尾,比如sshd、xinetd、crond等
- 2、作用
1.守护进程是一个生存周期较长的进程,通常独立于控制终端并且周期性的执行某种任务或者等待处理某些待发生的事件
2.大多数服务都是通过守护进程实现的
3.关闭终端,相应的进程都会被关闭,而守护进程却能够突破这种限制 Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括:
系统日志进程syslogd、 web服务器httpd、 邮件服务器sendmail 数据库服务器mysqld等。
3、创建流程
背景知识:
进程组:一个或多个进程的集合,进程组由进程组ID标识,进程组长的进程ID和进程组ID一致,并且进程组ID不会由于进程组长的退出而受到影响
会话周期:一个或多个进程组的集合,比如用户从登陆到退出,这个期间用户运行的所有进程都属于该会话周期
setsid函数:创建一个新会话,并担任该会话组的组长,调用setsid函数的目的:让进程摆脱原会话,原进程组,原终端的控制。如果,调用setsid的进程不是一个进程组的组长,此函数创建一个新的会话期。
创建守护进程的过程:
1)fork()创建子进程,父进程exit()退出
这是创建守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离,在后台工作。2)在子进程中调用 setsid() 函数创建新的会话
在调用了fork()函数后,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,这还不是真正意义上的独立开来,而
setsid() 函数能够使进程完全独立出来。3)再次 fork() 一个孙进程并让子进程退出
为什么要再次fork呢,假定有这样一种情况,之前的父进程fork出子进程以后还有别的事情要做,在做事情的过程中因为某种原因阻塞了,而此时的子进程因为某些非正常原因要退出的话,就会形成僵尸进程,所以由子进程fork出一个孙进程以后立即退出,孙进程作为守护进程会被init接管,此时无论父进程想做什么都随它了。4)在孙进程中调用 chdir() 函数,让根目录 ”/” 成为孙进程的工作目录
这一步也是必要的步骤,使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件系统(如“/mnt/usb”)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此,通常的做法是让"/"作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp,改变工作目录的常见函数是chdir。
5)在孙进程中调用 umask() 函数,设置进程的文件权限掩码为0
文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask。在这里,通常的使用方法为umask(0)。6)在孙进程中关闭任何不需要的文件描述符
同文件权限码一样,用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。在上面的第2)步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2
的3个文件(常说的输入、输出和报错)已经失去了存在的价值,也应被关闭。7)守护进程退出处理 当用户需要外部停止守护进程运行时,往往会使用 kill 命令停止该守护进程。所以,守护进程中需要编码来实现 kill
发出的signal信号处理,达到进程的正常退出。
--------------daemon.hpp----------------------------
#pragma once
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include"log.hpp"
void Daemon()
{
//1、忽略信号
signal(SIGPIPE,SIG_IGN);
signal(SIGCHLD,SIG_IGN);
//2、让自己不要成为组长
if(fork()>0) exit(0);//父进程直接退,子进程就不是进程组第一个,不是组长就可以成功调用守护进程
//3、新建回话,自己成为会话的话首进程
pid_t ret = setsid();
if((int)ret == -1)
{
logMessage(FATAL,"daemon error,code:%d,string:%s",errno,strerror(errno));
exit(SETSID_ERR);
}
//4、可以更改守护进程的工作路径
//chdir("/");//更改工作目录为根目录
//5、处理后续对于0,1,2的问题
int fd = open("/dev/null",O_RDWR);
if(fd<0)
{
logMessage(FATAL,"open error,code:%d,string:%s",errno,strerror(errno));
exit(OPEN_ERR);
}
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
close(fd);
}
---------------err.hpp---------------------------
#pragma once
enum{
USAGE_ERR=1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
SETSID_ERR,
OPEN_ERR
};
----------------llog.hpp--------------------------
#pragma once
#include<cstdio>
#include<cstdarg>
#include <string>
#include<sys/types.h>
#include<unistd.h>
#include<ctime>
const std::string filename = "tcpserver.log";
//日志等级
enum
{
DEBUG = 0,
INFO ,
WARNING ,
ERROR ,
FATAL ,
UNKNOWN
};
static std::string toLevelString(int level)
{
switch(level)
{
case DEBUG:return "DEBUG";
case INFO:return "INFO";
case WARNING:return "WARNING";
case ERROR:return "ERROR";
case FATAL:return "FATAL";
default: return "UNKNOWN";
}
}
std::string getTime()
{
time_t cur = time(nullptr);
struct tm *tmp = localtime(&cur);
char buffer[128];
snprintf(buffer,sizeof(buffer),"%d-%d-%d %d:%d:%d",tmp->tm_year+1900,tmp->tm_mon+1,\
tmp->tm_mday,tmp->tm_hour,tmp->tm_min,tmp->tm_sec);
return buffer;
}
//日志格式:日志头(日志等级、时间、文件、行号、pid) 日志体(消息体)
void logMessage(int level,const char* format,...)
{
// va_list p;
// int a = va_arg(p,int);//根据类型提取参数
// va_start(p,format);//p指向可变参数部分的起始地址
// va_end(p);//p=NULL;
char logHead[1024];
std::string level_string = toLevelString(level);
std::string cur_time = getTime();
snprintf(logHead,sizeof(logHead),"[%s] [%s] [%d] ",level_string.c_str(),cur_time.c_str(),getpid());
char logBody[1024];
va_list p;
va_start(p,format);
vsnprintf(logBody,sizeof(logBody),format,p);
va_end(p);
//打印
// printf("%s%s\n",logHead,logBody);
FILE *fp = fopen(filename.c_str(),"a");
if(fp==nullptr) return;
fprintf(fp,"%s%s\n",logHead,logBody);
fflush(fp);
fclose(fp);
}
------------------Makefile------------------------
.PHONY:all
all:tcp_client tcp_server
tcp_client:tcp_client.cc
g++ -o $@ $^ -std=c++11 #-lpthread
tcp_server:tcp_server.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f tcp_client tcp_server
----------------task.hpp--------------------------
#pragma once
#include<iostream>
#include<string>
#include<functional>
#include<unistd.h>
using cb_t = std::function<void(int,const std::string &,const uint16_t &)>;
class Task
{
public:
Task()
{}
Task(int sock,const std::string &ip,const uint16_t &port,cb_t cb)
:_sock(sock),_ip(ip),_port(port),_cb(cb)
{
}
void operator()()
{
_cb(_sock,_ip,_port);
}
~Task()
{
}
private:
int _sock;
std::string _ip;
uint16_t _port;
cb_t _cb;
};
----------------tcp_client.cc--------------------------
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<functional>
#include"err.hpp"
static void usage(std::string proc)
{
std::cout<<"Usage:\n\t"<<proc<<"port\n"<<std::endl;
}
//tcp_client
int main(int argc,char* argv[])
{
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport= atoi(argv[2]);
//1、创建socket
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
std::cerr<<"socket error"<<strerror(errno)<<std::endl;
exit(SOCKET_ERR);
}
//客户端要绑定,但是不需要自己绑定;不需要listen;也不需要accept
//2、connect
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
inet_aton(serverip.c_str(),&server.sin_addr);
int cnt = 5;
while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)
{
sleep(1);
std::cout<<"正在尝试重连,重连次数还有:"<<cnt--<<std::endl;
if(cnt<=0) break;
}
//连接成功or连接失败
if(cnt<=0)
{
//连接失败
std::cerr<<"连接失败..."<<std::endl;
exit(CONNECT_ERR);
}
//3、连接成功
char buffer[1024];
while(true)
{
std::string line;
std::cout<<"Enter>> ";
std::getline(std::cin,line);
write(sock,line.c_str(),line.size());
ssize_t s = read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s] = 0;
std::cout<<"server echo >>>"<<buffer<<std::endl;
}
else if(s==0)
{
std::cerr<<"server quit"<<std::endl;
break;
}
else
{
std::cerr<<"read error:"<<strerror(errno)<<std::endl;
break;
}
}
close(sock);
return 0;
}
--------------tcp_server.cc----------------------------
#include"tcp_server.hpp"
#include"daemon.hpp"
#include<memory>
using namespace std;
using namespace ns_server;
static void usage(string proc)
{
std::cout<<"Usage:\n\t"<<proc<<"port\n"<<std::endl;
}
std::string echo(const std::string& message)
{
return message;;
}
//tcp_server port
int main(int argc,char* argv[])
{
if(argc!=2)
{
usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port =atoi(argv[1]);
unique_ptr<TcpServer> tsvr(new TcpServer(echo,port));
tsvr->initServer();
//将服务器守护进程化
Daemon();
tsvr->start();
return 0;
}
-------------tcp_server.hpp-----------------------------
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<wait.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<functional>
#include<signal.h>
#include"threadPool.hpp"
#include"log.hpp"
#include"task.hpp"
#include"err.hpp"
namespace ns_server
{
static const uint16_t defaultport = 8080;
static int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;
class TcpServer;
class ThreadData
{
public:
ThreadData(int fd,const std::string &ip,const uint16_t &port, TcpServer *current)
:_sock(fd),_clientip(ip),_clientport(port),_current(current)
{}
public:
int _sock;
std::string _clientip;
uint16_t _clientport;
TcpServer *_current;
};
class TcpServer
{
public:
TcpServer(func_t func,uint16_t port = defaultport)
:_func(func),_port(port),_quit(true)
{}
void initServer()
{
//1、创建socket
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
// std::cerr<<"create socket error"<<std::endl;
logMessage(FATAL,"creae socket error,code:%d,error string:%s",errno,strerror(errno));
exit(SOCKET_ERR);
}
logMessage(INFO,"creae socket success,code:%d,error string:%s",errno,strerror(errno));
//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 = htonl(INADDR_ANY);
if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)
{
// std::cerr<<"bind socket error"<<std::endl;
logMessage(FATAL,"bind socket error,code:%d,error string:%s",errno,strerror(errno));
exit(BIND_ERR);
}
logMessage(INFO,"bind socket success,code:%d,error string:%s",errno,strerror(errno));
//3、监听
if(listen(_listensock,backlog)<0)
{
// std::cerr<<"listen socket error"<<std::endl;
logMessage(FATAL,"listen socket error,code:%d,error string:%s",errno,strerror(errno));
exit(LISTEN_ERR);
}
logMessage(INFO,"listen socket success,code:%d,error string:%s",errno,strerror(errno));
}
void start()
{
signal(SIGCHLD,SIG_IGN);//保证子进程退出之后,资源被回收
_quit = false;
while(!_quit)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
//4、获取连接、accept
int sock = accept(_listensock,(struct sockaddr*)&client,&len) ;
if(sock<0)
{
// std::cerr<<"accept error"<<std::endl;
logMessage(WARNING,"accept error,code:%d,error string:%s",errno,strerror(errno));
continue;
}
//提取client信息
std::string clientip = inet_ntoa(client.sin_addr);
uint16_t clientport = ntohs(client.sin_port);
//5、获取新连接成功
std::cout<<"获取新连接成功:"<<sock<<" from "<<_listensock<<" , "<<clientip<<" - "<<clientport<<std::endl;
logMessage(INFO,"获取连接成功: %d from %d ,who:%s - %d",socket,_listensock,clientip.c_str(),clientport);
// v1 service(sock,clientip,clientport);
//v2 多进程
// pid_t id = fork();
// if(id<0)
// {
// close(sock);
// continue;
// }
// else if(id == 0)//子进程,父子不会用同一张文件描述符表,但是父进程的fd会被子进程继承
// {
// //建议关闭掉不需要的fd
// close(_listensock);
// service(sock, clientip,clientport);
// exit(0);
// }
// //父进程一定要关闭不需要的fd,不关闭会导致fd泄漏
// close(sock);
// // pid_t ret = waitpid(id,nullptr,0);//阻塞的
// // if(ret == id)
// // {
// // std::cout<<"wait child"<<id<<"success"<<std::endl;
// // }
// //v3 多线程
// pthread_t tid;
// ThreadData *td = new ThreadData(sock,clientip,clientport,this);
// pthread_create(&tid,nullptr,threadRoutine,td);
//v4 线程池
Task t(sock,clientip,clientport,std::bind(&TcpServer::service,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
ThreadPool<Task>::getinstance()->pushTask(t);
}
}
static void* threadRoutine(void* args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
td->_current->service(td->_sock,td->_clientip,td->_clientport);
delete td;
return nullptr;
}
void service(int sock,const std::string &clientip,const uint16_t &clientport)
{
std::string who = clientip + '-' + std::to_string(clientport);
char buffer[1024];
while(true)
{
ssize_t s = read(sock, buffer,sizeof(buffer)-1);//recv TODO
if(s>0)
{
//read成功
buffer[s] = 0;
std::string res = _func(buffer);//进行回调
// std::cout<<who<<">>>"<<res<<std::endl;
logMessage(DEBUG,"%s# %s ",who.c_str(),res.c_str());
write(sock,res.c_str(),res.size());
}
else if(s==0)
{
//对方将连接关闭
// std::cout<<who<<" quit,me too"<<std::endl;
logMessage(INFO,"%squit,me too ",who.c_str());
break;
}
else
{
//read失败
// std::cout<<"read error:"<<strerror(errno)<<std::endl;
logMessage(ERROR,"read error,%d:%",errno,strerror(errno));
break;
}
close(sock);
}
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensock;
bool _quit;
func_t _func;
};
}
------------------thread.hpp------------------------
#pragma once
#include<iostream>
#include<string>
#include<cstdlib>
#include<pthread.h>
class Thread
{
public:
typedef enum
{
NEW = 0,
RUNNING,
EXITED
}ThreadStatus;
typedef void (*func_t)(void*);
public:
Thread(int num,func_t func,void *args):_tid(0),_status(NEW),_func(func),_args(args)
{
char name[128];
snprintf(name,sizeof(name),"thread-%d",num);
_name = name;
}
int status(){return _status;}
std::string threadname(){return _name;}
pthread_t threadid()
{
if(_status == RUNNING)
return _tid;
else
{
return 0;
}
}
//是不是类的成员函数,而类的成员函数,具有莫惹吧参数this,需要static
//但是会有新的问题,static成员函数,无法直接访问类属性和其他成员函数
static void* runHelper(void *args)
{
Thread *ts = (Thread*)args;
(*ts)();
return nullptr;
}
void operator()()
{
if(_func!=nullptr)_func(_args);
}
void run()
{
int n = pthread_create(&_tid,nullptr,runHelper,this);
if(n!=0) exit(1);
_status = RUNNING;
}
void join()
{
int n = pthread_join(_tid,nullptr);
if(n!=0)
{
std::cerr<<"main thread join thread"<<_name<<"error"<<std::endl;
return;
}
_status = EXITED;
}
~Thread()
{
}
private:
pthread_t _tid;
std::string _name;
func_t _func;//线程未来需要执行的回调
void *_args;
ThreadStatus _status;
};
--------------threadPool.hpp----------------------------
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <unistd.h>
#include "thread.hpp"
#include "task.hpp"
#include"log.hpp"
#include"lockGuard.hpp"
const static int N = 10;
template <class T>
class ThreadPool
{
private:
ThreadPool(int num = N) : _num(num)
{
pthread_mutex_init(&_lock, nullptr);
pthread_cond_init(&_cond, nullptr);
}
ThreadPool(const ThreadPool<T> &tp)=delete;
void operator=(const ThreadPool<T> &tp) = delete;
public:
static ThreadPool<T> *getinstance()
{
if(nullptr==instance)
{
LockGuard lockguard(&instance_lock);
if(nullptr==instance)
{
// std::cout<<"线程池单例形成"<<std::endl;
logMessage(DEBUG,"线程池单例形成");
instance = new ThreadPool<T>();
instance->init();
instance->start();
}
}
return instance;
}
pthread_mutex_t* getlock()
{
return &_lock;
}
void threadWait()
{
pthread_cond_wait(&_cond, &_lock);
}
void threadWakeup()
{
pthread_cond_signal(&_cond);
}
bool isEmpty()
{
return _tasks.empty();
}
T popTask()
{
T t = _tasks.front();
_tasks.pop();
return t;
}
static void threadRoutine(void *args)
{
// pthread_detach(pthread_self());
ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
while (true)
{
// 1. 检测有没有任务
// 2. 有:处理
// 3. 无:等待
// 细节:必定加锁
T t;
LockGuard lockguard(tp->getlock());
while (tp->isEmpty())
{
tp->threadWait();
}
t = tp->popTask(); // 从公共区域拿到私有区域
// for test
t();
// t.run(); // 处理任务,应不应该在临界区中处理?1,0
}
}
void init()
{
for (int i = 0; i < _num; i++)
{
_threads.push_back(Thread(i, threadRoutine, this));
// std::cout<<i<<"thread runing "<<std::endl;
logMessage(DEBUG,"thread runing");
}
}
void start()
{
for (auto &t : _threads)
{
t.run();
}
}
void check()
{
for (auto &t : _threads)
{
std::cout << t.threadname() << " running..." << std::endl;
}
}
void pushTask(const T &t)
{
LockGuard lockguard(&_lock);
_tasks.push(t);
threadWakeup();
}
~ThreadPool()
{
for (auto &t : _threads)
{
t.join();
}
pthread_mutex_destroy(&_lock);
pthread_cond_destroy(&_cond);
}
private:
std::vector<Thread> _threads;
int _num;
std::queue<T> _tasks; // 使用stl的自动扩容的特性
pthread_mutex_t _lock;
pthread_cond_t _cond;
static ThreadPool<T> *instance;
static pthread_mutex_t instance_lock;
};
template <class T>
ThreadPool<T>* ThreadPool<T>::instance = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::instance_lock = PTHREAD_MUTEX_INITIALIZER;
运行结果:
TCP协议通讯流程
服务器初始化
- 调用socket,创建文件描述符
- 调用bind,将当前的文件描述符和ip/port绑定在一起;如果这个端口被其他进程占用了,就会bind失败
- 调用listen,声明当前这个文件描述符作为一个服务器的文件描述符,为后面的accept做好准备
- 调用accept,并阻塞,等待客户端连接过来
建立连接的过程
- 调用socket,创建文件描述符
- 调用connect,向服务器发起连接请求
- connect会发出SYN段并阻塞等待服务器应答;(第一次
- 服务器收到客户端的SYN,会应答一个SYN-ACK段表示“同意建立连接”;(第二次)
- 客户端收到SYN-ACK后会从connect()返回,同时应答一个ACK段;(第三次)
这个建立连接的过程,通常称为三次握手
数据传输的过程
- 建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方可以同时写数据;相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据;
- 服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;
- 这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理,
- 在此期间客户端调用read()阻塞等待服务器的应答; 服务器调用write()将处理结果发回给客户端,
- 再次调用read()阻塞等待下一条请求; 客户端收到后从read()返回, 发送下一条请求,如此循环下去;
断开连接的过程:
- 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);
- 此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);
- read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)
- 客户端收到FIN, 再返回一个ACK给服务器; (第四次)
这个断开连接的过程, 通常称为四次挥手
在学习socket API时要注意应用程序和TCP协议层是如何交互的:
- 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段
- 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段
TCP 和 UDP 对比
- 可靠传输 vs 不可靠传输
- 有连接 vs 无连接
- 字节流 vs 数据报
应用层
网络版计算器
约定方案一
- 客户端发送一个形如“1+1”的字符串
- 这个字符串中有两个操作数,都是整形
- 两个数字之间会有一个字符是运算符
- 数字和运算符之间没有空格
约定方案二
- 定义结构体来表示我们需要交互的信息
- 序列化(发送数据时将这个结构体按照一个规则转换成字符串)
- 反序列化(接受到数据的时候再按照相同的规则把字符串转化回结构体)
------------------CalculatorClient.cc------------------------------
#include "TcpClient.hpp"
#include "Sock.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <string>
using namespace protocol_ns;
static void usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
<< std::endl;
}
enum
{
LEFT,
OPER,
RIGHT
};
// 10+20
Request ParseLine(const std::string &line)
{
std::string left, right;
char op;
int status = LEFT;
int i = 0;
while(i < line.size())
{
// if(isdigit(e)) left.push_back;
switch (status)
{
case LEFT:
if (isdigit(line[i]))
left.push_back(line[i++]);
else
status = OPER;
break;
case OPER:
op = line[i++];
status = RIGHT;
break;
case RIGHT:
if (isdigit(line[i]))
right.push_back(line[i++]);
break;
}
}
Request req;
std::cout << "left: " << left << std::endl;
std::cout << "right: " << right << std::endl;
std::cout << "op: " << op << std::endl;
req._x = std::stoi(left);
req._y = std::stoi(right);
req._op = op;
return req;
}
// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
Sock sock;
sock.Socket();
int n = sock.Connect(serverip, serverport);
if (n != 0)
return 1;
std::string buffer;
while (true)
{
std::cout << "Enter# "; // 1+1,2*9
std::string line;
std::getline(std::cin, line);
Request req = ParseLine(line);
// std::cout << "data1# ";
// std::cin >> req._x;
// std::cout << "data2# ";
// std::cin >> req._y;
// std::cout << "op# ";
// std::cin >> req._op;
std::cout << "test: " << req._x << req._op << req._y << std::endl;
// 1. 序列化
std::string sendString;
req.Serialize(&sendString);
// 2. 添加报头
sendString = AddHeader(sendString);
// 3. send
send(sock.Fd(), sendString.c_str(), sendString.size(), 0);
// 4. 获取响应
std::string package;
int n = 0;
START:
n = ReadPackage(sock.Fd(), buffer, &package);
if (n == 0)
goto START;
else if (n < 0)
break;
else
{
}
// 5. 去掉报头
package = RemoveHeader(package, n);
// 6. 反序列化
Response resp;
resp.Deserialize(package);
std::cout << "result: " << resp._result << "[code: " << resp._code << "]" << std::endl;
}
sock.Close();
return 0;
}
------------CalculatorServer.hpp------------------------------
#include "TcpServer.hpp"
#include <memory>
using namespace tcpserver_ns;
// ./calserver 8888
Response calculate(const Request &req)
{
// 走到这里,一定保证req是有具体数据的!
// _result(result), _code(code)
Response resp(0, 0);
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 (req._y == 0)
resp._code = 1;
else
resp._result = req._x / req._y;
break;
case '%':
if (req._y == 0)
resp._code = 2;
else
resp._result = req._x % req._y;
break;
default:
resp._code = 3;
break;
}
return resp;
}
int main()
{
uint16_t port = 8888;
std::unique_ptr<TcpServer> tsvr(new TcpServer(calculate, port)); // TODO
tsvr->InitServer();
tsvr->Start();
return 0;
}
-------------------Err.hpp-----------------------------
#pragma once
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
SETSID_ERR,
OPEN_ERR
};
---------------------Log.hpp---------------------------
#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
// 日志是有日志等级的
const std::string filename = "log/tcpserver.log";
enum
{
Debug = 0,
Info,
Warning,
Error,
Fatal,
Uknown
};
static std::string toLevelString(int level)
{
switch (level)
{
case Debug:
return "Debug";
case Info:
return "Info";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "Uknown";
}
}
static std::string getTime()
{
time_t curr = time(nullptr);
struct tm *tmp = localtime(&curr);
char buffer[128];
snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon+1, tmp->tm_mday,
tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
return buffer;
}
// 日志格式: 日志等级 时间 pid 消息体
// logMessage(DEBUG, "hello: %d, %s", 12, s.c_str()); // DEBUG hello:12, world
void logMessage(int level, const char *format, ...)
{
char logLeft[1024];
std::string level_string = toLevelString(level);
std::string curr_time = getTime();
snprintf(logLeft, sizeof(logLeft), "[%s] [%s] [%d] ", level_string.c_str(), curr_time.c_str(), getpid());
char logRight[1024];
va_list p;
va_start(p, format);
vsnprintf(logRight, sizeof(logRight), format, p);
va_end(p);
// 打印
printf("%s%s\n", logLeft, logRight);
// 保存到文件中
// FILE *fp = fopen(filename.c_str(), "a");
// if(fp == nullptr)return;
// fprintf(fp,"%s%s\n", logLeft, logRight);
// fflush(fp); //可写也可以不写
// fclose(fp);
// 预备
// va_list p; // char *
// int a = va_arg(p, int); // 根据类型提取参数
// va_start(p, format); //p指向可变参数部分的起始地址
// va_end(p); // p = NULL;
}
--------------------Makefile----------------------------
.PHONY:all
all:calserver calclient
calclient:CalculatorClient.cc
g++ -o $@ $^ -std=c++11 -ljsoncpp
calserver:CalculatorServer.cc
g++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp
.PHONY:clean
clean:
rm -f calclient calserver
-----------------Protocol.hpp-------------------------------
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <jsoncpp/json/json.h>
#include "Util.hpp"
// #define MYSELF 1
// 给网络版本计算机定制协议
namespace protocol_ns
{
#define SEP " "
#define SEP_LEN strlen(SEP) // 绝对不能写成sizeof
#define HEADER_SEP "\r\n"
#define HEADER_SEP_LEN strlen("\r\n")
// "长度"\r\n"协议号"\r\n""_x _op _y"\r\n
// "10 + 20" => "7"\r\n""10 + 20"\r\n => 报头 + 有效载荷
// 请求/响应 = 报头\r\n有效载荷\r\n
// "10 + 20" => "7"\r\n""10 + 20"\r\n
std::string AddHeader(const std::string &str)
{
std::cout << "AddHeader 之前:\n"
<< str << std::endl;
std::string s = std::to_string(str.size());
s += HEADER_SEP;
s += str;
s += HEADER_SEP;
std::cout << "AddHeader 之后:\n"
<< s << std::endl;
return s;
}
// "7"\r\n""10 + 20"\r\n => "10 + 20"
std::string RemoveHeader(const std::string &str, int len)
{
std::cout << "RemoveHeader 之前:\n"
<< str << std::endl;
std::string res = str.substr(str.size() - HEADER_SEP_LEN - len, len);
std::cout << "RemoveHeader 之后:\n"
<< res << std::endl;
return res;
}
// const &: 输入
// *: 输出
// &: 输入输出
int ReadPackage(int sock, std::string &inbuffer, std::string *package)
{
std::cout << "ReadPackage inbuffer 之前:\n"
<< inbuffer << std::endl;
// 边读取
char buffer[1024];
ssize_t s = recv(sock, buffer, sizeof(buffer - 1), 0);
if (s <= 0)
return -1;
buffer[s] = 0;
inbuffer += buffer;
std::cout << "ReadPackage inbuffer 之中:\n"
<< inbuffer << std::endl;
// 边分析, "7"\r\n""10 + 20"\r\n
auto pos = inbuffer.find(HEADER_SEP);
if (pos == std::string::npos)
return 0; // inbuffer我没看可什么都没有动
std::string lenStr = inbuffer.substr(0, pos); // 获取了头部字符串, inbuffer我没看可什么都没有动
int len = Util::toInt(lenStr); // "123" -> 123 inbuffer我没看可什么都没有动
int targetPackageLen = lenStr.size() + len + 2 * HEADER_SEP_LEN; // inbuffer我没看可什么都没有动
if (inbuffer.size() < targetPackageLen)
return 0; // inbuffer我没看可什么都没有动
*package = inbuffer.substr(0, targetPackageLen); // 提取到了报文有效载荷,inbuffer我没看可什么都没有动
inbuffer.erase(0, targetPackageLen); // 从inbuffer中直接移除整个报文
std::cout << "ReadPackage inbuffer 之后:\n"
<< inbuffer << std::endl;
return len;
}
///
// Request && Response都要提供序列化和反序列化功能
// 1. 自己手写 -- done
// 2. 用别人写的 --- json, xml, protobuf(精品课)
// class CalRequest
class Request
{
public:
Request() {}
Request(int x, int y, char op) : _x(x), _y(y), _op(op)
{
}
// struct->string
// 目前: "_x _op _y"
bool Serialize(std::string *outStr)
{
*outStr = "";
#ifdef MYSELF
std::string x_string = std::to_string(_x);
std::string y_string = std::to_string(_y);
// 手动序列化
*outStr = x_string + SEP + _op + SEP + y_string;
std::cout << "Request Serialize:\n"
<< *outStr << std::endl;
#else
Json::Value root; // Value: 一种万能对象, 接受任意的kv类型
root["x"] = _x;
root["y"] = _y;
root["op"] = _op;
// Json::FastWriter writer; // Writer:是用来进行序列化的 struct -> string
Json::StyledWriter writer;
*outStr = writer.write(root);
#endif
return true;
}
// string->struct
bool Deserialize(const std::string &inStr)
{
#ifdef MYSELF
// inStr : 10 + 20 => [0]=>10, [1]=>+, [2]=>20
// string -> vector
std::vector<std::string> result;
Util::StringSplit(inStr, SEP, &result);
if (result.size() != 3)
return false;
if (result[1].size() != 1)
return false;
_x = Util::toInt(result[0]);
_y = Util::toInt(result[2]);
_op = result[1][0];
#else
Json::Value root;
Json::Reader reader; // Reader: 用来进行反序列化的
reader.parse(inStr, root);
_x = root["x"].asInt();
_y = root["y"].asInt();
_op = root["op"].asInt();
#endif
Print();
return true;
}
void Print()
{
std::cout << "_x: " << _x << std::endl;
std::cout << "_y: " << _y << std::endl;
std::cout << "_z: " << _op << std::endl;
}
~Request() {}
public:
// _x op _y ==> 10 / 0 ? ==> 10 * 9 ?
int _x;
int _y;
char _op;
};
// class CalResponse
class Response
{
public:
Response() {}
Response(int result, int code) : _result(result), _code(code)
{
}
// struct->string
bool Serialize(std::string *outStr)
{
*outStr = "";
#ifdef MYSELF
//_result _code
std::string res_string = std::to_string(_result);
std::string code_string = std::to_string(_code);
*outStr = res_string + SEP + code_string;
std::cout << "Response Serialize:\n"
<< *outStr << std::endl;
#else
Json::Value root;
root["result"] = _result;
root["code"] = _code;
// Json::FastWriter writer;
Json::StyledWriter writer;
*outStr = writer.write(root);
#endif
return true;
}
// string->struct
bool Deserialize(const std::string &inStr)
{
#ifdef MYSELF
// 10 0, 10 1
std::vector<std::string> result;
Util::StringSplit(inStr, SEP, &result);
if (result.size() != 2)
return false;
_result = Util::toInt(result[0]);
_code = Util::toInt(result[1]);
#else
Json::Value root;
Json::Reader reader;
reader.parse(inStr, root);
_result = root["result"].asInt();
_code = root["code"].asInt();
#endif
Print();
return true;
}
void Print()
{
std::cout << "_result: " << _result << std::endl;
std::cout << "_code: " << _code << std::endl;
}
~Response() {}
public:
int _result;
int _code; // 0 success, 1,2,3,4代表不同的错误码
};
}
---------------Sock.hpp---------------------------------
#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "Err.hpp"
static const int gbacklog = 32;
static const int defaultfd = -1;
class Sock
{
public:
Sock() : _sock(defaultfd)
{
}
void Socket()
{
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_sock < 0)
{
logMessage(Fatal, "socket error, code: %d, errstring: %s", errno, strerror(errno));
exit(SOCKET_ERR);
}
}
void Bind(const uint16_t &port)
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(Fatal, "bind error, code: %d, errstring: %s", errno, strerror(errno));
exit(BIND_ERR);
}
}
void Listen()
{
if (listen(_sock, gbacklog) < 0)
{
logMessage(Fatal, "listen error, code: %d, errstring: %s", errno, strerror(errno));
exit(LISTEN_ERR);
}
}
int Accept(std::string *clientip, uint16_t *clientport)
{
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int sock = accept(_sock, (struct sockaddr *)&temp, &len);
if (sock < 0)
{
logMessage(Warning, "accept error, code: %d, errstring: %s", errno, strerror(errno));
}
else
{
*clientip = inet_ntoa(temp.sin_addr);
*clientport = ntohs(temp.sin_port);
}
return sock;
}
int Connect(const std::string &serverip, const uint16_t &serverport)
{
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());
return connect(_sock, (struct sockaddr *)&server, sizeof(server));
}
int Fd()
{
return _sock;
}
void Close()
{
if (_sock != defaultfd)
close(_sock);
}
~Sock()
{
}
private:
int _sock;
};
--------------TcpClient.hpp----------------------------------
#pragma once
#include <iostream>
--------------TcpServer.hpp-------------------------------
#pragma once
#include <iostream>
#include <pthread.h>
#include <functional>
#include "Sock.hpp"
#include "Protocol.hpp"
namespace tcpserver_ns
{
using namespace protocol_ns;
class TcpServer;
using func_t = std::function<Response(const Request &)>;
class ThreadData
{
public:
ThreadData(int sock, std::string ip, uint16_t port, TcpServer *tsvrp)
: _sock(sock), _ip(ip), _port(port), _tsvrp(tsvrp)
{
}
~ThreadData() {}
public:
int _sock;
std::string _ip;
uint16_t _port;
TcpServer *_tsvrp;
};
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port) : _func(func), _port(port)
{
}
void InitServer()
{
// 1. 初始化服务器
_listensock.Socket();
_listensock.Bind(_port);
_listensock.Listen();
logMessage(Info, "init server done, listensock: %d", _listensock.Fd());
}
void Start()
{
for (;;)
{
std::string clientip;
uint16_t clientport;
int sock = _listensock.Accept(&clientip, &clientport);
if (sock < 0)
continue;
logMessage(Debug, "get a new client, client info : [%s:%d]", clientip.c_str(), clientport);
pthread_t tid;
ThreadData *td = new ThreadData(sock, clientip, clientport, this);
pthread_create(&tid, nullptr, ThreadRoutine, td);
}
}
static void *ThreadRoutine(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
td->_tsvrp->ServiceIO(td->_sock, td->_ip, td->_port);
logMessage(Debug, "thread quit, client quit ...");
delete td;
return nullptr;
}
// 我们这个函数是被多线程调用的
void ServiceIO(int sock, const std::string &ip, const uint16_t &port)
{
std::string inbuffer;
while (true)
{
// 0. 你怎么保证你读到了一个完整的字符串报文?"7"\r\n""10 + 20"\r\n
std::string package;
int n = ReadPackage(sock, inbuffer, &package);
if (n == -1)
break;
else if (n == 0)
continue;
else
{
// 一定得到了一个"7"\r\n""10 + 20"\r\n
// 1. 你需要的只是有效载荷"10 + 20"
package = RemoveHeader(package, n);
// decode
// 2. 假设已经读到了一个完整的string
Request req;
req.Deserialize(package); // 对读到的request字符串要进行反序列化
// 3. 直接提取用户的请求数据啦
Response resp = _func(req); // 业务逻辑!
// 4. 给用户返回响应 - 序列化
std::string send_string;
resp.Serialize(&send_string); // 对计算完毕的response结构要进行序列化,形成可发送字符串
// 5. 添加报头
send_string = AddHeader(send_string);
//encode
// 6. 发送
send(sock, send_string.c_str(), send_string.size(), 0);
}
}
close(sock);
}
~TcpServer()
{
_listensock.Close();
}
private:
uint16_t _port;
Sock _listensock;
func_t _func;
};
}
------------Util.hpp---------------------------------
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
using namespace std;
class Util
{
public:
// 输入: const &
// 输出: *
// 输入输出: &
static bool StringSplit(const string &str, const string &sep, vector<std::string> *result)
{
size_t start = 0;
// + 20
// "abcd efg" -> for(int i = 0; i < 10; i++) != for(int i = 0; i <= 9; i++)
while (start < str.size())
{
auto pos = str.find(sep, start);
if (pos == string::npos) break;
result->push_back(str.substr(start, pos-start));
// 位置的重新reload
start = pos + sep.size();
}
if(start < str.size()) result->push_back(str.substr(start));
return true;
}
static int toInt(const std::string &s)
{
// std::stoi();
return atoi(s.c_str());
}
};
安装json库
yum install -y jsoncpp-devel
无论我们采用方案一,还是方案二,只要保证,一端发送时构造的数据,在另一端能够正确的进行解析,就是OK的,这种约定就是应用层协议
HTTP协议
认识URL
平时我们俗称的“网址”其实就是说的URL(uniform resource location)统一资源定位符
urlencode和urldecode
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
“+”被转义成了"%2B"
urldecode就是urlencode的逆过程