java实现tcp通信多线程_JavaSE笔记(四)网络通讯和多线程

本文详细介绍了Java中TCP通信的实现原理,包括网络基础知识、IP地址、TCP/IP协议、端口号等概念。讲解了InetAddress、URL、Sockets和Datagram等网络功能类的使用,以及如何通过ServerSocket和Socket进行服务器端和客户端的通信。同时,文章提到了多线程在TCP通信中的应用,以及如何处理并发连接。此外,还探讨了线程同步和互斥的概念,包括临界区、互斥量、信号量和事件的区别,并提供了Thread类的一些常用方法。
摘要由CSDN通过智能技术生成

Socket编程

一、网络基础知识

两台计算机要通过网络进行通信,必须具备:

a、唯一的标识(IP地址);

b、需要共同的语言(协议);

c、辨别不同应用程序(端口号)。

1、IP地址:

每台计算机的唯一标识,用来区分网络中的不同主机,是两台主机进行网络通信必不可少的。IPv4

2、协议:

a、TCP/IP协议:目前世界上应用最为广泛的协议。是以TCP和IP为基础的不同层次上多个协议的集合。也称为:TCP/IP协议族 或者 TCP/IP协议栈。

b、TCP: Transmission Control Protocol 传输控制协议

c、IP :Internet Protocol 互联网协议

d、TCP/IP模型(网络分层):

1、物理层;网线。。。

2、数据链路层

3、网络层

4、传输层:TCP/IP协议

5、应用层:HTTP超文本传输协议、FTP文件传输协议、SMTP简单邮件传送协议、Telnet远程登录服务。

3、端口号:

a、用于区分不同的应用程序;

b、端口号范围为0-65535,其中0-1023为系统所保留;

c、IP地址和端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础。

d、常用端口号-- http: 80; ftp; 21; telnet; 23。

JAVA中得网络支持

针对网络通信的不同层次,Java提供的网络功能有四大类:

1、InetAddress:用于标识网络上的硬件资源。(IP地址)

2、URL:统一资源定位符---通过URL可以直接读取或写入网络上得数据。

3、Sockets:使用TCP协议实现网络通信的Socket相关的类。

4、Datagram:使用UDP协议,将数据保存在数据报中,通过网络进行通信

1.InetAddress类没有构造方法,所以不能直接new出一个对象;

可以通过InetAddress类的静态方法获得InetAddress的对象;

InetAddress.getLocalHost();

InetAddress.getByName("");

2.类主要方法:

String - address.getHostName();

String - address.getHostAddress();

public static InetAddress getByName(String host) throws UnknownHostException

在给定主机名的情况下确定主机的 IP 地址。

主机名可以是机器名(如 "java.sun.com"),也可以是其 IP 地址的文本表示形式

// 获取本机的InetAdress实例

InetAddress address = InetAddress.getLocalHost();

输出:hostName/hostAdress

// 根据主机名称获取InetAdress实例

InetAddress address2 = InetAddress.getByName("hostName");

// 根据IP地址获取实例

InetAddress address2 = InetAddress.getByName("ipAdress");

URL

url:统一资源定位符:表示internet上的网络资源

协议+资源名称

url常用方法:

存在java.net包中,提供创建url/子url,获取url等方法

第一步:创建实例

URL imooc=new URL("http://www.imooc.com");

//在原有url下再创建url

URL url=new URL(imooc,"/index.html?username=tom#test")

//获取url的信息

url.getProtocol();//获取协议http

url.getHost();//获取主机www.imooc.com

url.getPort();//获取端口号:-1

url.getPath();//获取文件路径/index.html

url.getFile();//获取文件名/index.html?username=tom

url.getRef();//获取相对路径test

url.getQuery();//查询字符串username=tom

注:创建url时没有指定端口号则getPort方法返回-1,协议不同会使用默认端口

2url读取网页内容:

URL url=new URL("http://www.baidu.com");

//获取输入流通过openStream方法

InputStream is=url.openStream();

//转化成字符输入流

InputStream isr=new InputStreamReader(is);

//加缓冲提高读取效率

BufferedReader br=new BufferedReader();

String date=br.teadline();

while(date.next()){System.out.print(date);

date=br.readLine();

}

完成后要关闭资源相关资源:br,isr字符输入流,is字节输入流

注:如果输出是乱码则要在is字节输入流中规定编码为

InputStream isr=new InputStreamReader(is,"utf8");

二、通信过程(Socket通信模型):

