C++面试集锦( 面试被问到的问题 ):总结

20 篇文章 4 订阅
9 篇文章 0 订阅

目录

一、目的

1、找到了这个博客,然后自己将其总结一下

二、参考

1、C++面试集锦( 面试被问到的问题 )

三、操作:参考:C++面试集锦( 面试被问到的问题 )

1、C 和 C++ 区别(20200613)

2、 const 有什么用途(20200613)

3、指针和引用的区别(20200613)

4、 C++中有了malloc / free , 为什么还需要 new / delete (20200613)

5、编写类String 的构造函数,析构函数,拷贝构造函数和赋值函数

6、多态的实现

7、单链表的逆置

8、 堆和栈的区别   (20200613)

9、不调用C/C++ 的字符串库函数,编写strcpy  (20200613)

10、关键字static的作用 (20200614)

11、在c++程序中调用被C编译器编译后的函数,为什么要加extern“C”(20200614)

12、头文件种的ifndef/define/endif 是干什么用的(20200614)

13、线程和进程的联系和区别(20200614)

14、 线程有哪几种状态(20200614)

15、进程间的通信方式(20200614)

16、线程同步和线程互斥的区别(20200614)

17、线程同步的方式(20200614)

18、网络七层(20200614)

19、TCP和UDP有什么区别(20200614)

20、编写socket套接字的步骤(20200614)

21、TCP三次握手和四次挥手, 以及各个状态的作用(20200614)

22、HTTP协议(20200614)

23、使用过的 shell 命令(20200614)

24、使用过的 vim 命令(20200616)

25、使用过的 gdb 命令(20200616)

26、常见算法(20200616)

27、C库函数实现

28、静态链表和动态链表的区别(20200620)

29、大并发( epoll )(20200620)

30、海量数据处理的知识点,(hash表, hash统计)(20200620)

31、什么时候要用虚析构函数(20200621)

32、 c++怎样让返回对象的函数不调用拷贝构造函数(20200621)

33、孤儿进程和僵尸进程(20200621)



一、目的

1、找到了这个博客,然后自己将其总结一下

 

二、参考

1、C++面试集锦( 面试被问到的问题 )

https://www.cnblogs.com/Y1Focus/p/6707121.html

 

三、操作:参考:C++面试集锦( 面试被问到的问题 )

1、C 和 C++ 区别(20200613)

 

2、 const 有什么用途(20200613)

主要有三点:

      1:定义只读变量,即常量 

      2:修饰函数的参数和函数的返回值 

      3: 修饰函数的定义体,这里的函数为类的成员函数,被const修饰的成员函数代表不修改成员变量的值

 

3、指针和引用的区别(20200613)

    1:引用是变量的一个别名,内部实现是只读指针

    2:引用只能在初始化时被赋值,其他时候值不能被改变,指针的值可以在任何时候被改变

    3:引用不能为NULL,指针可以为NULL

    4:引用变量内存单元保存的是被引用变量的地址

    5:“sizeof 引用" = 指向变量的大小 , "sizeof 指针"= 指针本身的大小

    6:引用可以取地址操作,返回的是被引用变量本身所在的内存单元地址

    7:引用使用在源代码级相当于普通的变量一样使用,做函数参数时,内部传递的实际是变量地址

 

4、 C++中有了malloc / free , 为什么还需要 new / delete (20200613)

1,malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
2,对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。
     对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
     由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
3,因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

5、编写类String 的构造函数,析构函数,拷贝构造函数和赋值函数

 

6、多态的实现

 

7、单链表的逆置

 

8、 堆和栈的区别   (20200613)

 一个由c/C++编译的程序占用的内存分为以下几个部分 
  1、栈区(stack)―   由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 
  2、堆区(heap) ―   一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
     注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。 
  3、全局区(静态区)(static)―,全局变量和静态变量的存储是放在一块的,
     初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 
  4、文字常量区  ―常量字符串就是放在这里的。 程序结束后由系统释放 
  5、程序代码区―存放函数体的二进制代码。

9、不调用C/C++ 的字符串库函数,编写strcpy  (20200613)

