总结的文档

利用l三个线程 交替打印 abc abc。

问题synchronized和Lock区别

1.synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;tryLock()成功和失败的返回值不同
3.synchronized会自动释放锁,Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
解决问题:
Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

lock condition代码打印abc:

private static Lock lock = new ReentrantLock();
private static Condition A = lock.newCondition();
private static Condition B = lock.newCondition();
private static Condition C = lock.newCondition();

private static int count = 0;

static class ThreadA extends Thread {
    @Override
    public void run() {
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                while (count % 3 != 0) {//这里是不等于0,也就是说没轮到该线程执行,之前一直等待状态
                    A.await(); //该线程A将会释放lock锁,构造成节点加入等待队列并进入等待状态
                }
                System.out.print("A");
                count++;
                B.signal(); // signal()和signalall()将等待队列中的当前线程节点移除,重新加入到同步队列进行同步状态的竞争,
                            // 如果抢占失败,则加入到同步队列尾部 这里是唤醒 B的线程
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

static class ThreadB extends Thread {
    @Override
    public void run() {
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                while (count % 3 != 1) {
                    B.await();// B释放lock锁,当前面A线程执行后会通过B.signal()唤醒该线程
                }
                System.out.print("B");
                count++;
                C.signal();// B执行完唤醒C线程
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

static class ThreadC extends Thread {
    @Override
    public void run() {
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                while (count % 3 != 2) {
                    C.await();// C释放lock锁
                }
                System.out.print("C");
                count++;
                A.signal();// C执行完唤醒A线程
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public static void main(String[] args) {
    new ThreadA().start();
    new ThreadB().start();
    new ThreadC().start();
}

applicationcontext和beanfactory 是什么 ? 区别?

Bean factory

是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。

特性:

Bean 的实例化/串联

通常情况,BeanFactory 的实现是使用懒加载的方式,这意味着 beans 只有在我们通过 getBean() 方法直接调用它们时才进行实例化
实现 BeanFactory 最常用的 API 是 XMLBeanFactory

ApplicationContext

ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

特性:

Bean instantiation/wiring
Bean 的实例化/串联
自动的 BeanPostProcessor 注册
自动的 BeanFactoryPostProcessor 注册
方便的 MessageSource 访问(i18n)
ApplicationEvent 的发布

与 BeanFactory 懒加载的方式不同,它是预加载,所以,每一个 bean 都在 ApplicationContext 启动之后实例化

总结

ApplicationContext 包含 BeanFactory 的所有特性,通常推荐使用前者。但是也有一些限制情形,比如移动应用内存消耗比较严苛,在那些情景中,使用更轻量级的 BeanFactory 是更合理的。然而,在大多数企业级的应用中,ApplicationContext 首选。

概述spring加载过程

1、项目启动
每个SpringBoot程序都有一个主入口,也就是main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,该方法所在类需要使用@SpringBootApplication注解来标注SpringApplication.run() 方法。
启动一个项目首先是创建了 SpringApplication 的实例,然后调用了 SpringApplication 的run()方法。
2、bean组件生成
spring的IOC容器(beanfactory)会扫描所有类并且将所有被 @component以及其扩展注解@controller、@repository、@service 所标注的类,通过类的全类名通过反射实例化一个bean组件放进ioc容器(beanfactory)管理,存储方式类似MAP,类名首字母小写为key,类的实例化为value,
3、依赖注入
@autowried以及@recourse修饰的变量会自动装配bean组件完成依赖注入
依赖注入过程同样是调用BeanFactory,获取所依赖的属性(依赖注入的方式:构造方法,set方式,注解方式等, 获取方法不尽相同)通过反射实例化对应的依赖,并将实例化后的依赖 封装进 类所实例化的bean中,至此,依赖注入完成。(也就是说通过反射对beanfactory中的bean组件按注入所包含变量的属性)

分类

公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。

非公平锁
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

可重入锁
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。

不可重入锁
不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。

独享锁:该锁每一次只能被一个线程所持有。

共享锁:该锁可被多个线程共有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占。

互斥锁
在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。
如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态,
第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。在这种方式下,只有一个线程能够访问被互斥锁保护的资源

读写锁
读写锁既是互斥锁,又是共享锁,read模式是共享,write是互斥(排它锁)的。

悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

分段锁
分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

偏向锁
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

轻量级
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

重量级锁
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

自旋锁
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

为什么会有偏向锁,轻量级锁,重量级锁:

1.为什么要引入偏向锁?
因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。

2.为什么要引入轻量级锁?
轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

3.轻量级锁什么时候升级为重量级锁?
自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,
线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
重量级锁线程会被挂起park,耗性能

Synchronized与Lock的区别

其实ReentrantLock和Synchronized最核心的区别就在于Synchronized适合于并发竞争低的情况,因为Synchronized的锁升级如果最终升级为重量级锁在使用的过程中是没有办法消除的,意味着每次都要和cpu去请求锁资源,而ReentrantLock主要是提供了阻塞的能力,通过在高并发下线程的挂起,来减少竞争,提高并发能力,所以我们文章标题的答案,也就显而易见了
synchronized是一个关键字,是由jvm层面去实现的,而ReentrantLock是由java api去实现的。
synchronized是隐式锁,可以自动释放锁,ReentrantLock是显式锁,需要手动释放锁。
ReentrantLock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
ReentrantLock可以获取锁状态,而synchronized不能。

synchronized底层原理

synchronized的使用

synchronized的使用一般出现在三个地方:
修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁

synchronized修饰实例方法

修饰实例方法即一个类中有多个实例方法(A,B,C),synchronized可对类中的实例方法进行修饰,如下所示:synchronized 关键字修饰 increase()方法
public class AccountingSync implements Runnable{ //共享资源(临界资源)
static int i=0;
//synchronized 修饰实例方法
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}}
}

注意: 如果创建多个不同类实例的线程t1,t2,此时t1和t2可以同时访问被synchronized修饰的实例方法。如图下所示:创建了两个不同类实例的线程t1,t2
这种情况下,t1和t2可以同时获取到increase()的锁,因为我们创建了两个不同的实例锁,t1和t2获取的是不同实例锁。
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new AccountingSync());
Thread t2=new Thread(new AccountingSync());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。
正确用法:
应创建线程时使用同一实例进行创建,这时候t1和t2就会进行increase()的锁竞争。而不会同步执行。
public static void main(String[] args) throws InterruptedException {
AccountingSync instance=new AccountingSync();
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}

synchronized修饰静态方法

当synchronized作用于静态方法时,其锁就是当前类的class对象锁。因为静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态 成员的并发操作。定义如下:
public class AccountingSyncClass implements Runnable{
static int i=0;
/**
* 作用于静态方法,锁是当前class对象,也就是
* AccountingSyncClass类对应的class对象
*/
public static synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}

用法:
此时如果我们还是像上文一样创建两个不同实例的线程,去调用synchronized 修饰的静态方法increase(),此时也会进行锁竞争,因为静态方法的锁是class对象锁,而不是实例锁。如下所示:
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new AccountingSyncClass());
Thread t2=new Thread(new AccountingSyncClass()); /
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

synchronized修饰代码块

在方法体较大,但是需要同步的代码只有其中一部分的时候,可以选择使用synchronized修饰代码块,提高程序效率。如下所示:
public class AccountingSync implements Runnable{
static AccountingSync instance=new AccountingSync();
static int i=0;
@Override
public void run() { //省略其他耗时操作…
//使用同步代码块对变量i进行同步操作,锁对象为instance
synchronized(instance){
for(int j=0;j<1000000;j++){
i++;
}
}
}

代码看出,将synchronized作用于一个给定的实例对象instance,即当前实例对象就是锁对象,每次当线程进入synchronized包裹的代码块时就会要求当前线程持有instance实例对象锁,如果当前有其他线程正持有该对象锁,那么新到的线程就必须等待,这样也就保证了每次只有一个线程执行i++;操作。当然除了instance作为对象外,我们还可以使用this对象(代表当前实例)或者当前类的class对象作为锁。

synchronized原理

Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现, 无论是显式同步(有明确的 monitorenter 和 monitorexit 指令,即同步代码块)还是隐式同步都是如此。
synchronized 修饰的代码块:使用monitorenter 和 monitorexit 指令来实现同步的
synchronized 修饰的同步方法:同步方法 并不是由 monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的

总的来说,synchronized修饰代码块同步的底层原理就是在代码块的前后位置使用monitorenter和monitorexit 指令。monitorenter会判断是否可以获取当前对象锁的对象监听器,如果可以获取将就将其锁计数器加1。此时其他线程要获取该对象锁的对象监听器的时候就会被阻塞。直到当前线程执行monitorexit 指令,进行对象监听器的释放,同时将锁计数器减1。

方法级的同步是隐式的,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。
当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。
在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放

synchronized锁升级策略

偏向锁:
当锁对象第一次被线程获取的时候,线程使用CAS操作把这个锁的线程ID记录在对象头之中,同时置偏向标志位1。以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。
如果线程使用CAS操作时失败则表示该锁对象上存在竞争并且这个时候另外一个线程获得偏向锁的所有权。当到达全局安全点(safepoint,这个时间点上没有正在执行的字节码)时获得偏向锁的线程被挂起,膨胀为轻量级锁。也就是说:当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束
轻量级锁:
当前线程使用CAS将对象头的mark Word锁标记位替换为锁记录指针,如果成功,当前线程获得锁。
如果失败,表示其他线程竞争锁,当前线程尝试通过自旋获取锁
如果自旋成功则依然处于轻量级状态
如果自旋失败,升级为重量级锁
重量级锁: 此时就升级到重量级锁了

synchronized可重入性

从互斥锁的设计上来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁,请求将会成功。
在java中synchronized是基于原子性的内部锁机制,是可重入的,因此在一个线程调用synchronized方法的同时在其方法体内部调用该对象另一个synchronized方法,也就是说一个线程得到一个对象锁后再次请求该对象锁,是允许的,这就是synchronized的可重入性。如下:
此时我们创建一个实例的线程t1,而在t1进入同步代码块运行过程中,又要进入该实例锁下的其他同步方法,这种情况是被允许的。
public class AccountingSync implements Runnable {
// static AccountingSync instance = new AccountingSync();
static int i=0;
static int j=0;
@Override
public void run() {
for(int j=0;j<1000000;j++){ //this,当前实例对象锁
synchronized(this){
i++;
increase();//synchronized的可重入性
}
}
}

public synchronized void increase(){
    j++;
}

public static void main(String[] args) throws InterruptedException {
    AccountingSync instance = new AccountingSync();
    Thread t1=new Thread(instance);
    t1.start();
    t1.join();
    System.out.println(i);
    System.out.println(j);
}

}

在获取当前实例对象锁后进入synchronized代码块执行同步代码,并在代码块中调用了当前实例对象的另外一个synchronized方法,再次请求当前实例锁时,将被允许,进而执行方法体代码,这就是重入锁最直接的体现,需要特别注意另外一种情况,当子类继承父类时,子类也是可以通过可重入锁调用父类的同步方法。注意由于synchronized是基于monitor实现的,因此每次重入,monitor中的计数器仍会加1。

总结

synchronized三种用法:修饰方法,静态方法,代码块
synchronized实现同步代码块是通过两个指令实现,获取当前的对象监听器,同时锁计数器加1
synchronized同步方法是隐式实现的,通过判断标志位实现,但是底层实际还是通过对象监听器实现
synchronized锁升级策略:偏向锁,轻量级锁(自旋锁),重量级锁
synchronized可重入性

ReentrantLock底层原理

CAS操作

CAS是一种无锁算法。有3个操作数:内存值V、旧的预期值A、要修改的新值B。
当我们要修改内存值的时候要进行判断,当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

AQS队列

AQS是一个用于构建锁和同步容器的框架。
AQS使用一个FIFO的队列(也叫CLH队列,是CLH锁的一种变形),表示排队等待锁的线程。队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus。结构如下图所示:

ReentrantLock的锁流程

ReentrantLock先通过CAS尝试获取锁,如果获取了就将锁状态state设置为1
如果此时锁已经被占用:
被自己占用:判断当前的锁是否是自己占用了,如果是的话就锁计数器会state++(可重入性)
被其他线程占用:该线程加入AQS队列并wait()
当前驱线程的锁被释放,一直到state==0,挂在CLH队列为首的线程就会被notify(),然后继续CAS尝试获取锁,此时:
非公平锁,如果有其他线程尝试lock(),有可能被其他刚好申请锁的线程抢占
公平锁,只有在CLH队列头的线程才可以获取锁,新来的线程只能插入到队尾。
(注:ReentrantLock默认是非公平锁,也可以指定为公平锁)

ReentrantLock的使用

ReentrantLock的使用是通过lock()和 unlock()方法实现的
具体底层实现方法就是通过大量的CAS操作+AQS队列去维护state的状态。
具体lock和unlock的使用这里就不做阐述了,无非就是获取锁和释放锁的两个函数。

总结

ReentrantLock底层通过大量的CAS操作和AQS队列去维护state变量的状态去实现线程安全的功能。
ReentrantLock锁流程就是先通过CAS操作尝试修改state状态获取锁,如果获取失败就判断当前占用锁的是不是自身,如果是的话就进行重入。如果不是就进入AQS队列等待。
ReentrantLock默认是非公平锁,也可以设置为公平锁使用,那么就要维护AQS队列
ReentrantLock是可重入锁

使用场景

在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化
由于ReentrantLock但是当同步非常激烈的时候,还能维持常态。所以比较适合高并发的场景。

TCP和HTTP

TCP

面向连接的通信协议,通过三次握手建立连接(socket通过TCP/IP连接时就是经过3次握手),通信完成后要关闭连接,它只用于端对端的通讯
TCP协议通过3次握手建立起一个可靠的连接,通过将数据包进行排序以及检验的方式,可以提供一种可靠的数据流服务 :
第一次握手:客户端发送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头部+实际数据
TCP头包括源端口号和目标主机端口号、顺序号、确认号、校验字

TCP编程的服务器端一般步骤是:
  1、创建一个socket,用函数socket();
  2、设置socket属性,用函数setsockopt(); * 可选
  3、绑定IP地址、端口等信息到socket上,用函数bind();
  4、开启监听,用函数listen();
  5、接收客户端上来的连接,用函数accept();
  6、收发数据,用函数send()和recv(),或者read()和write();
  7、关闭网络连接;
  8、关闭监听;

TCP编程的客户端一般步骤是:
  1、创建一个socket,用函数socket();
  2、设置socket属性,用函数setsockopt();* 可选
  3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
  4、设置要连接的对方的IP地址和端口等属性;
  5、连接服务器,用函数connect();
  6、收发数据,用函数send()和recv(),或者read()和write();
  7、关闭网络连接;

HTTP

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

SOCKET原理

Socket实现服务器与客户端之间的物理连接,并进行数据传输。主要有TCP/UDP两个协议。Socket处于网络协议的传输层。

socket概念

socket是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

建立socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3、 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户 端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

SOCKET连接与TCP连接

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

Socket连接与HTTP连接

由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用 中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导 致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。
而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。
很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给 客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以 保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

HTTP协议 与 TCP协议 的区别

1、TCP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。
2、我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等。
3、Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据完毕后,Http会立即将TCP连接断开。我们直接通过Socket编程使用TCP协议的时候,因为我们自己可以通过代码区控制什么时候打开连接什么时候关闭连接,只要我们不通过代码把连接关闭,这个连接就会在客户端和服务端的进程中一直存在,相关状态数据会一直保存着。
4、HTTP(超文本传输协议)是利用TCP在两台电脑(通常是Web服务器和客户端)之间传输信息的协议。客户端使用Web浏览器发起HTTP请求给Web服务器,Web服务器发送被请求的信息给客户端。

HTTP连接举例:
我们模拟一下TCP短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起 close操作。
为什么?一般的server不会回复完client后立即关闭连接的,当然不排除有特殊的情况。
短连接一般只会在 client/server间传递一次读写操作。
短连接的操作步骤是:建立连接——数据传输——关闭连接…建立连接——数据传输——关闭连接…建立连接——数据传输——关闭连接

http和tcp应用场景

TCP :适用于发送的文件多,需要将发送的内容分成多个数据包进行传输。【 需要分段,编号,流量控制,拥泵控制,可靠传输的功能。客户端与服务器建立TCP连接(协商参数:选择确认性,最大报文),通信结束后,需要释放连接】【适用场景例如:电影下载,文件下载】

TCP:
HTTP诞生之初主要是应用于WEB端内容获取

实时聊天功能

思路 
1.创建一个Socket实例
  2.利用I/O流与服务器进行通信

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值