sherecho的个人博客

弱小和无知不是生存的障碍,傲慢才是

计算机网络复习网络传输-IP-传输层-DNS

TCP/IP和OSI网络模型

为什么需要分层网络模型?

网络通信中需要很多很多协议、功能、组件的协作。如果采用模块化的网络体系结构,适应性差,难以维护,对系统实现是场灾难。使用分层模型,如果之分为两层的化,每一种新的应用都需要物理层的每个种类适配,扩展性较差。因此需要引入中间层适配物理层和应用层。

分层的优点:各层相互独立,将建造一个网络的问题分解为多个可处理的部分,一层解决一部分问题。灵活性好:任何层发生变化时,只要接口不变,上下层不受影响。结构上可分割开:各层都可以采用最合适的技术实现易于实现和维护能促进标准化工作。

OSI七层模型每层都有什么,对应什么功能

osi模型
功能

TCP/IP模型与OSI的对应关系

TCP/IP
阅读全文 »

计算机网络知识总结:网安,QOS,P2P,CDN,网络测量,区块链

网络安全

什么是网络安全

网络安全的一个通用定义指网络信息系统的==硬件、软件及其系统中的数据受到保护==,不因偶然的或者恶意的===破坏、更改、泄露==, 系统能连续、可靠、正常地运行,服务不中断

网络安全的五大要素

网络安全机制位于协议的那一层

没有一个单独的位置,因为安全性与每一层都有关

  • 物理层:传输线封装在包含高压氩气的密封管,漏气报警
  • 数据链路层:点到点线路加解密,不能经过中间路由器
  • 网络层:防火墙、IP报文头的安全域
  • 传输层:端到端连接的加解密
  • 应用层:大多数安全机制都集中在此层

加密技术

阅读全文 »

wireshark抓包分析https

wireshark 配置

配置网站的私钥

我想抓取分析自己服务器搭建出来的模拟https这需要使用网站生成证书使用的私钥进行解密,把它添加到 wireshark 配置中如下所示:

配置首选项

通过curl访问的 https 协议的 URL,在配置了该服务器对应的私钥后可以抓取到对应的 HTTP 明文。

工作中的wireshark将抓取到相关数据包,在过滤栏设置过滤条件以避免其他无用数据包影响分析,比如:ip.addr == xx &&ssl表示只显示ICPM协议且源主机IP或者目的主机IP为185.199.111.153的数据包。说明:协议名称ssl要小写

抓包

部分小问题:

wireshark提示Ignored Unknow Record

阅读全文 »

并发请求处理

多线程实现的必要性

当一个客户端与服务器建立连接以后,服务器端 accept()返回,进而准备循环接收客户端发过来的数据。如果客户端暂时没发数据,服务端会在 recv()处阻塞。此时,其他客户端向服务器发起连接后,由于服务器阻塞了,无法执行 accept()接受连接,也就是其他客户端发送的数据,服务器无法读取。服务器也就无法并发同时处理多个客户端。 服务器如何实现同时处理多个客户端的同时通信:有如下几种方案:多线程,多进程,select,poll,epoll,以及使用epoll+reactor

多线程

采用pthread多线程来分别处理单个连接,实现多个客户端连接的同时处理

1
2
3
4
5
6
7
8
9
10
11
while (1) {
struct sockaddr_in client;
socklen_t len = sizeof(client);
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
pthread_t threadid;
pthread_create(&threadid, NULL, client_routine, (void*)&connfd);//创建线程,并执行client_routine回调函数功能,将connfd作为回调函数的参数传入,client_routine函数将处理相应文件描述符connfd的发送和接收功能

}

accept以后创建线程处理响应。服务端接受一个客户端的连接后,创建一个线程(或者进程),然后在新创建的线程或进程中循环处理数据。主线程(父进程)只负责监听客户端的连接,并使用 accept()接受连接,不进行数据的处理。

多线程

实验代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <pthread.h>

#define MAXLNE 4096

