部门网站建设多少钱,域名申请证书,discuz 企业网站 模板,怎样让网站做301处理前言 作者#xff1a;小蜗牛向前冲 名言#xff1a;我可以接受失败#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话#xff0c;还请点赞#xff0c;收藏#xff0c;关注#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、基础知识… 前言 作者小蜗牛向前冲 名言我可以接受失败但我不能接受放弃 如果觉的博主的文章还不错的话还请点赞收藏关注支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、基础知识
1、源IP地址和目的IP地址 2、端口号
二、网络套接字
1、网络字节序
2、socket编程接口
三、基于tcp协议的网络通信
1、服务器的编写 2、客户端的编写
3、日志报告的编写 四、TCP协议通讯流程
1、通信流程
2、三次握手和四次挥手 本期学习网络基础知识网络套接字基于tcp协议的网络编程,tcp协议的三次握手和四次挥手。 一、基础知识
1、源IP地址和目的IP地址
源IP地址 源IP地址是指发起网络通信的设备或主机的IP地址。在TCP/IP协议中源IP地址用于标识数据包的来源使得接收方知道从哪里收到数据。源IP地址包含在网络数据包的IP头部中。 目的IP地址 目的IP地址是指网络通信的目标设备或主机的IP地址。在TCP/IP协议中目的IP地址用于指定数据包的目标确保数据包被传递到正确的位置。目的IP地址同样包含在网络数据包的IP头部中。 下面我们用唐僧取经的例子来理解
唐僧到女儿国那国王问高僧从那来到哪里去 在网络中就是问源ip和目的ip。
也可能会问高僧上一站从那来下一站到哪里去。在网络中指的就是MAC地址。
MAC地址 MAC地址Media Access Control address也称为物理地址或硬件地址是网络通讯中用于唯一标识网络接口控制器NIC网络接口卡的一个地址。每个网络设备的NIC都有一个全球唯一的MAC地址这个地址在生产时被固化在硬件中。 MAC地址的长度通常是48位6个字节有时也表示为64位以适应某些特定技术标准。 MAC地址通常以十六进制数表示每个字节之间用冒号:或者破折号-分隔例如00:1A:2B:3C:4D:5E或00-1A-2B-3C-4D-5E。 2、端口号 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上, 但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析。所以就提出用端口号来标识唯一的程序(服务器)。
端口号(port)是传输层协议的内容: 端口号是一个2字节16位的整数;端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;IP地址 端口号能够标识网络上的某一台主机的某一个进程;一个端口号只能被一个进程占用 问题1 我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这 两者之间是怎样的关系? a:系统是系统网络是网络这里可以达到解耦的效果
b: 需要客户端每次都能找到服务端而pid是在进程每次生成时随机分配的
c:不是所以的网络进程都需要网络提供网络服务或者请求但是所以的进程都需要用pid进行标识 所以说一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定 注意 端口号范围从0到65535其中0到1023是被知名服务占用的端口号称为“系统端口”或“保留端口”而1024到65535是动态或私有端口用于一般应用程序或自定义服务(下面我们进行的测试常用8080端口)传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 数据是谁发的, 要 发给谁。 二、网络套接字
1、网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
在网络中无论是发送主机还是接收主机都是将缓冲区中的数据按内存地址从低到高的顺序发送或者接收
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
也就是说不管这台主机是大端机还是小端机到会按照大端机发送。 库函数做网络 字节序和主机字节序的转换。
#includearpa/inet.huint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort); 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回; 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回. 2、socket编程接口
APIApplication Programming Interface是一组定义在软件中不同组件之间交互的规范和工具。API可以看作是一座桥梁它定义了如何访问或使用软件组件的方法。在软件开发中API允许不同的程序部分之间进行通信以便它们能够相互协作而无需详细了解彼此的内部实现。
常见的API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockaddr结构 sockaddr 结构有时候在不同的系统中会有稍微不同的变体如 sockaddr_in是用于表示套接字地址信息的数据结构在网络编程中经常会遇到。它通常用于指定网络通信中的端点地址包括 IP 地址和端口号。
struct sockaddr {unsigned short sa_family; // 地址族如 AF_INETIPv4或 AF_INET6IPv6char sa_data[14]; // 地址数据
};
主要的字段 sa_family用于指定地址族表示地址的类型。例如AF_INET 表示 IPv4 地址族AF_INET6 表示 IPv6 地址族等。这个字段是一个无符号短整型unsigned short。 sa_data包含地址的具体数据通常用于存储 IP 地址和端口号等信息。在不同的地址族下这个字段的内容会有所不同。 在实际使用中为了更方便地表示 IPv4 地址通常会使用更具体的套接字地址结构如 sockaddr_in它的定义如下
struct sockaddr_in {short int sin_family; // 地址族如 AF_INETunsigned short int sin_port; // 端口号struct in_addr sin_addr; // IPv4 地址unsigned char sin_zero[8]; // 未使用的填充字段
};
三、基于tcp协议的网络通信
上面我们说了怎么多下面我们就用起来虽然我们现在还是那么清楚udp和tcp协议但是我大概清楚了他是用来通信的。
这里我们要实现一个简单版本的服务器为客户端提供服务这里我们的服务仅仅需要回显信息。
我们要写一个服务器
tcpServer.hpp
tcpServer.cc
一个客户端
tcpClient.hpp
tcpClinet.cc一个日志报告
log.hpp
1、服务器的编写
tcpServer.hpp:这里我们要完成服务器的初始化启动和销毁。
服务器的初始化是通过socket创建套嵌字bind绑定网络在进行listen监听.
启动要完成accept获取新链接,在执行程序任务。
这里我们在执行任务的过程中我们可以 让程序自己执行创建子进程执行让多线程执行让线程池执行 这里为了让程序更好的理解我们就让子进程执行我们serverio。
#pragma once#include iostream
#include string
#include cstring
#include cstdlib
#include unistd.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include sys/wait.h
#include signal.h
#include pthread.h#include log.hpp
#include Task.hpp
#include ThreadPool.hppnamespace server
{enum{USAGE_ERR 1, // usage_errSOCKET_ERR, // sockft_errBIND_ERR, // bind_errLISTEN_ERR // listen_err};static const uint16_t gport 8080;static const int gbacklog 5;class TcpServer; // 这是一个前置声明我们在类tcpServer中定义了ThreadData数据的类防止出现循环依赖class ThreadData{public:ThreadData(TcpServer *self, int sock) : _self(self), _sock(sock){}public:TcpServer *_self;int _sock;};class TcpServer{public:TcpServer(const uint16_t port gport) : _listensock(-1), _port(port){}void initServer(){// 1 创建套接字_listensock socket(AF_INET, SOCK_STREAM, 0); // sock_streamif (_listensock 0){logMessage(FATAL, create error socket);exit(SOCKET_ERR);}logMessage(NORMAL, create socket success:%d, _listensock);// 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 INADDR_ANY; // inaddr_anyif (bind(_listensock, (struct sockaddr *)local, sizeof(local)) 0){logMessage(FATAL, bind error );exit(BIND_ERR);}logMessage(NORMAL, bind socket success);// 3 设置socket 为监听状态if (listen(_listensock, gbacklog) 0){logMessage(FATAL, listen socket success);exit(LISTEN_ERR);}logMessage(NORMAL, listen socket success);}void start(){for (;;){// 4 server获取链接// sock, 和client进行通信的fdstruct sockaddr_in peer;socklen_t len sizeof(peer);int sock accept(_listensock, (struct sockaddr *)peer, len);if (sock 0){logMessage(ERROR, accept error, next);continue;}logMessage(NORMAL, accept a new link success, get new sock: %d, sock);// 5. 这里就是一个sock未来通信我们就用这个sock面向字节流的后续全部都是文件操作// version1// serviceIO(sock);// close(sock);// version2多进程版本pid_t id fork();// 我们让子进程执行的io任务父进程什么都不做回收子进程就好if (id 0){close(_listensock);serviceIO(sock);close(sock);exit(0);}close(sock);// 父进程pid_t ret waitpid(id, nullptr, 0);if (ret 0){std::cout waitsuccess: ret std::endl;}// version3多线程// pthread_t tid;// ThreadData *td new ThreadData(this, sock);// pthread_create(tid, nullptr, threadRoutine, td);// pthread_join(tid, nullptr);// version4 线程池// ThreadPoolTask::getInstance()-push(Task(sock, serviceIO));}}// static void *threadRoutine(void *args)// {// pthread_detach(pthread_self());// ThreadData *td static_castThreadData *(args);// serviceIO(td-_sock);// close(td-_sock);// delete td;// return nullptr;// }~TcpServer(){}private:int _listensock; // 用了监听uint16_t _port;};
}
下面是一些任务
#pragma once#include iostream
#include string
#include cstdio
#include functionalusing namespace std;void serviceIO(int sock)
{char buffer[1024];while (true){ssize_t n read(sock, buffer, sizeof(buffer) - 1);if (n 0){// 把读到的数据当做字符串buffer[n] 0;cout recv message buffer endl;// 信息返回string outbuffer buffer;outbuffer buffer;outbuffer server[echo];// outbuffer.c_str(),将字符串类型的指针转换为字符类型的指针write(sock, outbuffer.c_str(), sizeof(outbuffer) - 1);}else if (n 0){// client退出logMessage(NORMAL, client quit,me to);break;}}
}class Task
{// using 用于创建类型别名using func_t functionvoid(int);public:Task(){}Task(int sock, func_t func): _sock(sock), _callback(func){}void operator()(){_callback(_sock);}~Task(){}private:int _sock;func_t _callback;
};
tcpServer.cc:主程序的执行
#include tcpserver.hpp
#include daemon.hpp
#include memoryusing namespace server;
using namespace std;static void Usage(string proc)
{cout \nUsage:\n\t proc local_port\n\n;
}// tcp服务器启动上和udp server一模一样
// ./tcpserver local_port
int main(int argc, char *argv[])
{if (argc ! 2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port atoi(argv[1]);unique_ptrTcpServer tsvr(new TcpServer(port));tsvr-initServer();// daemonSelf();tsvr-start();// daemonSelf();return 0;
} 2、客户端的编写
tcpclient.hpp:对于客户端的初始化我们仅仅只是需要创建套接字而bind操作系统会帮助我们完成。
客户端的启动我们要用connect接收链接
#pragma once#include iostream
#include string
#include cstring
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include unistd.h#define NUM 1024
using namespace std;
class tcpclient
{
public:tcpclient(const string serverip, const uint16_t serverport): _sock(-1), _serverip(serverip), _serverport(serverport){}void initclient(){// 1 创建套接字_sock socket(AF_INET, SOCK_STREAM, 0);if (_sock 0){std::cerr socket create error std::endl;exit(2);}}void start(){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 socket connect error std::endl;}else{string msg;while (true){cout Enter#;std::getline(std::cin, msg);write(_sock, msg.c_str(), sizeof(msg));char buffer[NUM];int n read(_sock, buffer, sizeof(buffer));if (n 0){// 目前我们把读到的数据当成字符串, 截止目前buffer[n] 0;std::cout Server回显# buffer std::endl;}elsebreak;}}}~tcpclient(){if (_sock 0)close(_sock);}private:int _sock;std::string _serverip;uint16_t _serverport;
};
tcpcline.cc:执行程序
#include tcpclient.hpp
#include memoryusing namespace std;static void Usage(string proc)
{cout \nUsage:\n\t proc serverip serverport\n\n;
}
// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{if (argc ! 3){Usage(argv[0]);exit(1);}string serverip argv[1];uint16_t serverport atoi(argv[2]);unique_ptrtcpclient tcli(new tcpclient(serverip, serverport));tcli-initclient();tcli-start();return 0;
}
3、日志报告的编写
对于日志报告我们通过对错误进行分类进行不同等级的日志错误信息输入。
#pragma once#include iostream
#include cstdio
#include sys/types.h
#include unistd.h
#include time.h// 定义错误类型
#define DEBUG 0 // debug
#define NORMAL 1 // normal
#define WARNING 2 // warning
#define ERROR 3 // error
#define FATAL 4 // fatal致命#define NUM 1024// 定于不同类型日常写入的文件
#define LOG_NORMAL log.txt
#define LOG_ERR log.error
// 标志位
const char *to_levelstr(int level)
{switch (level){case DEBUG:return DEBUG;case NORMAL:return NORMAL;case WARNING:return NORMAL;case ERROR:return ERROR;case FATAL:return FATAL;}
}// 日志信息
void logMessage(int level, const char *format, ...) // 可变参数函数
{// [日志等级] [时间戳/时间] [pid] [messge]// [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败]char logprefix[NUM]; // 存放日志前缀snprintf(logprefix, sizeof(logprefix), [%d][%ld][pid::%d],to_levelstr(level), (long int)time(nullptr), getpid());char logcontent[NUM];va_list arg;snprintf(logprefix, sizeof(logprefix), format, arg);FILE *log fopen(LOG_NORMAL, a); // log_normalFILE *err fopen(LOG_ERR, a); // log_errorif (log ! nullptr err ! nullptr){FILE *curr nullptr;if (level DEBUG || level NORMAL || level WARNING)curr log;if (level ERROR || level FATAL)curr err;if (curr)fprintf(curr, %s%s\n, logprefix, logcontent);fclose(log);fclose(err);}
}
程序运行测试 四、TCP协议通讯流程
1、通信流程 TCP协议的通讯流程可以分为以下几个步骤 创建连接客户端和服务端都需要创建一个套接字socket这通常涉及到系统调用如socket()。 连接请求客户端向服务端的套接字发送连接请求报文这一步可以通过connect()函数来实现。 服务器响应服务器收到客户端的连接请求后需要进行确认以确定是否允许连接。这通常涉及三部分握手 服务器的确认ACK客户端的确认SYN服务器的应答ACK 连接建立在双方完成了三次握手后连接正式建立。此时客户端可以开始发送数据给服务器而服务器也可以开始接收数据。 数据传输在这个阶段客户端和服务器端可以进行数据的读写操作。这些操作包括读取recv()、写入send()以及关闭连接close()。 断开连接当通信结束后客户端或服务器端可以选择断开当前的连接。这通常涉及到四次挥手 服务器的释放FIN客户端的确认ACK服务器的确认ACK客户端的释放ACK 错误处理在通信过程中可能会遇到各种错误如数据包丢失或乱序。TCP提供了自动重传机制来处理这些问题。 2、三次握手和四次挥手
TCP三次握手Three-way Handshake: 客户端发送连接请求SYN 客户端向服务器发送一个特殊的TCP报文段该报文段中设置了SYN同步标志位表明客户端要求建立连接并指明初始序列号ISN。 服务器确认请求并发送自己的连接请求SYN ACK 服务器接收到客户端的连接请求后向客户端发送一个确认报文段。该报文段中既包含了确认序号ACK也设置了SYN标志位表示服务器同意建立连接并指明自己的初始序列号。 客户端确认服务器的连接请求ACK 客户端接收到服务器的确认报文后会再次向服务器发送一个确认报文段其中确认号字段会加1表示客户端也同意建立连接。 TCP四次挥手Four-way Handshake 发起关闭请求 通信的一方称为主动关闭方发送一个FIN报文段表示它已经完成了数据的发送任务。 接收关闭请求并发送确认 接收到关闭请求的一方被动关闭方收到FIN报文后会发送一个确认报文表明它已经接收到关闭请求。 发送数据并发起关闭请求 被动关闭方发送完所有数据后也会向主动关闭方发送一个FIN报文段表示它已经完成了数据的发送任务并且准备关闭连接。 确认关闭请求并发送关闭确认 主动关闭方接收到被动关闭方的FIN报文后会发送一个确认报文表明它已经接收到了关闭请求。一旦被动关闭方收到了这个确认连接就会关闭。 在linux下有许多命令老是忘记在下面的文章中多会为大家分享一些小命令
Linux小命令 :
netstat 是一个用于显示网络状态信息的命令行工具常用于查看网络连接、路由表、接口统计等信息。在给定的命令中-nltp 参数的含义如下 -n: 显示数字形式的地址和端口号而不进行域名和服务名称的解析。-l: 仅显示监听状态的连接。-t: 仅显示TCP协议的连接。-p: 显示与连接相关的进程标识符PID及进程名称。