淄博云天网站建设推广seo搜索引擎优化业务
网络协议初识
- 所谓的协议就是人们为了通信的一种约定
- 操作系统要进行协议管理,必然会先描述,再组织
- 协议本质就是软件,软件是可以"分层"
- 协议在设计的时候,就是被层状的划分的,
为什么要划分成为层状结构
- 场景复杂
- 功能解耦(便于人们进行各种维护)
OSI七层模型
局域网中两台主机是可以直接通信的
- 每层都有自己的协议定制方案,
- 每层协议都要有自己的协议报头
- 从上到下交付数据的时候,要添加报头
- 从下到上递交数据的时候,要去掉报头
如何理解报头
- 报头就像快递的快递单号一样,虽然我们不看,但是必须有
- 封装的本质: 添加报头
- 解包: 去掉报头&&展开分析
- 在使用TCP/IP协议的网络中,IP及其向上的协议,看到的报文都是一样的
- 报文是要被封装的,如何解包?
- 决定我们的有效载荷交付给上层的哪一个协议的问题?
- 每一个协议都要考虑,都要有一定的方式解决这两个公共问题
理解源IP地址和目的IP地址
- 把数据哦送到对方的机器不是目的,真正的网络通信: 其实是进程间通信
- IP地址(公网IP),标定了主机的唯一性
- 源IP是固定不变的,目的IP是可以变的
认识端口号
- 端口号是标识特定主机上的网络进程的唯一性
- 端口号是一个2字节16位的整数
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用
socket编程接口
常见的套接字:
- 域间socket
- 原始socket
- 网络socket
理论上,是三种应用场景,对应的应该是三套接口,不过将所有的接口进行统一
使用网络在两台主机之间进行通信(之间互发信息)
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 4const char *gLevelMap[] = {"DEBUG","NORMAL","WARNING","ERROR","FATAL"
};// #define LOGFILE "./threadpool.log"// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOWif(level== DEBUG) return;
#endifchar 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_serverudp_client:udp_client.ccg++ -o $@ $^ -std=c++11
udp_server:udp_server.ccg++ -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));// 清0server.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和PORTsendto(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 1024class 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));// 清0local.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));// donereturn true;}void Start(){// 服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了!// echo server: client给我们发送消息,我们原封不动返回char buffer[SIZE];for (;;){// 注意:peer是纯输出型参数struct sockaddr_in peer;bzero(&peer, sizeof(peer));// 输入: peer缓冲区大小// 输出: 实际读到的peersocklen_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
使用网络在两台主机之间进行通信(交互式的命令)
void Start(){// 服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了!// echo server: client给我们发送消息,我们原封不动返回char buffer[SIZE];for (;;){// 注意:peer是纯输出型参数struct sockaddr_in peer;bzero(&peer, sizeof(peer));// 输入: peer缓冲区大小// 输出: 实际读到的peersocklen_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);}
使用网络在两台主机之间进行通信(多进程)
makeflie
.PHONY:all
all:udp_client udp_serverudp_client:udp_client.ccg++ -o $@ $^ -std=c++11 -lpthread
udp_server:udp_server.ccg++ -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 4const char *gLevelMap[] = {"DEBUG","NORMAL","WARNING","ERROR","FATAL"
};// #define LOGFILE "./threadpool.log"// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOWif(level== DEBUG) return;
#endifchar 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 1024class 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));// 清0local.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-8080logMessage(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是全双工的,可以同时进行收发而不受干扰
使用网络在两台主机之间进行通信(多进程-父子进程):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 serverchar 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实现的