char * strcpy(char * strDest,const char * strSrc)
        {
                if ((strDest==NULL)||strSrc==NULL))                     
                   return NULL;    
                char * strDestCopy=strDest; 
                while ((*strDest++=*strSrc++)!='\0'); 
                *strDest = '\0';
                return strDestCopy;
        }

①操作:运行结果:

  • 总结:char 创建时候一定要设置好长度
  • 总结:这个我自己抄了几次了,但是还是不怎么记住
  • 总结:一定要判断空
  • 总结:while循环进行赋值

#include <stdlib.h>
#include <iostream>

char *strcpy(char *strDest,const char *strSrc)
{
	if ((strDest==NULL)||strSrc==NULL)
	{
		return NULL;
	}
	char *strDestCopy=strDest;
	while((*strDest++=*strSrc++)!='\0');
	*strDest='\0';
	return strDestCopy;
}

int main()
{
	char *strSrc="asdasdqwe";
	char strDest[256];
	strcpy(strDest,strSrc);
	std::cout<<"strDest="<<strDest<<std::endl;

	system("pause");
	return EXIT_SUCCESS;
}

 

10、关键字static的作用 (20200614)

  •  1.  函数体内 static 变量的作用范围为该函数体,不同于 auto 变量, 该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值
  •     2.  在模块内的 static 全局变量可以被模块内所有函数访问,但不能被模块外其他函数访问
  •     3.  在模块内的static 函数只可被这一模块内的其他函数调用,这个函数的使用范围被限制在声明它的模块内
  •     4.  在类的static 成员变量属于整个类所拥有,对类的所以对象只有一份拷贝
  •     5.  在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的 static 成员变量

   

     介绍它最重要的一条:隐藏。(static函数,static变量均可) --> 对应上面的2、3项
        当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
        举例来说明。同时编译两个源文件,一个是a.c,另一个是main.c。

 //a.c
    char a = 'A';               // global variable
    void msg()
    {
      printf("Hello\n");
    }
//main.c
   int main()
   {
     extern char a;       // extern variable must be declared before use
     printf("%c ", a);
     (void)msg();
     return 0;
   }

 程序的运行结果是:

A Hello

  为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?

      前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,

        因此对于另外的源文件main.c是可见的。

     如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。

     利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏

①操作:运行结果:成功

  • 总结:

①操作:运行结果:失败

①操作:运行结果:失败

11、在c++程序中调用被C编译器编译后的函数,为什么要加extern“C”(20200614)

  C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同,

      假设某个函数原型为:

  1.           void foo(int x, inty);

    该函数被C编译器编译后在库中的名字为:  _foo

    而C++编译器则会产生像: _foo_int_int   之类的名字。
    为了解决此类名字匹配的问题,C++提供了C链接交换指定符号 extern "C"。

12、头文件种的ifndef/define/endif 是干什么用的(20200614)

 防止头文件被重复包含

 

13、线程和进程的联系和区别(20200614)

 ①参考:https://blog.csdn.net/wolenski/article/details/7969908

原文:

第一题:线程的基本概念、线程的基本状态及状态之间的关系?

线程,有时称为轻量级进程,是CPU使用的基本单元;它由线程ID、程序计数器、寄存器集合和堆栈组成。它与属于同一进程的其他线程共享其代码段、数据段和其他操作系统资源(如打开文件和信号)。

线程有四种状态:新生状态、可运行状态、被阻塞状态、死亡状态。状态之间的转换如下图所示:

 

 

第二题:线程与进程的区别?

1、 线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。
2、 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个进程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
3、 系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,出了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。
4、 与进程的控制表PCB相似,线程也有自己的控制表TCB,但是TCB中所保存的线程状态比PCB表中少多了。
5、 进程是系统所有资源分配时候的一个基本单位,拥有一个完整的虚拟空间地址,并不依赖线程而独立存在。

 

第三题:多线程有几种实现方法,都是什么?

 1. 继承 Thread 类
  2. 实现 Runnable 接口再 new Thread(YourRunnableOjbect) 

第四题:多线程同步和互斥有几种实现方法,都是什么?



线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。

 

第五题:多线程同步和互斥有何异同,在什么情况下分别使用他们?举例说明。

线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步(下文统称为同步)。

 

14、 线程有哪几种状态(20200614)

①参考:同上一题目

 

