C#面试题

目录

基本概念

装箱和拆箱

1、装箱拆箱的“箱”是什么,“箱”存放在哪里?

2、装箱快还是拆箱快?

3、装箱和拆箱有什么性能影响?

值类型和引用类型分别是哪些

访问权限修饰符

委托(delegate)

 什么是委托链

 委托链用途

事件(event)是委托吗

虚函数(virtual/override)

构造函数、析构函数可以写成虚函数么?

 抽象函数(abstract)

 集合

 泛型

反射

多线程

 常用关键字(Thread、Task、ThreadPool)

 什么时候会用到多线程

进程和线程的区别

什么是死锁

死锁的四个条件

 怎么避免死锁

 多线程中最少要多少个资源才能保证不发生死锁?

内存

程序内存的构成

 堆栈的区别

设计模式

单例模式

套字节(Socket) 

用法示例

TCP和UDP区别

TCP三次握手四次挥手


基本概念

装箱和拆箱

装箱的过程,是将 值类型 转换为 引用类型 的过程; 拆箱则是将引用类型转换为值类型

int val = 100; 
object obj = val; //装箱
int num = (int) obj; //拆箱

装箱拆箱进阶

1、装箱拆箱的“箱”是什么,“箱”存放在哪里?

装箱(Boxing)操作会创建一个堆上的对象,而拆箱(Unboxing)操作则会从堆上的对象中提取值。

"箱"代表的是创建的堆上对象,它是一个将值类型包装为引用类型的容器。当进行装箱操作时,CLR会在堆上分配内存以存储值类型的值,并返回对该对象的引用。这个对象实际上是值类型的副本,被封装在引用类型内。

具体来说,当进行装箱操作时,CLR执行以下步骤:

  1. 在堆上分配内存,以存储值类型的值;
  2. 将值类型的值复制到刚分配的内存中;
  3. 返回对这个堆上对象的引用;

所以,装箱后的对象存放在堆上。

        当进行拆箱操作时,CLR会将装箱后的对象中存储的值类型的值提取出来,并将其存储在相应的值类型变量中。

2、装箱快还是拆箱快?

一般来说,拆箱的性能要优于装箱。

装箱操作需要将值类型转换为引用类型,并在堆上创建一个对象来存储值类型的拷贝。这个过程涉及到内存分配、复制数据和类型检查,相对比较耗时。

拆箱操作则是从装箱后的对象中提取值类型的值,并将其存储在相应的值类型变量中。这个过程主要涉及到数据的复制和类型检查,相对来说相对简单和高效。

因此,一般情况下,拆箱的性能要优于装箱。频繁进行装箱操作可能会对性能产生负面影响,特别是在循环或大规模数据处理等性能敏感的场景下。所以,在需要高性能的情况下,应尽量避免不必要的装箱操作,而尽可能使用直接操作值类型的方式。

然而,需要根据具体的应用场景和代码逻辑来综合评估装箱和拆箱的性能开销。对于某些特定的情况,如需要将值类型存储在集合类(如List、ArrayList)中,不可避免地需要进行装箱操作,但要注意避免过度的装箱和拆箱操作,以提高代码的性能。

3、装箱和拆箱有什么性能影响?

装箱的性能影响:
内存分配:装箱操作需要在堆上分配额外的内存用于存储值类型的拷贝。这涉及到内存分配和释放的开销,可能导致内存碎片化。
数据复制:装箱操作会将值类型的值复制到堆上创建的对象中。这个过程涉及到数据的复制,增加了额外的时间消耗。
类型检查:CLR会进行类型检查,确保装箱后的对象是合法的引用类型。这个检查会引入附加的开销。

拆箱的性能影响:
数据复制:拆箱操作会从装箱后的对象中提取值类型的值,并将其存储在相应的值类型变量中。这个过程主要涉及到数据的复制,可以说是相对较快的步骤。
类型检查:CLR会进行类型检查,确保拆箱的目标类型与装箱对象的类型匹配。这个检查会引入一定的开销。
总体而言,装箱和拆箱操作都会涉及到数据的复制和类型检查,这些操作都需要耗费额外的时间和内存。在频繁使用和大规模数据处理的场景下,过多的装箱和拆箱操作可能会降低性能并增加资源消耗。

为了提高性能,应该尽量避免不必要的装箱和拆箱操作,特别是在循环或性能敏感的代码中。可以通过使用泛型集合(如List<T>)来替代装箱的集合类,或者使用值类型数组等直接操作值类型的方式,从而避免装箱和拆箱带来的性能损失。

值类型和引用类型分别是哪些