#define POLL_SIZE 1024

void *client_routine(void *arg) { //client_routine函数是多线程的回调函数,用来处理每一个连接的请求,也就是接收与发送信息功能

int connfd = *(int *)arg;

char buff[MAXLNE];

while (1) {

int n = recv(connfd, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);

send(connfd, buff, n, 0);
} else if (n == 0) {
close(connfd);
break;
}

}

return NULL;
}


int main(int argc, char **argv)
{
int listenfd, connfd, n;
struct sockaddr_in servaddr;
char buff[MAXLNE];

if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {//socket创建listen的文件描述符
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}

memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(10000);//端口信息可以自己设置
//bind函数绑定IP,端口信息
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}

if (listen(listenfd, 10) == -1) {//listen监听函数,参数10表示未完成连接与已完成连接队列之和,一般设置为5的倍数
printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
while (1) {
struct sockaddr_in client;
socklen_t len = sizeof(client);
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
pthread_t threadid;
pthread_create(&threadid, NULL, client_routine, (void*)&connfd);//创建线程,并执行client_routine回调函数功能,将connfd作为回调函数的参数传入,client_routine函数将处理相应文件描述符connfd的发送和接收功能
}
return 0;
}
阅读全文 »

高性能服务器程序框架

C/S模型

工作流程:

服务器启动后首先创建一个(或者多个)监听socket,并调用bind函数将其绑定在服务器感兴趣的端口上,然后调用listen函数等待客户连接。服务器稳定后客户端可以调用connect函数向服务器发起连接。由于客户连接请求是随机到达的异步事件,服务器需要使用某种I/O模型监听这一事件。下图是其中一种使用I/O复用技术的select系统调用

TCP服务器和TCP客户端工作流程

p2p模型

2种服务器模型

服务器基本框架

服务器基本框架

I/O处理单元是服务器管理客户连接的模块,用于等待并接受新的客户连接,接受客户数据,将服务器响应数据返回给客户端。对于服务器集群而言,i/O处理单元是一个专门的接入服务器,可以用于实现负载均衡

阅读全文 »

Linux服务器程序规范

Linux系统日志

内核日志由一个守护进程rklogd管理,内核日志通过printk打印到内核缓存,映射到/proc/kmsg

syslogd(rsyslogd),另一个守护进程处理系统日志,获取/proc/kmsg文件读取内核日志

日志体系

用户信息

UID和EUID

UID(User Identifier):

  • UID是一个用于唯一标识用户的整数值。每个用户在系统中都有一个唯一的UID。
  • UID为0的用户是超级用户(root),具有系统中最高的权限。
  • 普通用户的UID通常从非零的正整数开始分配。

EUID(Effective User Identifier):

阅读全文 »

高级IO函数

基本的GCI服务器

CGI(Common Gateway Interface)服务器是一种通过 Web 服务器执行外部程序的标准接口。它允许 Web 服务器调用外部程序来处理客户端的请求,并将程序的输出发送回客户端浏览器。CGI 是连接 Web 服务器和其他软件(通常是动态生成的程序或脚本)的桥梁。

基本上,当 Web 服务器接收到一个对动态内容的请求时,它可以调用与之相关联的 CGI 脚本或程序,将用户的请求传递给该程序进行处理。该程序生成动态内容,然后将其输出返回给 Web 服务器,最终发送到用户的浏览器。

一些关键点和特性:

  1. 动态内容生成: CGI 允许服务器调用外部程序来生成动态内容,而不是直接返回静态文件。
  2. 编程语言无关性: CGI 接口是独立于编程语言的,因此可以使用多种编程语言编写 CGI 脚本,包括但不限于 Perl、Python、C、C++等。
  3. 处理表单数据: CGI 可以用于处理来自 HTML 表单的数据。用户提交表单后,服务器可以调用 CGI 脚本来处理表单数据并生成相应的响应。
  4. 与 Web 服务器的通信: CGI 脚本与 Web 服务器通过环境变量和标准输入输出进行通信。
  5. 安全性考虑: 由于 CGI 允许执行外部程序,必须小心处理用户输入,以防止潜在的安全漏洞。