1、在服务端建立一个ServerSocket,绑定相应的端口,并且在指定的端口进行侦听,等待客户端的连接。

2、当客户端创建连接Socket并且向服务端发送请求。

3、服务器收到请求,并且接受客户端的请求信息。一旦接收到客户端的连接请求后,会创建一个链接socket,用来与客户端的socket进行通信。通过相应的输入/输出流进行数据的交换,数据的发送接收以及数据的响应等等。

4、当客户端和服务端通信完毕后,需要分别关闭socket,结束通信。

ServerSocket常用方法:

ServerSocket(int port)——创建并绑定到特定端口的服务器套接字

accept()——侦听并接受到此套接字的连接

close()——关闭此套接字

getInetAddress()——得到ServerSocket对象绑定的IP地址。如果ServerSocket对象未绑定IP地址,返回0.0.0.0。

getLocalPort()——返回此套接字在其上侦听的端口

Socket常用方法:

Socket(InetAddress address, int port)——创建一个套接字并将其连接到指定ip地址的指定端口号

Socket(String host, int port)——创建一个套接字并将其连接到指定主机上的指定端口号

close()——关闭此套接字

getInetAddress()——返回套接字连接的地址

getInputStream()——返回此套接字的输入流

getOutputStream——返回此套接字的输出流

ed99f14c55b2

Paste_Image.png

实例

步骤:

(1)创建ServerSocket和Socket

(2)打开连接到Socket的输入/输出操作

(3)按照协议对Socket进行读/写操作

(4)关闭输入输出流,关闭Socket

服务器端:

(1)创建ServerSocket对象,绑定监听器

(2)通过accept()方法监听客户端请求

(3)连接建立以后通过读取客户端发送请求消息

(4)通过输出流向客户端发送响应信息

(5)关闭资源

客户端:

(1)创建Socket对象,指明需要连接的服务器地址和端口号(1023以后的端口)

(2)连接建立后,通过输出流向服务器端请求

(3)通过输入流获取服务器响应信息

(4)关闭资源

常用I/O操作

InputStream is = socket.getInputStream();//字节输入流

InputStreamReader isr = new InputStreamReader(is)//将字节输入流转换为字符输入流

BufferedReader br = new BufferedReader(isr);//为输入流添加缓冲

br.readLine()按行读取

flush()刷新缓存

使用多线程实现多客户端的

主线程负责创建socket

* 一个线程用来读取

* 一个线程用来写入,用两个内部类

* 要用多线程实现,send线程和recived线程同时访问buff数据区。

* 发送完成后notify,接收线程。接收线程自身wait

* 接收的说,我先wait一下,可能缓存中还没有数据,我会拿到空值得。

* 发送的说Ok,我先写,写完了我notifyall你。我就一直锁着,这样默契的合作了。变成并行处理了

每个客户端连接服务端都会产生一个新的socket。

UDP编程

1、UDP协议(用户数据报协议)是无连接、不可靠、无序的。

2、UDP协议以数据报作为数据传输的载体。

3、使用UDP进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram),在数据报中指明所要达到的Socket(主机地址和端口号),然后在将数据报发生出去。

4、相关操作类:DatagramPacket:表示数据报包

DatagramSocket:进行端到端通信的类

DatagramPacket类构造方法:

1、DatagramPacket(byte[] buf,int length)//接受长度为length的数据包

2、DatagramPacket(byte[] buf,int length,InetAddress address,int port)//将指定长度的字节发生到指定主机的指定端口

DatagramSocket类

1、构造方法:DatagramSocket();

DatagramSocket(int port,InetAddress laddr);

2、close();//关闭DatagramSocket

3、getInetAddress();//获取地址

4、getPort();//获取端口号

5、send(DatagramPacket p);//从此套接字发送数据包

recrive(DatagramPacket p);//从此套接字接收数据包

服务器端实现步骤:

1、创建DatagramSocket,指定端口号

2、创建DatagramPacket

3、接收客户端发送的数据信息

4、读取数据

客户端:

1、定义发送信息

2、创建DatagramPacket:包含将要发送信息

3、创建DatagramSocket

4、发送数据

服务端具体代码:

1、创建服务器端DatagramSocket

DatagramSocket socket=DatagramSocket(8800);

2、创建数据报,用户接收客户端发送的数据

byte[] data=new byte[1024];

DatagramPacket packet=new datagramPacket(data,data.length);

3、接收客户端发送的数据