15、进程间的通信方式(20200614)

 管道、有名管道、信号、共享内存、消息队列、信号量、套接字、文件.

 

16、线程同步和线程互斥的区别(20200614)

①参考:同题目13

 

17、线程同步的方式(20200614)

     Linux:   互斥锁、条件变量和信号量

 ①参考: https://blog.csdn.net/zsf8701/article/details/7844316

原文:

线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。

一、互斥锁(mutex)

通过锁机制实现线程间的同步。

  1. 初始化锁。在Linux下,线程的互斥量数据类型是pthread_mutex_t。在使用前,要对它进行初始化。
    静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    动态分配:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);
  2. 加锁。对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。
    int pthread_mutex_lock(pthread_mutex *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
  3. 解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
  4. 销毁锁。锁在是使用完成后,需要进行销毁以释放资源。
    int pthread_mutex_destroy(pthread_mutex *mutex);
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
#include "iostream"
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int tmp;
void* thread(void *arg)
{
	cout << "thread id is " << pthread_self() << endl;
	pthread_mutex_lock(&mutex);
	tmp = 12;
	cout << "Now a is " << tmp << endl;
	pthread_mutex_unlock(&mutex);
	return NULL;
}
int main()
{
	pthread_t id;
	cout << "main thread id is " << pthread_self() << endl;
	tmp = 3;
	cout << "In main func tmp = " << tmp << endl;
	if (!pthread_create(&id, NULL, thread, NULL))
	{
		cout << "Create thread success!" << endl;
	}
	else
	{
		cout << "Create thread failed!" << endl;
	}
	pthread_join(id, NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}
//编译:g++ -o thread testthread.cpp -lpthread

二、条件变量(cond)

互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分: 条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。

  1. 初始化条件变量。
    静态态初始化,pthread_cond_t cond = PTHREAD_COND_INITIALIER;
    动态初始化,int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
  2. 等待条件成立。释放锁,同时阻塞等待条件变量为真才行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
  3. 激活条件变量。pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
    int pthread_cond_signal(pthread_cond_t *cond);
    int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞
  4. 清除条件变量。无线程等待,否则返回EBUSY
    int pthread_cond_destroy(pthread_cond_t *cond);
#include <stdio.h>
#include <pthread.h>
#include "stdlib.h"
#include "unistd.h"
pthread_mutex_t mutex;
pthread_cond_t cond;
void hander(void *arg)
{
	free(arg);
	(void)pthread_mutex_unlock(&mutex);
}
void *thread1(void *arg)
{
	pthread_cleanup_push(hander, &mutex);
	while(1)
	{
		printf("thread1 is running\n");
		pthread_mutex_lock(&mutex);
		pthread_cond_wait(&cond, &mutex);
		printf("thread1 applied the condition\n");
		pthread_mutex_unlock(&mutex);
		sleep(4);
	}
	pthread_cleanup_pop(0);
}
void *thread2(void *arg)
{
	while(1)
	{
		printf("thread2 is running\n");
		pthread_mutex_lock(&mutex);
		pthread_cond_wait(&cond, &mutex);
		printf("thread2 applied the condition\n");
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
}
int main()
{
	pthread_t thid1,thid2;
	printf("condition variable study!\n");
	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init(&cond, NULL);
	pthread_create(&thid1, NULL, thread1, NULL);
	pthread_create(&thid2, NULL, thread2, NULL);
	sleep(1);
	do
	{
		pthread_cond_signal(&cond);
	}while(1);
	sleep(20);
	pthread_exit(0);
	return 0;
}
#include <pthread.h>
#include <unistd.h>
#include "stdio.h"
#include "stdlib.h"
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
struct node
{
	int n_number;
	struct node *n_next;
}*head = NULL;
 
static void cleanup_handler(void *arg)
{
	printf("Cleanup handler of second thread./n");
	free(arg);
	(void)pthread_mutex_unlock(&mtx);
}
static void *thread_func(void *arg)
{
	struct node *p = NULL;
	pthread_cleanup_push(cleanup_handler, p);
	while (1)
	{
		//这个mutex主要是用来保证pthread_cond_wait的并发性
		pthread_mutex_lock(&mtx);
		while (head == NULL)
		{
			//这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何
			//这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线
			//程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。
			//这个时候,应该让线程继续进入pthread_cond_wait
			// pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,
			//然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立
			//而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
			//用这个流程是比较清楚的
			pthread_cond_wait(&cond, &mtx);
			p = head;
			head = head->n_next;
			printf("Got %d from front of queue/n", p->n_number);
			free(p);
		}
		pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁
	}
	pthread_cleanup_pop(0);
	return 0;
}
int main(void)
{
	pthread_t tid;
	int i;
	struct node *p;
	//子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而
	//不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大
	pthread_create(&tid, NULL, thread_func, NULL);
	sleep(1);
	for (i = 0; i < 10; i++)
	{
		p = (struct node*)malloc(sizeof(struct node));
		p->n_number = i;
		pthread_mutex_lock(&mtx); //需要操作head这个临界资源,先加锁,
		p->n_next = head;
		head = p;
		pthread_cond_signal(&cond);
		pthread_mutex_unlock(&mtx); //解锁
		sleep(1);
	}
	printf("thread 1 wanna end the line.So cancel thread 2./n");
	//关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出
	//线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。
	pthread_cancel(tid);
	pthread_join(tid, NULL);
	printf("All done -- exiting/n");
	return 0;
}

三、信号量(sem)

如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。

  1. 信号量初始化。
    int sem_init (sem_t *sem , int pshared, unsigned int value);
    这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。
  2. 等待信号量。给信号量减1,然后等待直到信号量的值大于0。
    int sem_wait(sem_t *sem);
  3. 释放信号量。信号量值加1。并通知其他等待线程。
    int sem_post(sem_t *sem);
  4. 销毁信号量。我们用完信号量后都它进行清理。归还占有的一切资源。
    int sem_destroy(sem_t *sem);
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>
#define return_if_fail(p) if((p) == 0){printf ("[%s]:func error!/n", __func__);return;}
typedef struct _PrivInfo
{
	sem_t s1;
	sem_t s2;
	time_t end_time;
}PrivInfo;
 
static void info_init (PrivInfo* thiz);
static void info_destroy (PrivInfo* thiz);
static void* pthread_func_1 (PrivInfo* thiz);
static void* pthread_func_2 (PrivInfo* thiz);
 
int main (int argc, char** argv)
{
	pthread_t pt_1 = 0;
	pthread_t pt_2 = 0;
	int ret = 0;
	PrivInfo* thiz = NULL;
	thiz = (PrivInfo* )malloc (sizeof (PrivInfo));
	if (thiz == NULL)
	{
		printf ("[%s]: Failed to malloc priv./n");
		return -1;
	}
	info_init (thiz);
	ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, thiz);
	if (ret != 0)
	{
		perror ("pthread_1_create:");
	}
	ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, thiz);
	if (ret != 0)
	{
		perror ("pthread_2_create:");
	}
	pthread_join (pt_1, NULL);
	pthread_join (pt_2, NULL);
	info_destroy (thiz);
	return 0;
}
static void info_init (PrivInfo* thiz)
{
	return_if_fail (thiz != NULL);
	thiz->end_time = time(NULL) + 10;
	sem_init (&thiz->s1, 0, 1);
	sem_init (&thiz->s2, 0, 0);
	return;
}
static void info_destroy (PrivInfo* thiz)
{
	return_if_fail (thiz != NULL);
	sem_destroy (&thiz->s1);
	sem_destroy (&thiz->s2);
	free (thiz);
	thiz = NULL;
	return;
}
static void* pthread_func_1 (PrivInfo* thiz)
{
	return_if_fail(thiz != NULL);
	while (time(NULL) < thiz->end_time)
	{
		sem_wait (&thiz->s2);
		printf ("pthread1: pthread1 get the lock./n");
		sem_post (&thiz->s1);
		printf ("pthread1: pthread1 unlock/n");
		sleep (1);
	}
	return;
}
static void* pthread_func_2 (PrivInfo* thiz)
{
	return_if_fail (thiz != NULL);
	while (time (NULL) < thiz->end_time)
	{
		sem_wait (&thiz->s1);
		printf ("pthread2: pthread2 get the unlock./n");
		sem_post (&thiz->s2);
		printf ("pthread2: pthread2 unlock./n");
		sleep (1);
	}
	return;
}

18、网络七层(20200614)

 

19、TCP和UDP有什么区别(20200614)

TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。

                 当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。

                 TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。

UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。

                 UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。

                 由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快

 

 

20、编写socket套接字的步骤(20200614)

 

21、TCP三次握手和四次挥手, 以及各个状态的作用(20200614)

①参考(我自己总结的):TCP/IP 协议 常见面试题1

https://blog.csdn.net/qq_40544338/article/details/106109071

22、HTTP协议(20200614)

   http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式,

     HTTP1.1版本中给出一种持续连接的机制,绝大多数的Web开发,都是构建在HTTP协议之上的Web应用。

    TCP 和 HTTP区别:https://blog.csdn.net/lemonxuexue/article/details/4485877

①原文:

相信不少初学手机联网开发的朋友都想知道Http与Socket连接究竟有什么区别,希望通过自己的浅显理解能对初学者有所帮助。

 

1、TCP连接

 手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上。

 

建立起一个TCP连接需要经过“三次握手”:

 

第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

 

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

 

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

 

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)

 