虽然 CGI 曾经是 Web 开发的主要方式,但随着时间的推移,其他技术和方法,如 FastCGI、mod_perl、PHP、ASP.NET 等,已经逐渐取代了纯粹的 CGI 模型,提供了更高效和更强大的动态内容生成机制。

将服务器标准输出重定向到客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );

struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );

int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );

int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );

ret = listen( sock, 5 );
assert( ret != -1 );

struct sockaddr_in client;
socklen_t client_addrlength = sizeof( client );
int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
}
else
{
close( STDOUT_FILENO );
dup( connfd );
printf( "abcd\n" );
close( connfd );
}

close( sock );
return 0;
}

关闭标准输出(STDOUT_FILENO):

阅读全文 »

进程运行轨迹的跟踪与统计

实验内容

进程从创建(Linux 下调用 fork())到结束的整个过程就是进程的生命期,进程在其生命期中的运行轨迹实际上就表现为进程状态的多次切换,如进程创建以后会成为就绪态;当该进程被调度以后会切换到运行态;在运行的过程中如果启动了一个文件读写操作,操作系统会将该进程切换到阻塞态(等待态)从而让出 CPU;当文件读写完毕以后,操作系统会在将其切换成就绪态,等待进程调度算法来调度该进程执行……

本次实验包括如下内容:

  • 基于模板 process.c 编写多进程的样本程序,实现如下功能: + 所有子进程都并行运行,每个子进程的实际运行时间一般不超过 30 秒; + 父进程向标准输出打印所有q子进程的 id,并在所有子进程都退出后才退出;
  • Linux0.11 上实现进程运行轨迹的跟踪。 + 基本任务是在内核中维护一个日志文件 /var/process.log,把从操作系统启动到系统关机过程中所有进程的运行轨迹都记录在这一 log 文件中。
  • 在修改过的 0.11 上运行样本程序,通过分析 log 文件,统计该程序建立的所有进程的等待时间、完成时间(周转时间)和运行时间,然后计算平均等待时间,平均完成时间和吞吐量。可以自己编写统计程序,也可以使用 python 脚本程序—— stat_log.py(在 /home/teacher/ 目录下) ——进行统计。
  • 修改 0.11 进程调度的时间片,然后再运行同样的样本程序,统计同样的时间数据,和原有的情况对比,体会不同时间片带来的差异。

编写process.c 样本程序

process.c是样本程序的模板。它主要实现了一个函数:

1
2
3
4
5
6
7
8
9
/*
* 此函数按照参数占用CPU和I/O时间
* last: 函数实际占用CPU和I/O的总时间,不含在就绪队列中的时间,>=0是必须的
* cpu_time: 一次连续占用CPU的时间,>=0是必须的
* io_time: 一次I/O消耗的时间,>=0是必须的
* 如果last > cpu_time + io_time,则往复多次占用CPU和I/O,直到总运行时间超过last为止
* 所有时间的单位为秒
*/
cpuio_bound(int last, int cpu_time, int io_time);

比如一个进程如果要占用10秒的CPU时间,它可以调用:

1
cpuio_bound(10, 1, 0);  // 只要cpu_time>0,io_time=0,效果相同
阅读全文 »

lambda表达式解析

什么是lambda表达式

lambda表达式是一个可调用的代码单元,可以将其理解为一个未命名的内联函数。

lambda表达式形式:

[捕获列表](参数列表)-> return type{函数体}

其中参数列表和return type可以省略:

auto f=[]{return 42;};

我们经常在排序中使用:

1
2
3
4
5
vector<string> words={"aa","bbbb","ccccc"};
stable_sort(words.begin(),words.end(),[](const string &a,const string &b){return a.size()<b.size();});

return 0;
}

初步分析

阅读全文 »
0%