socket.receive(packet);//会处于阻塞,直到接收到数据报

4、读取数据

String info=new String(data,0,packet.getLength());

System.out.println("客户端说"+info)

UDP编程-服务器向客户端响应数据(与客户端向服务器发送数据类似)

1、定义客户端的地址、端口号、数据。通过DatagramPacket的.getAddress()方法获取客户端的地址,通过.getPort()方法获取端口号。

2、创建数据报DatagramPacket,包含响应的数据信息。

3、响应客户端。调用DatagramSocket的.send()方法。

4、关闭资源

UDP编程-客户端接受服务器端响应的数据

1、创建数据报DatagramPacket,用于接受服务器端响应的数据。首先创建一个字节数组。

2、接受服务器端响应的数据。调用DatagramPacket的.receive()方法。

3、读取数据。将字节数组转变为字符串。

4、关闭资源。

同步模式:

同步模式的特点是在通过Socket进行连接、接收、发送数据时,客户机和服务器在接收到对方响应前会处于阻塞状态,即一直等到收到对方请求进才继续执行下面的语句。可见,同步模式只适用于数据处理不太多的场合。当程序执行的任务很多时,长时间的等待可能会让用户无法忍受。

异步模式:

异步模式的特点是在通过Socket进行连接、接收、发送操作时,客户机或服务器不会处于阻塞方式,而是利用callback机制进行连接、接收、发送处理,这样就可以在调用发送或接收的方法后直接返回,并继续执行下面的程序。可见,异步套接字特别适用于进行大量数据处理的场合。

Socket 总结

1、多线程的优先级(死循环中注意设置优先级问题。)建议降低优先级。

2、关闭socket流,而不提倡关闭输入输出流。

3、使用tcp通信传输对象更符合面向对象编程的思想。

4、通过socket编程传输文件的功能模块是:通过io流读取文件字符流进行传输。

深入浅出Java多线程

概念

1.进程:是程序或任务的执行的过程,具有动态性,它持有资源(共享内存,共享文件)和线程

2.线程:系统中最小的执行单元。 比如一个软件里边的各种任务就是线程。同一进程中有多个线程,线程共享进程的资源

3.线程交互:即线程通信

4.线程之间存在同步和互斥

线程同步互斥的4种方式

临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别

1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程

对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被

挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。

2、互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保

证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共

资源安全共享

3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目

4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作

Thread常用方法

sleep(long millis, int nanos) 线程休眠 millis休眠的时间,单位是毫秒,可以精确到纳秒

join(long millis, int nanos) 调用线程 可以让其它线程等待自己运行,直到结束

static void yield() 当前运行线程释放处理器资源并且重新去竞争处理器资源

static Thread currentThread() 返回当前正在处理器上运行的线程的引用

重载的几个形式

1.没有参数,指明了其它的线程一定要等待正在执行的线程执行完毕之后,都会获得运行的机会

2.nanos是要把精确度改变,可改成纳秒

ed99f14c55b2

Paste_Image.png

两种方法实现线程:

1、继承 Thread 类

class MyThread extends Thread{};

Thread myThread = new MyThread();

myThread.start();

2、实现Runnable类

class MyRunnable implements Runnable{}

Thread myRunnable = new Thread(new MyRunnable);

myRunnable.start();

3、Thread启动后执行run()方法

4、若实现接口通过Thread.currentThread().getName()方法获取当前线程名称,继承Thread则getName()方法获取当前线程。

1.加入join是为了让舞台线程最后停止,如果不加有可能舞台线程结束,军队线程还未停止,就好比导演喊停,演员还在

演!可以在join后面加入测试语句System.out.println("舞台结束!");,然后去掉或者保留join观察效果。

2.volatile 关键字 保证了线程可以正确地读取其他线程写入的值,如果不写成volatile,由于可见性的问题,当前线程有可能

不能读到这个值//可见性JMM(JAVA内存模型)happens-before原则、可见性原则

用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的值

3.Thread.yield();//让出处理器时间,公平竞争

线程执行过程中几个重要的方法

sleep(); 让线程休眠一段时间,

yield(); 让出当前线程的执行权限,让线程调度重新选择线程进行执行;

join(); 让其他线程都停止,等待当前线程执行完毕。

当某个线程使用join()方法加入到另一个线程时,另一个线程会等待该线程执行完毕后再继续执行。

stop方法使得线程戛然而止,完成了什么工作,哪些工作还没有做,都不知道,且清理工作也没有做,所以不是正确的停止线程方法