值类型:基本数据类型(int、long、short、byte、float、double、char、bool)、枚举(enum)、结构体(struct)、可空类型(在后面加问号,如:int?、double?

引用类型class、interface、delegate、array、string以及装箱后的可空类型

访问权限修饰符

private私有成员, 在类的内部才可以访问(只能从其声明上下文中进行访问)
protected保护成员,该类内部和从该类派生的类中可以访问
protected internal在派生类或同一程序集内都可以访问。
public公共成员,完全公开,没有访问限制。  
internal在同一程序集(dll 或 exe)中可以访问。

委托(delegate)

委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。说白了就是类似指向函数方法的指针。

 什么是委托链

委托本身不是链表。它们只是一个存储方法地址的变量。你可以将委托看作是一个方法的引用,就像一个指针。

然而,委托可以用来实现链表的概念——委托链,它是一组委托对象,可以在这组委托中添加、删除和执行委托。

示例:

public delegate void MyDelegate();class Program
{static void Main(){MyDelegate myDelegate = Method1;//用 += 运算符添加委托会创建一个委托链。移除则使用-=myDelegate += Method2;myDelegate += Method3;myDelegate();//输出:Method1 Method2 Method3Console.ReadKey();}static void Method1(){Console.Write("Method1");}static void Method2(){Console.Write("Method2");}static void Method3(){Console.Write("Method3");}
}

 委托链用途

委托链非常有用,可以以简单优美的方式表示程序控制流。例如,我们可以使用委托链在事件的触发和处理中实现松耦合。使用场景例如:事件处理、拦截器、插件系统等。

事件(event)是委托吗

不是委托,但它们之间有紧密的联系。事件基于委托,为委托提供了一个发布/订阅机制。事件是一种特殊的委托,它用于实现观察者模式,允许对象在特定事件发生时通知其他对象。事件的声明使用event关键词,它的返回类型是一个委托类型。在编码中尽量使用规范命名,通常以名字+Event作为事件的名称。

虚函数(virtual/override)

为了指明某个成员函数具有多态性,用关键字virtual来标记其为虚函数,表示可以被派生类重写。关键字override实现。

详细实现示例:

class A
{public virtual void Func()//注意virtual关键字,表明这是一个虚函数{Console.WriteLine("A");}
}class B:A // 注意B是从A类继承,所以A是父类,B是子类
{public override void Func() // 注意override关键字,表明重新实现了虚函数{Console.WriteLine("B");}
}class C : B // 注意C是从B类继承,所以B是父类,C是子类
{
}class D : A // 注意D是从A类继承,所以A是父类,D是子类
{public new void Func() // 注意new ,表明覆盖父类里的同名类,而不是重新实现{Console.WriteLine("D");}
}
class Program
{static void Main(string[] args){A a = new A();//A为声明类,4为实例类//A为声明类,B为实例类//A为声明类,C为实例类A b = new B();A c = new C();       A d= new D(); //A为声明类,D为实例类D d1 = new D(); //D为声明类,D为实例类a.Func(); // 执行过程: 1.先检查声明类 2.检查到是虚方法 3.转去检查实例类A,就为本身 4.执行实例类A中的方法 5.输出结果 Ab.Func();// 执行过程: 1.先检查声明类A 2.检查到是虚方法 3.转去检查实例类B,有重载的 4.执行实例类B中的方法 5.输出结果  Bc.Func();// 执行过程: 1.先检查声明类 2.检查到是虚方法 3.转去检查实例类C,无重载的 4.转去检查类C的父类B,有重载的 5.执行父类B中的方法 6.输出结果  B d.Func(); //执行过程: 1.先检查声明类 2.检查到是虚方法 3.转去检查实例类D,无重载的(注意,虽然D里有实现Func,但没有使用override关键字 ,所以不会被认为是重载) 4.转去检查类D的父类,就为本身 5.执行父类中的方法 5.输出结果  Ad1.Func();  // 执行D类里的Fun,输出结果DConsole.ReadLine();}
}

构造函数、析构函数可以写成虚函数么?

构造函数:不行

原因:构造函数是在创建对象时调用的特殊方法,用于初始化对象的状态。由于构造函数是在对象创建时立即调用的,因此它们不能被声明为虚函数。

虚函数是可以在派生类中被覆盖的函数,它们可以在运行时根据对象的实际类型动态地调用。然而,构造函数是在对象创建时立即调用的,因此它们不能被覆盖或重写。

如果需要在派生类中初始化对象的状态,可以在派生类的构造函数中调用基类的构造函数,或者在派生类中添加新的构造函数。

析构函数:不行

原因:析构函数是用于释放对象所占用的资源的方法,它们是在对象被垃圾回收器回收之前调用的。由于析构函数是在对象生命周期结束时调用的,它们也不能被声明为虚函数。

虚函数主要用于实现多态性,即在运行时根据对象的实际类型动态地调用不同的方法。而析构函数和构造函数是用于初始化或释放对象的状态,它们是在对象创建或销毁时立即调用的,因此不能被声明为虚函数。

如果需要在派生类中释放对象所占用的资源,可以在派生类的析构函数中调用基类的析构函数,或者在派生类中添加新的析构函数。
(在C++中析构函数可以写出虚函数,详情:C++构造函数、析构函数可以写成虚函数么?)

 抽象函数(abstract)

抽象函数abstract修饰,是指在抽象类(Abstract Class)或接口(Interface)中声明的、没有具体实现的函数。并且必须在派生类中被重写(override)。

    public abstract class A{public abstract void Func();}public  class B:A{public override void Func(){throw new NotImplementedException();}}

 集合

 class 集合{//https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/collections //集合讲解//https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,cf7f4095e4de7646 List源码public static void Collections_Test(){//System.Collections.Generic  List<string> list;//表示可按索引访问的对象的列表。 提供用于对列表进行搜索、排序和修改的方法。默认大小4,容量不够则2倍扩容Dictionary<string, string> dictionary;//表示基于键进行组织的键/ 值对的集合。SortedList<string, string> sortedList; //表示基于相关的 IComparer<T> 实现按键进行排序的键/ 值对的集合。HashSet<int> hashSet = new HashSet<int>();//值不能重复且没有顺序Queue<string> queue = new Queue<string>(); //表示对象的先进先出(FIFO) 集合。Stack<string> stack;//表示对象的后进先出(LIFO) 集合。queue.Dequeue();//尽量不用System.Collections下面的,是类型不安全的 ,性能较低//因为上面泛型中指定了类型, 是类型安全的,所以其不需要进行装箱拆箱操作,效率也就相对提升//System.CollectionsArrayList a1 = new ArrayList();// 表示对象的数组,这些对象的大小会根据需要动态增加。Hashtable b1 = new Hashtable(); b1.Add("key", "value");// 表示根据键的哈希代码进行组织的键/ 值对的集合。SortedList so1 = new SortedList(); // 表示键/值对的集合,这些键值对按键排序并可按照键和索引访问。Queue c1 = new Queue();//表示对象的先进先出(FIFO) 集合。Stack d1 = new Stack();//表示对象的后进先出(LIFO) 集合。}}

 泛型

泛型约束,可用作在类或者方法上面。

where T : struct类型参数必须是不可为 null 的值类型。 有关可为 null 的值类型的信息,请参阅可为 null 的值类型。 由于所有值类型都具有可访问的无参数构造函数,因此 struct 约束表示 new() 约束,并且不能与 new() 约束结合使用。 struct 约束也不能与 unmanaged 约束结合使用。
where T : class类型参数必须是引用类型。 此约束还应用于任何类、接口、委托或数组类型。 在可为 null 的上下文中,T 必须是不可为 null 的引用类型。
where T : class?类型参数必须是可为 null 或不可为 null 的引用类型。 此约束还应用于任何类、接口、委托或数组类型。
where T : notnull类型参数必须是不可为 null 的类型。 参数可以是不可为 null 的引用类型,也可以是不可为 null 的值类型。
where T : default重写方法或提供显式接口实现时,如果需要指定不受约束的类型参数,此约束可解决歧义。 default 约束表示基方法,但不包含 class 或 struct 约束。 有关详细信息,请参阅default约束规范建议。
where T : unmanaged类型参数必须是不可为 null 的非托管类型。 unmanaged 约束表示 struct 约束,且不能与 struct 约束或 new() 约束结合使用。
where T : new()类型参数必须具有公共无参数构造函数。 与其他约束一起使用时,new() 约束必须最后指定。 new() 约束不能与 struct 和 unmanaged 约束结合使用。
where T :<基类名>类型参数必须是指定的基类或派生自指定的基类。 在可为 null 的上下文中,T 必须是从指定基类派生的不可为 null 的引用类型。
where T :<基类名>?类型参数必须是指定的基类或派生自指定的基类。 在可为 null 的上下文中,T 可以是从指定基类派生的可为 null 或不可为 null 的类型。
where T :<接口名称>类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在的可为 null 的上下文中,T 必须是实现指定接口的不可为 null 的类型。
where T :<接口名称>?类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在可为 null 的上下文中,T 可以是可为 null 的引用类型、不可为 null 的引用类型或值类型。 T 不能是可为 null 的值类型。
where T : U为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。 在可为 null 的上下文中,如果 U 是不可为 null 的引用类型,T 必须是不可为 null 的引用类型。 如果 U 是可为 null 的引用类型,则 T 可以是可为 null 的引用类型,也可以是不可为 null 的引用类型。

反射

C#编写的程序会编译成一个程序集(.DLL或.exe),其中会包含元数据、编译代码和资源,通过反射可以获取到程序集中的信息。
反射加载dll,读取module、类、方法、特性。通俗来讲,反射就是我们在只知道一个对象的外部而不了解内部结构的情况下,可以知道这个对象的内部实现。

详情请看反射详解

补充:

反射的使用场景:

       假设有多种相机的SDK, 反射出DLL的成员方法,设计合理的情况下使得可以在不改程序任何代码的情况下,适应新相机的DLL。

多线程

 常用关键字(Thread、Task、ThreadPool

  用法示例及解释:

  class 多线程{/*  *  Task和Thread有区别吗?*  Task和Thread都能创建⽤多线程的⽅式执⾏代码,但它们有较⼤的区别。*  Task较新,发布于.NET 4.5,能结合新的async/await代码模型写代码,它不⽌能创建新线程,还能使⽤线程池(默认)、单线程等⽅式编程,*  在UI编程领域,Task还能⾃动返回UI线程上下⽂,还提供了许多便利API以管理多个Task。*/public  void Thread_Test(){//Thread 的几种初始化方式Thread thread = new Thread(() => { });thread = new Thread(delegate () { });thread = new Thread(run);thread = new Thread(new ThreadStart(run));int i = 0;thread = new Thread(run2);//thread.Start(i);//传参//Task net4.5才加入进来,可以使用async, wait进行异步编程Task task = Task.Factory.StartNew(()=> { });task = new Task(()=> { });task = new Task(run);//ThreadPool 常用于解决并发问题/** 许多应用程序创建大量处于睡眠状态,等待事件发生的线程。还有许多线程可能会进入休眠状态,* 这些线程只是为了定期唤醒以轮询更改或更新的状态信息。线程池,使您可以通过由系统管理的工作线程池来更有效地使用线程。*/int workerThreads, completionPortThreads;ThreadPool.SetMaxThreads(16, 16);// 设置线程池最大线程数量ThreadPool.SetMinThreads(8, 8);ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);// 获取线程池最大线程数量ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);ThreadPool.QueueUserWorkItem(new WaitCallback(run2));}public void run(){}public void run2(object obj)//必须为object{}}

 什么时候会用到多线程

并发处理:需要同时处理多个任务或请求时,可以使用多线程来并发执行不同的任务,从而提高程序的并发性和响应性。例如,Web服务器需要同时处理多个用户请求,可以使用多线程来并发处理这些请求。
资源共享:需要多个线程共享某些资源(如内存、文件等)时,可以使用多线程来协调访问和修改这些资源。例如,一个多线程的文件下载器需要同时下载多个文件,可以使用多线程来并发下载这些文件。
可扩展性:需要根据系统负载自动调整线程数量时,可以使用多线程来动态创建或销毁线程。例如,一个Web服务器需要根据用户请求数量自动调整线程池大小,可以使用多线程来实现。
提高效率:需要利用多核处理器的并行计算能力时,可以使用多线程来并行执行计算密集型任务。例如,一个图像处理软件需要对多张图片进行处理,可以使用多线程来并行处理这些图片。

进程和线程的区别

(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位

(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行

(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.

(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

什么是死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。

死锁的四个条件

互斥条件:至少有一个资源必须处于非共享(占用)状态,即一次只能被一个进程或线程占用。
请求与保持条件:进程或线程至少需要持有一个资源,并且在等待其他资源时不释放已占有的资源。
不剥夺条件:已分配给进程或线程的资源不能被强制性地剥夺,只能由持有资源的进程或线程主动释放。
循环等待条件:存在一个进程或线程的资源申请序列,使得每个进程或线程都在等待下一个进程或线程所持有的资源。

 怎么避免死锁

打破4个条件其一,
1. 破坏互斥条件:对于某些资源,可以允许多个进程或线程同时访问,从而避免资源互斥。
2. 破坏请求与保持条件:进程或线程在申请资源时,一次性申请所有需要的资源,如果无法满足,则释放已占有的资源,等待重新申请。
3. 破坏不可剥夺条件:当一个进程或线程占有一些资源时,如果申请新的资源被拒绝,可以强制性地剥夺已占有的资源,以满足其他进程或线程的需求。
4. 破坏循环等待条件:对系统中的资源进行编号,规定进程或线程只能按照编号递增的顺序申请资源,从而避免循环等待。
加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
死锁检测(银行家算法)

 多线程中最少要多少个资源才能保证不发生死锁?

k * ( n - 1 ) + 1(k=线程,n=所需资源数)

内存

程序内存的构成

1、栈区(stack)基本数据都放这
2、堆区(heap) new出来的都放这
3、静态数据区(全局)静态,全局对象都放这
4、程序代码区

 堆栈的区别

•    堆是动态分配的内存区域,用于存储程序运行时动态创建的对象和数据。
•    堆的大小通常比栈大,并且可以根据程序的需要进行动态调整。
•    对象在堆上进行分配和释放内存,需要手动管理,程序员负责手动分配和释放内存空间。
•    堆上的对象在程序的任意位置都可以被访问。
•    可以通过指针或引用来访问堆上的对象。
•    在堆上分配的内存需要手动释放,否则可能会导致内存泄漏。
•    栈是一种特殊的数据结构,用于存储函数调用时的局部变量、函数参数和返回地址等信息。
•    栈的大小通常比堆小,大小固定。
•    栈内存由编译器自动分配和释放,无需手动管理。
•    栈上的数据是按照"先进后出"(LIFO)的顺序进行存储和访问。
•    函数调用时,当前函数的状态(包括局部变量值、返回地址等)被压入栈中,当函数执行完毕时,栈顶的数据被弹出,恢复上一个函数的执行状态。
•    栈内存的分配和释放速度较快。
总结: 堆和栈是计算机内存中的两个不同区域,用于存储不同类型的数据。堆用于存储动态分配的对象和数据,大小动态调整;栈用于存储函数调用时的局部变量和函数执行状态,大小固定。堆上的内存需要手动分配和释放,而栈内存由编译器自动管理。

设计模式

单例模式

懒汉模式(下面的示例是最优写法)——顾名思义就是懒,没有对象需要调用它的时候不去实例化,有人来向它要对象的时候再实例化对象,因为懒,比我还懒

using System;
using System.Collections.Generic;
/// <summary>
/// 适用于在多线程的情况下保证只有一个实例化对象的情况,例如银行的操作系统
/// </summary>
namespace DoubleLockInstance
{//----------------------------------// 双重锁定单例public sealed class Singleton{// 定义一个类对象,用于内部实现private static Singleton myInstance;// readonly   -   这个成员只能在“类初始化”时赋值  ,所谓的类初始化,就是直接在类里面初始化// 变量标记为 readonly,第一次引用类的成员时创建实例private static readonly object lockRoot = new object ();// 设置构造方法为私有,这样就不能在外部实例化类对象了private Singleton (){}// 实例化对象的方法public static Singleton GetInstance (){// 外部不能实例化对象,但是能调用类里面的静态方法// 外部需要调用这个方法来使用类对象,如果对象不存在就创建// 这里面使用两个判断是否为null的原因是,我们不需要每次都对实例化的语句进行加锁,只有当对象不存在的时候加锁就可以了if (myInstance == null) {// 锁定的作用就是为了保证当多线程同时执行这句代码的时候保证对象的唯一性// 锁定会让同时执行这段代码的线程排队执行// lock里面需要用一个已经存在的对象来判断,所以不能使用myInstancelock (lockRoot) {// 这里还需要一个判断的原因是,如果多线程都通过了外层的判断进行排队// 那将会实例化多个对象出来,所以这里还需要进行一次判断,保证线程的安全if (myInstance == null) {myInstance = new Singleton ();}}}return myInstance;}}
}

套字节(Socket) 

用法示例

  class SOCKET{public SOCKET(){}public void ClientSocket(){// 创建一个Socket实例Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);// 连接服务器IPAddress serverIP = IPAddress.Parse("127.0.0.1");IPEndPoint serverEP = new IPEndPoint(serverIP, 8888);clientSocket.Connect(serverEP);// 发送和接收数据byte[] sendData = Encoding.ASCII.GetBytes("Hello, server!");clientSocket.Send(sendData);byte[] recvData = new byte[1024];int recvLen = clientSocket.Receive(recvData);string recvMsg = Encoding.ASCII.GetString(recvData, 0, recvLen);Console.WriteLine("Received from server: " + recvMsg);//短链接的话,使用完之后,直接用下面的代码关闭就行了//长链接的话,就在你想断开的时候使用下面的代码// 关闭连接clientSocket.Shutdown(SocketShutdown.Both);clientSocket.Close();}public void StartSocketServer(){new Thread(() =>{// 创建一个Socket实例Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);// 绑定服务器地址和端口IPAddress serverIP = IPAddress.Parse("127.0.0.1");  // 使用任意可用的IP地址IPEndPoint serverEP = new IPEndPoint(serverIP, 8888);serverSocket.Bind(serverEP);// 监听连接请求,设置最大允许的连接数为10serverSocket.Listen(10);Console.WriteLine("等待客户端连接...");while (true){// 等待客户端连接Socket clientSocket = serverSocket.Accept();Console.WriteLine("客户端已连接: " + clientSocket.RemoteEndPoint);// 开启一个新线程处理客户端请求System.Threading.Thread clientThread = new System.Threading.Thread(() =>{try{while (true){byte[] buffer = new byte[1024];int bytesRead = clientSocket.Receive(buffer);  // 接收客户端数据string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);Console.WriteLine("接收到客户端数据: " + message);string response = "Hello, Client!";  // 构造响应数据clientSocket.Send(Encoding.ASCII.GetBytes(response));  // 发送响应数据}}catch (SocketException ex){Console.WriteLine("客户端断开: " + ex.Message);}catch (Exception ex){Console.WriteLine("与客户端通信发生异常: " + ex.Message);}finally{clientSocket.Shutdown(SocketShutdown.Both);  // 关闭客户端连接clientSocket.Close();}});clientThread.Start();}}).Start();}#region UDPclass UDPServer{static void Main(){// 监听的 IP 地址和端口string ipAddress = "127.0.0.1";int port = 12345;// 创建 UDP 服务器套接字UdpClient udpServer = new UdpClient(port);Console.WriteLine("Server started. Waiting for clients...");while (true){// 接收客户端数据IPEndPoint clientEndPoint = new IPEndPoint(IPAddress.Any, 0);byte[] receivedBytes = udpServer.Receive(ref clientEndPoint);string request = Encoding.UTF8.GetString(receivedBytes);Console.WriteLine("Received request: {0}", request);// 做出响应string response = "Hello from server!";byte[] responseBytes = Encoding.UTF8.GetBytes(response);// 发送响应数据给客户端udpServer.Send(responseBytes, responseBytes.Length, clientEndPoint);}}}class UDPClient{static void Main(){// 服务器的 IP 地址和端口string ipAddress = "127.0.0.1";int port = 12345;// 创建 UDP 客户端套接字UdpClient udpClient = new UdpClient();// 构造服务器的终结点IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);// 发送请求数据给服务器string request = "Hello from client!";byte[] requestBytes = Encoding.UTF8.GetBytes(request);udpClient.Send(requestBytes, requestBytes.Length, serverEndPoint);// 接收服务器的响应byte[] responseBytes = udpClient.Receive(ref serverEndPoint);string response = Encoding.UTF8.GetString(responseBytes);Console.WriteLine("Received response: {0}", response);// 关闭连接udpClient.Close();}}#endregion#region TCPclass TCPServer{static void Main(){// 监听的 IP 地址和端口string ipAddress = "127.0.0.1";int port = 12345;// 创建 TCP 服务器套接字TcpListener listener = new TcpListener(IPAddress.Parse(ipAddress), port);// 开始监听客户端连接listener.Start();Console.WriteLine("Server started. Waiting for clients...");while (true){// 接受客户端连接TcpClient client = listener.AcceptTcpClient();Console.WriteLine("Client connected.");// 处理客户端请求{  // 获取网络流NetworkStream stream = client.GetStream();// 读取客户端发送的数据byte[] buffer = new byte[1024];int bytesRead = stream.Read(buffer, 0, buffer.Length);string request = Encoding.UTF8.GetString(buffer, 0, bytesRead);Console.WriteLine("Received request: {0}", request);// 做出响应string response = "Hello from server!";byte[] responseBuffer = Encoding.UTF8.GetBytes(response);// 发送响应数据给客户端stream.Write(responseBuffer, 0, responseBuffer.Length);// 关闭连接client.Close();Console.WriteLine("Client disconnected.");}}}}class TCPClient{static void Main(){// 服务器的 IP 地址和端口string ipAddress = "127.0.0.1";int port = 12345;// 创建 TCP 客户端套接字TcpClient client = new TcpClient();// 连接到服务器client.Connect(IPAddress.Parse(ipAddress), port);Console.WriteLine("Connected to server.");// 获取网络流NetworkStream stream = client.GetStream();// 发送请求数据给服务器string request = "Hello from client!";byte[] requestBuffer = Encoding.UTF8.GetBytes(request);stream.Write(requestBuffer, 0, requestBuffer.Length);// 接收服务器的响应byte[] buffer = new byte[1024];int bytesRead = stream.Read(buffer, 0, buffer.Length);string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);Console.WriteLine("Received response: {0}", response);// 关闭连接client.Close();Console.WriteLine("Disconnected from server.");}}#endregion}

TCP和UDP区别

连接性TCP是面向连接的协议,需要在发送和接收数据前建立可靠的连接。而UDP是无连接的协议,发送数据前不需要建立连接。
可靠性TCP提供可靠的服务,通过TCP连接传送的数据无差错,不丢失,不重复,且按序到达。而UDP尽最大努力交付,不保证可靠交付。
资源需求TCP要求系统资源较多,而UDP较少。
传输方式TCP是流式传输,没有边界,但保证顺序和可靠。而UDP是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
拥塞控制TCP有拥塞控制

TCP三次握手四次挥手

三次握手:
第一次握手(SYN):客户端向服务器发送一个连接请求报文段,报文段中包含SYN(同步序列号)标志位,并随机选择一个初始序列号(ISN)。
第二次握手(SYN/ACK):服务器接收到客户端的连接请求报文段后,向客户端发送确认报文段,报文段中包含SYN(同步序列号)和ACK(确认序列号)标志位,确认号为客户端的初始序列号+1,同时服务器也随机选择一个初始序列号。
第三次握手(ACK):客户端接收到服务器的确认报文段后,向服务器发送确认报文段,报文段中包含ACK(确认序列号)标志位和确认号,确认号为服务器的初始序列号+1。


TCP四次挥手:
第一次挥手:客户端发送一个FIN报文段,用来关闭客户端到服务器的数据传送,客户端进入FIN_WAIT_1状态。
第二次挥手:服务器收到FIN报文段后,回复一个应答报文段(ACK),表示接受关闭请求。服务器进入CLOSE_WAIT状态,客户端进入FIN_WAIT_2状态。
第三次挥手:服务器发送一个FIN报文段,请求关闭连接。之后服务器进入LAST_ACK状态。
第四次挥手:客户端收到FIN报文段后,回复一个应答报文段(ACK),然后进入TIME_WAIT状态。服务器收到ACK报文段后,就关闭连接。
最终客户端在等待2MSL之后进入CLOSED状态。(RFC文档中规定为2分钟,但是实际实现过程中,MSL一般为:30秒、1分钟、2分钟)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/219431.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】实战项目——通讯录

引言 学会创建一个通讯录&#xff0c;对过往知识进行加深和巩固。 文章很长&#xff0c;要耐心学完哦&#xff01; ✨ 猪巴戒&#xff1a;个人主页✨ 所属专栏&#xff1a;《C语言进阶》 &#x1f388;跟着猪巴戒&#xff0c;一起学习C语言&#x1f388; 目录 引言 实战 建…

VLAN间的通讯---三层交换

一.三层交换 1.概念 使用三层交换技术实现VLAN间通信 三层交换二层交换 三层转发 2.基于CEF的MLS CEF是一种基于拓补转发的模型 转发信息库&#xff08;FIB&#xff09;临接关系表 转发信息库&#xff08;FIB&#xff09;可以理解为路由表 邻接关系表可以理解为MAC地址表…

Linux驱动(中断、异步通知):红外对射,并在Qt StatusBus使用指示灯进行显示

本文工作&#xff1a; 1、Linux驱动与应用程序编写&#xff1a;使用了设备树、中断、异步通知知识点&#xff0c;实现了红外对射状态的异步信息提醒。 2、QT程序编写&#xff1a;自定义了一个“文本指示灯”类&#xff0c;并放置在QMainWidget的StatusBus中。 3、C与C混合编程与…

【华为数据之道学习笔记】5-4 数据入湖方式

数据入湖遵循华为信息架构&#xff0c;以逻辑数据实体为粒度入湖&#xff0c;逻辑数据实体在首次入湖时应该考虑信息的完整性。原则上&#xff0c;一个逻辑数据实体的所有属性应该一次性进湖&#xff0c;避免一个逻辑实体多次入湖&#xff0c;增加入湖工作量。 数据入湖的方式…

Android Studio好用的插件推荐

目录 一、插件推荐 二、如何下载 1.点击File—>Settings ​2.点击Plugins然后进行搜索下载 三、Android Studio 模板 一、插件推荐 这个插件可以为您自动生成Parcelable代码。Parcelable是一种用于在Android组件之间传递自定义对象的机制&#xff0c;但手动编写Parcela…

RabbitMQ搭建集群环境、配置镜像集群、负载均衡

RabbitMQ集群搭建 Linux安装RabbitMQ下载安装基本操作命令开启管理界面及配置 RabbitMQ集群搭建确定rabbitmq安装目录启动第一个节点启动第二个节点停止命令创建集群查看集群集群管理 RabbitMQ镜像集群配置启用HA策略创建一个镜像队列测试镜像队列 负载均衡-HAProxy安装HAProxy…

从计算机底层深入Golang高并发

从计算机底层深入Golang高并发 1.源码流程架构图 2.源码解读 runtime/proc.go下的newpro() func newproc(fn *funcval) {//计算额外参数的地址argpgp : getg()pc : getcallerpc()//s1使用systemstack调用newproc1 systemstack(func() {newg : newproc1(fn, gp, pc)_p_ : getg…

代码随想录二刷 | 二叉树 | 112. 路径总和

代码随想录二刷 &#xff5c; 二叉树 &#xff5c; 112. 路径总和 题目描述解题思路递归迭代 代码实现递归迭代 题目描述 112.路径总和 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节…

leetcode-138-随机链表的复制(Java实现)

题目&#xff1a; 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点…

【LeetCode刷题】-- 166.分数到小数

166.分数到小数 class Solution {public String fractionToDecimal(int numerator, int denominator) {StringBuilder sb new StringBuilder();HashMap<Long,Integer> map new HashMap<>();//为了防止溢出&#xff0c;将分子和分母都转成64位整数long a numerat…

女生想通过培训转行软件测试类可行吗?

首先&#xff0c;女生转行IT行业做软件测试是可以的&#xff0c;因为软件测试岗&#xff0c;尤其是其中的功能性测试岗&#xff0c;入行门槛并不高&#xff0c;有很多女生在做&#xff0c;且我个人认为还蛮适合女生的&#xff0c;因为女生相对来说更细心&#xff0c;文档能力也…

HashMap构造函数解析与应用场景

目录 1. HashMap简介 2. HashMap的构造函数 2.1 默认构造函数 2.2 指定初始容量和加载因子的构造函数 3. 构造函数参数的影响 3.1 初始容量的选择 3.2 加载因子的选择 4. 构造函数的应用场景 4.1 默认构造函数的应用场景 4.2 指定初始容量和加载因子的构造函数的应用…

Linux(23):Linux 核心编译与管理

编译前的任务&#xff1a;认识核心与取得核心原始码 Linux 其实指的是核心。这个【核心(kernel)】是整个操作系统的最底层&#xff0c;他负责了整个硬件的驱动&#xff0c;以及提供各种系统所需的核心功能&#xff0c;包括防火墙机制、是否支持 LVM 或 Quota 等文件系统等等&a…

FFmpeg的AVcodecParser

文章目录 结构体操作函数支持的AVCodecParser 这个模块是AVCodec中的子模块&#xff0c;专门用来提前解析码流的元数据&#xff0c;为后面的解码做准备&#xff0c;这一点对cuda-NVdec非常明显&#xff0c;英伟达解码器的元数据解析是放在CPU上的&#xff0c;所以就非常依赖这个…

预测性维护对制造企业设备管理的作用

制造企业设备管理和维护对于生产效率和成本控制至关重要。然而&#xff0c;传统的维护方法往往无法准确预测设备故障&#xff0c;导致生产中断和高额维修费用。为了应对这一挑战&#xff0c;越来越多的制造企业开始采用预测性维护技术。 预测性维护是通过传感器数据、机器学习和…

jmeter,取“临时重定向的登录接口”响应头中的cookie

1、线程组--创建线程组&#xff1b; 2、线程组--添加--取样器--HTTP请求&#xff1b; 3、Http请求--添加--后置处理器--正则表达式提取器&#xff1b; 4、线程组--添加--监听器--查看结果树&#xff1b; 5、线程组--添加--取样器--调试取样器。 首先理解 自动重定向 与跟随…

【漏洞复现】红帆OA iorepsavexml.aspx文件上传漏洞

漏洞描述 广州红帆科技深耕医疗行业20余年,专注医院行政管控,与企业微信、阿里钉钉全方位结合,推出web移动一体化办公解决方案——iOffice20(医微云)。提供行政办公、专业科室应用、决策辅助等信息化工具,采取平台化管理模式,取代医疗机构过往多系统分散式管理,实现医…

【Qt】使用QDataStream向QByteArray内读写数据时,输出QByteArray数据为空解决方案

原因 今天写示例时&#xff0c;用到使用QDataStream类向QByteArray读写数据&#xff0c;但打印出来为空。 下面是简化代码&#xff1a; QByteArray ba;QDataStream out(&ba, QIODevice::WriteOnly);out << "helloworld";qDebug().noquote() << &quo…

地图自定义省市区合并展示数据整合

需求一&#xff1a;将省级地图下的两个市合并成一个区域&#xff0c;中间的分割线隐藏。 1、访问下方地址&#xff0c;搜索并下载省级地图json文件。 地址&#xff1a;https://datav.aliyun.com/portal/school/atlas/area_selector 2、切换到边界生成器&#xff0c;上传刚刚下…

手写VUE后台管理系统10 - 封装Axios实现异常统一处理

目录 前后端交互约定安装创建Axios实例拦截器封装请求方法业务异常处理 axios 是一个易用、简洁且高效的http库 axios 中文文档&#xff1a;http://www.axios-js.com/zh-cn/docs/ 前后端交互约定 在本项目中&#xff0c;前后端交互统一使用 application/json;charsetUTF-8 的请…