2、HTTP连接

 

HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。

 

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

 

1)在HTTP 1.0中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。
 
2)在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。
 
由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即时不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。

 


3、SOCKET原理

 

3.1套接字(socket)概念

套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

 

应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。


3.2 建立socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。

 

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

 

服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

 

客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

 

连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

 


4、SOCKET连接与TCP连接

创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。

 


5、Socket连接与HTTP连接

由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

 

而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

 

很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

 

23、使用过的 shell 命令(20200614)

cp , mv , rm , mkdir , touch , pwd , cd  , ls , top , cat , tail , less , df , du , man , find , kill , sudo , cat 

①总结:

 

24、使用过的 vim 命令(20200616)

 wq!, dd , dw , yy , p , i , %s/old/new/g , /abc 向后搜索字符串abc , ?abc向前搜索字符串abc

①总结:

 

25、使用过的 gdb 命令(20200616)

 

①总结:

 

①参考:gdb基本命令(非常详细)

https://blog.csdn.net/q1449516487/article/details/95331292

 

26、常见算法(20200616)

 

快速排序、堆排序和归并排序

堆排序 :堆排序原理及算法实现(最大堆):自己总结:https://blog.csdn.net/qq_40544338/article/details/106797123

快速排序、归并排序: https://blog.csdn.net/morewindows/article/details/6684558