正确的停止线程方法是,在线程执行中设置状态标识,通过控制标识来控制线程正常完整的执行结束线程

volatile是保证所有子线程里的变量都能同步到主内存里变量的值

不要用stop()方法结束线程

如何正确停止线程?

--使用退出标志

如本文:volatile boolean keepRunning=true;

这样做的好处是:使得线程有机会使得一个完整的业务步骤被完整地执行,在执行完业务步骤后有充分的时间去做代码的清理工作,使得线程代码在实际中更安全

@Java线程——如何正确停止线程

一、错误一:stop()方法

1、not stop:stop()方法会使线程戛然而止

2、使程序突然中止,无法完成完整的业务步骤,也无法进行清理工作

二、错误二:interrupt()方法

1、interrupt()方法只能设置interrupt标志位(且在线程阻塞情况下,标志位会被清除,更无法设置中断标志位),无法停止线程

三、正确方法:设置退出标志

1、使用退出标志位来停止while循环

2、完成最后一次业务后跳出while循环后,之后进行一些清理工作

线程停止:

1、调用stop()方法会使线程戛然停止,而无法知道线程任务完成情况,官方已经不推荐使用。

2、interrupt()方法设置线程的标识位,并在线程中判断标志位的状态,从而结束线程,但是当在线程中开启了另外的线程

时,比如在线程中Tread.sleep(),这时候调用interrupt()方法设置标志位可能设置的是你想要停止的线程,也可能是想要停

止的线程中的线程的标志位,因此interrupt()方法也并不能很好的结束线程。

3、第三种方法,在线程的类声明一个volatile变量来记录线程的状态,相当于interrupt()方法那样,volatile关键字表示线程

中的变量可以接受外部其他线程改变。因此可以在需要停止的地方设置volatile声明的变量的值设置为状态,并在执行run()

函数里判断是否结束。

线程交互

@Java线程——线程交互——争用条件

1、当多个线程同时共享访问同一数据(内存区域)时,每个线程都尝试操作该数据,从而导致数据被破坏(corrupted),这种现象称为争用条件

2、原因是,每个线程在操作数据时,会先将数据初值读【取到自己获得的内存中】,然后在内存中进行运算后,重新赋值到数据。

3、争用条件:线程1在还【未重新将值赋回去时】,线程1阻塞,线程2开始访问该数据,然后进行了修改,之后被阻塞的线程1再获得资源,而将之前计算的值覆盖掉线程2所修改的值,就出现了数据丢失情况

@Java线程——线程交互——互斥与同步

一、互斥

1、同一时间,只能有一个线程访问数据

二、同步

1、是一种通信机制,一个线程操作完成后,以某种方式通知其他线程

三、实现方法

1、【互斥】构建锁对象(Object objLock),通过synchronized(lockObj){ 互斥的代码块 }

2、加锁操作会开销系统资源,降低效率。

3、在某线程的条件不满足任务时,使用lockObj.wait()对线程进行阻挡,防止其继续竞争CPU资源,滞留在wait set中,等待唤醒,【唤醒后继续完成业务】

4、【同步】在某一代码正确执行完业务后,通过lockObj.notifyAll()唤醒所有在lockObj对象等待的线程

ed99f14c55b2

Paste_Image.png

同步是两个线程之间的一种交互的操作(一个线程发出消息另外一个线程响应)。

同步的实现:wait();notify();notifyAll();这三个方法都是属于Java中的Object对象的成员函数。

调用wait();和notifyAll();方法使线程进入等待或者唤醒不是在同一个线程的同一次操作中执行的,当操作结束,唤醒了所有的等待线程之后,线程又将有着公平的机会竞争CPU资源。

注意:notify();方法唤醒wait set 中的一条线程使其具有竞争CPU的机会,具体唤醒那一条线程是随机的由Java的底层算法决定,我们不能去控制。

通过synchronized关键字为临界区(critical)加锁,这样在线程竞争资源时,当某一条线程获得锁进入临界区后,其他线程将无法再次获取锁进入临界区(critical),直到获得锁的线程退出临界区(critical),释放锁资源。Java的语法保证了同一时间只能有一条线程可以获得lockObject。

ed99f14c55b2

Paste_Image.png

重点回顾:

1,创建线程的方法--继承Thread--实现Runnable接口----线程的基本操作

2,可见性--volatile关键字

3,征用条件

4,互斥与同步-----synchronized----wait/notify/notifyAll

未完待续...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值