稳定性分析 :https://baike.baidu.com/item/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E7%A8%B3%E5%AE%9A%E6%80%A7

27、C库函数实现

 

28、静态链表和动态链表的区别(20200620)

http://blog.csdn.net/toonny1985/article/details/4868786

29、大并发( epoll )(20200620)

优点:

      https://blog.csdn.net/sunyurun/article/details/8194979

实例:

        https://www.cnblogs.com/ggjucheng/archive/2012/01/17/2324974.html

 

30、海量数据处理的知识点,(hash表, hash统计)(20200620)

  hash表: http://hi.baidu.com/05104106/item/62736054402852c09e26679b

    海量数据处理方法: https://blog.csdn.net/qq_40544338/article/details/106891575

 

31、什么时候要用虚析构函数(20200621)

通过基类的指针来删除派生类的对象时,基类的析构函数应该是虚的。否则其删除效果将无法实现。

       一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,从而千万内存泄漏。

      原因:

              在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员。

              如果想要用基类对非继承成员进行操作,则要把基类的这个操作(函数)定义为虚函数。
              那么,析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的。

      注意:

      如果不需要基类对派生类及对象进行操作,则不能定义虚函数(包括虚析构函数),因为这样会增加内存开销。

  • 总结:基类析构函数是虚函数
  • 总结:如果不需要基类对派生类及对象进行操作,则不能定义虚函数(包括虚析构函数),因为这样会增加内存开销。

 

32、 c++怎样让返回对象的函数不调用拷贝构造函数(20200621)

 拷贝构造函数前加 “explicit” 关键字

 

33、孤儿进程和僵尸进程(20200621)

https://www.cnblogs.com/Anker/p/3271773.html

 

 

 

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值