阅读完需:约 36 分钟
1、概述
计算机网络是通过传输介质、通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来的,实现资源共享和数据传输的系统。网络编程就是编写程序使互联网的两个(或多个)设备(如计算机)之间进行数据传输。Java语言对网络编程提供了良好的支持。通过其提供的接口我们可以很方便地进行网络编程。
2、网络分层
通过网络发送数据是一项复杂的操作,必须仔细地协调网络的物理特性以及所发送数据的逻辑特征。通过网络将数据从一台主机发送到另外的主机,这个过程是通过计算机网络通信来完成。
网络通信的不同方面被分解为多个层,层与层之间用接口连接。通信的双方具有相同的层次,层次实现的功能由协议数据单元(PDU)来描述。不同系统中的同一层构成对等层,对等层之间通过对等层协议进行通信,理解批次定义好的规则和约定。每一层表示为物理硬件(即线缆和电流)与所传输信息之间的不同抽象层次。在理论上,每一层只与紧挨其上和其下的层对话。将网络分层,这样就可以修改甚至替换某一层的软件,只要层与层之间的接口保持不变,就不会影响到其他层。
计算机网络体系结构是计算机网络层次和协议的集合,网络体系结构对计算机网络实现的功能,以及网络协议、层次、接口和服务进行了描述,但并不涉及具体的实现。接口是同一节点内相邻层之间交换信息的连接处,也叫服务访问点(SAP)。

世界上第一个网络体系结构由IBM公司提出(1974年,SNA),以后其他公司也相继提出自己的网络体系结构。为了促进计算机网络的发展,国际标准化组织ISO在现有网络的基础上,提出了不基于具体机型、操作系统或公司的网络体系结构,称为开放系统互连参考模型,即OSI/RM(Open System Interconnection Reference Model)。
ISO制定的OSI参考模型过于庞大、复杂招致了许多批评。与此相对,美国国防部提出了TCP/IP协议栈参考模型,简化了OSI参考模型,由于TCP/IP协议栈的简单,获得了广泛的应用,并成为后续因特网使用的参考模型。
OSI参考模型
这里首先介绍OSI参考模型。OSI模型把网络通信的工作分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

- 物理层
物理层处于OSI的最底层,是整个开放系统的基础。物理层涉及通信信道上传输的原始比特流(bits),它的功能主要是为数据端设备提供传送数据的通路以及传输数据。
- 数据链路层
数据链路层的主要任务是实现计算机网络中相邻节点之间的可靠传输,把原始的、有差错的物理传输线加上数据链路协议以后,构成逻辑上可靠的数据链路。需要完成的功能有链路管理、成帧、差错控制以及流量控制等。其中成帧是对物理层的原始比特流进行界定,数据链路层也能够对帧的丢失进行处理。
- 网络层
网络层涉及源主机节点到目的主机节点之间可靠的网络传输,它需要完成的功能主要包括路由选择、网络寻址、流量控制、拥塞控制、网络互连等。
- 传输层
传输层起着承上启下的作用,涉及源端节点到目的端节点之间可靠的信息传输。传输层需要解决跨越网络连接的建立和释放,对底层不可靠的网络,建立连接时需要三次握手,释放连接时需要四次挥手。
- 会话层和表示层
会话层的主要功能是负责应用程序之间建立、维持和中断会话,同时也提供对设备和结点之间的会话控制,协调系统和服务之间的交流,并通过提供单工、半双工和全双工3种不同的通信方式,使系统和服务之间有序地进行通信。
表示层关心所传输数据信息的格式定义,其主要功能是把应用层提供的信息变换为能够共同理解的形式,提供字符代码、数据格式、控制信息格式、加密等的统一表示。
- 应用层
应用层为OSI的最高层,是直接为应用进程提供服务的。其作用是在实现多个系统应用进程相互通信的同时,完成一系列业务处理所需的服务。
TCP/IP参考模型
TCP/IP,即Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,是Internet最基本的协议,Internet国际互联网络的基础。
TCP/IP协议是一个开放的网络协议簇,它的名字主要取自最重要的网络层IP协议和传输层TCP协议。TCP/IP协议定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。TCP/IP参考模型采用4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求,这4个层次分别是:网络接口层、网络层(IP层)、传输层(TCP层)、应用层。

- 网络接口层
TCP/IP协议对网络接口层没有给出具体的描述,网络接口层对应着OSI参考模型的物理层和数据链路层
- 网络层(IP层)
网络层是整个TCP/IP协议栈的核心。它的功能是把分组发往目标网络或主机。同时,为了尽快地发送分组,可能需要沿不同的路径同时进行分组传递。因此,分组到达的顺序和发送的顺序可能不同,这就需要上层必须对分组进行排序。网络层除了需要完成路由的功能外,也可以完成将不同类型的网络(异构网)互连的任务。除此之外,互联网层还需要完成拥塞控制的功能。
- 传输层(TCP层)
TCP层负责在应用进程之间建立端到端的连接和可靠通信,它只存在与端节点中。TCP层涉及两个协议,TCP和UDP。其中,TCP协议提供面向连接的服务,提供按字节流的有序、可靠传输,可以实现连接管理、差错控制、流量控制、拥塞控制等。UDP协议提供无连接的服务,用于不需要或无法实现面向连接的网络应用中。
- 应用层
应用层为Internet中的各种网络应用提供服务。
网络协议
如同人与人之间相互交流是需要遵循一定的规则(如语言)一样,计算机之间能够进行相互通信是因为它们都共同遵守一定的规则,即网络协议。
OSI参考模型和TCP/IP模型在不同的层次中有许多不同的网络协议,如图所示:

网络协议之间的关系图如下:

IP协议(Internet protocol)
IP协议的作用在于把各种数据包准备无误的传递给对方,其中两个重要的条件是IP地址和MAC地址。由于IP地址是稀有资源,不可能每个人都拥有一个IP地址,所以我们通常的IP地址是路由器给我们生成的IP地址,路由器里面会记录我们的MAC地址。而MAC地址是全球唯一的。举例,IP地址就如同是我们居住小区的地址,而MAC地址就是我们住的那栋楼那个房间那个人。IP地址采用的IPv4格式,目前正在向IPv6过渡。
TCP协议(Transmission Control Protocol)
TCP(传输控制协议)是面向连接的传输层协议。TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。TCP协议采用字节流传输数据。
三次握手与四次挥手
TCP是面向连接的协议,因此每个TCP连接都有3个阶段:连接建立、数据传送和连接释放。连接建立经历三个步骤,通常称为“三次握手”。
TCP三次握手过程

1. 第一次握手(客户端发送请求)
客户机发送连接请求报文段到服务器,并进入SYN_SENT状态,等待服务器确认。发送连接请求报文段内容:SYN=1,seq=x;SYN=1意思是一个TCP的SYN标志位置为1的包,指明客户端打算连接的服务器的端口;seq=x表示客户端初始序号x,保存在包头的序列号(Sequence Number)字段里。
2. 第二次握手(服务端回传确认)
服务器收到客户端连接请求报文,如果同意建立连接,向客户机发回确认报文段(ACK)应答,并为该TCP连接分配TCP缓存和变量。服务器发回确认报文段内容:SYN=1,ACK=1,seq=y,ack=x+1;SYN标志位和ACK标志位均为1,同时将确认序号(Acknowledgement Number)设置为客户的ISN加1,即x+1;seq=y为服务端初始序号y。
3. 第三次握手(客户端回传确认)
客户机收到服务器的确认报文段后,向服务器给出确认报文段(ACK),并且也要给该连接分配缓存和变量。此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。客户端发回确认报文段内容:ACK=1,seq=x+1,ack=y+1;ACK=1为确认报文段;seq=x+1为客户端序号加1;ack=y+1,为服务器发来的ACK的初始序号字段+1。
注意:握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
TCP四次挥手过程

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
1. TCP客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态。发送报文段内容:FIN=1,seq=u;FIN=1表示请求切断连接;seq=u为客户端请求初始序号。
2. 服务端收到这个FIN,它发回一个ACK给客户端,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号;服务端进入CLOSE_WAIT状态。发送报文段内容:ACK=1,seq=v,ack=u+1;ACK=1为确认报文;seq=v为服务器确认初始序号;ack=u+1为客户端初始序号加1。
3. 服务器关闭客户端的连接后,发送一个FIN给客户端,服务端进入LAST_ACK状态。发送报文段内容:FIN=1,ACK=1,seq=w,ack=u+1;FIN=1为请求切断连接,ACK=1为确认报文,seq=w为服务端请求切断初始序号。
4. 客户端收到FIN后,客户端进入TIME_WAIT状态,接着发回一个ACK报文给服务端确认,并将确认序号设置为收到序号加1,服务端进入CLOSED状态,完成四次挥手。发送报文内容:ACK=1,seq=u+1,ack=w+1;ACK=1为确认报文,seq=u+1为客户端初始序号加1,ack=w+1为服务器初始序号加1。
注意:为什么连接的时候是三次握手,关闭的时候却是四次挥手?
因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭socket,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文,我收到了”。只有等到服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送,故需要四步挥手。
UDP协议(User Datagram Protocol)
UDP,用户数据报协议,它是TCP/IP协议簇中无连接的运输层协议。
- UDP是一个非连接的协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。
- 由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务器可同时向多个客户端传输相同的消息。
- UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小。
- 吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。
- UDP使用尽量最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表。
- UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部受就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。
TCP与UDP的区别
- TCP基于连接,UDP是无连接的;
- 对系统资源的要求,TCP较多,UDP较少;
- UDP程序结构较简单;
- TCP是流模式,而UDP是数据报模式;
- TCP保证数据正确性,而UDP可能丢包;TCP保证数据顺序,而UDP不保证;
HTTP协议(Hypertext Transfer Protocol)
HTTP,超文本传输协议,它是互联网上应用最为广泛的一种网络协议。HTTP是一种应用层协议,它是基于TCP协议之上的请求/响应式的协议。HTTP协议是Web浏览器和Web服务器之间通信的标准协议。HTTP指定客户端与服务器如何建立连接、客户端如何从服务器请求数据,服务器如何响应请求,以及最后如何关闭连接。HTTP连接使用TCP/IP来传输数据。
对于从客户端到服务器的每一个请求,都有4个步骤:
- 默认情况下,客户端在端口80打开与服务器的一个TCP连接,URL中还可以指定其他端口。
- 客户端向服务器发送消息,请求指定路径上的资源。这个资源包括一个首部,可选地(取决于请求的性质)还可以有一个空行,后面是这个请求的数据。
- 服务器向客户端发送响应。响应以响应码开头,后面是包含数据的首部、一个空行以及所请求的文档或错误消息。
- 服务器关闭连接。
现在使用的HTTP协议是HTTP/1.1版本,1997年之前采用的是HTTP1.0版本。HTTP连接在1.0版本中采用非持续连接工作方式,1.1版本采用的是持续连接工作方式,持续连接是指服务器在发送响应后仍然在一段时间内保持这条由TCP运输层协议建立起来的连接,使客户端和服务器可以继续在这条连接上传输HTTP报文。
是否采用持续连接工作方式,1.0中默认是关闭的,需要在HTTP头加入“Connection:Keep-Alive”,才能启用Keep-Alive。HTTP1.1中默认启用Keep-Alive,如果加入“Connection:close”,才关闭。目前大部分浏览器都是用HTTP1.1协议,也就是说默认都会发起Keep-Alive的连接请求了,所以是否能完成一个完整的Keep-Alive连接就看服务器设置情况。
HTTP和TCP/IP协议的关系
网络中有一段比较容易理解的介绍:
“我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如 果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也 可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发到网络上。”
3、网络通信
网络通信两个要素,通信双方的地址:
- IP
- 端口号
ip地址:Inet Adderss
- 唯一的标识internet 上的计算机( 通信实体)
- 本地回环地址(hostAddress):127.0.0.1 主机名 ( hostName ):localhost
- IP地址分类方式一: IPV4 IPV6
IPV4:4个字节组成,4个0~255。大概42亿个, 30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号隔开,如:2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b - IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。
- 192.168.开头的就是私有地址,范围即为 192.168.0.0 ~192.168.255.255,专门为组织机构内部使用
- 【查看JDK 帮助文档=> InetAddress类,代表IP】
public class demo {
public static void main(String[]args){
try{
//获得IP地址
InetAddress inetAddresses1= InetAddress.getByName("192.168.8.123");
System.out.println(inetAddresses1);
InetAddress inetAddresses2= InetAddress.getByName("www.baidu.com");
System.out.println(inetAddresses2);
//获取本地IP
InetAddress inetAddresses3=InetAddress.getByName("127.0.0.1");
System.out.println(inetAddresses3);
InetAddress inetAddresses4=InetAddress.getByName("localhost");
System.out.println(inetAddresses4);
InetAddress inetAddresses5=InetAddress.getLocalHost();
System.out.println(inetAddresses5);
//getHostName
System.out.println(inetAddresses2.getHostName());
//getHostAddress
System.out.println(inetAddresses2.getHostAddress());
//Canonical:规范的
System.out.println(inetAddresses2.getCanonicalHostName());
}catch(UnknownHostException e){
e.printStackTrace();
}
}
}
端口号
端口号标识正在计算机上运行的进程(程序)
- 不同的进程有不同的端口号,用来区分软件
- 被规定为一个16位的整数 0~65535
- TCP 和 UDP 各有 65535个端口,单个协议下端口不能冲突
- 端口分类:
- 公认端口: 0~1023。被预先定义的服务通信占用端口。
- HTTP 默认端口: 80
- HTTPS 默认端口:443
- FTP 默认端口: 21
- Telnet 默认端口:23
- 注册端口:1024~49151、分配给用户进程或应用程序。
- tomcat 默认端口:8080
- Mysql 默认端口:3306
- Oracle 默认端口:1521
- 动态、私有端口:49152~65535
- 公认端口: 0~1023。被预先定义的服务通信占用端口。
netstat -ano #查看所有端口
netstat -ano|findstr "6732" #查看指定端口
tasklist | findstr "6732" #查看指定进程
#使用任务管理器查看PID
端口号与IP地址的组合,得出一个网络套接字:Socket,所以说一些网络编程也被称为Socket编程
public class demo {
public static void main(String[] args) {
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8080);
InetSocketAddress socketAddress2 = new InetSocketAddress("localhost", 9000);
System.out.println(socketAddress.getHostName());
System.out.println(socketAddress.getAddress());
System.out.println(socketAddress.getPort());
System.out.println(socketAddress2.getHostName());
System.out.println(socketAddress2.getAddress());//返回地址
System.out.println(socketAddress2.getPort());//返回端口
}
}
Java Socket网络编程
Java的网络编程主要涉及到的内容是Socket编程。Socket,套接字,就是两台主机之间逻辑连接的端点。TCP/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。Socket是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议、本地主机的IP地址、本地进程的协议端口、远程主机的IP地址、远程进程的协议端口。
应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
Socket,实际上是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。实际上,Socket跟TCP/IP协议没有必然的关系,Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以说,Socket的出现,只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口,比如create、listen、accept、send、read和write等等。网络有一段关于socket和TCP/IP协议关系的说法比较容易理解:
“TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。” 实际上,传输层的TCP是基于网络层的IP协议的,而应用层的HTTP协议又是基于传输层的TCP协议的,而Socket本身不算是协议,就像上面所说,它只是提供了一个针对TCP或者UDP编程的接口。socket是对端口通信开发的工具,它要更底层一些。
Socket整体流程
Socket编程主要涉及到客户端和服务端两个方面,首先是在服务器端创建一个服务器套接字(ServerSocket),并把它附加到一个端口上,服务器从这个端口监听连接。端口号的范围是0到65536,但是0到1024是为特权服务保留的端口号,我们可以选择任意一个当前没有被其他进程使用的端口。
客户端请求与服务器进行连接的时候,根据服务器的域名或者IP地址,加上端口号,打开一个套接字。当服务器接受连接后,服务器和客户端之间的通信就像输入输出流一样进行操作。

Socket可以认为是两个互联机器终端应用软件的抽象,即对于一个网络连接,两端都有一个Socket,应用可以通过套接字进行交互通信。
在Java中,创建Socket连接另一台机器,可以从Socket中获取InputStream和OutputStream,将其作为输入输出流,使应用程序与操作本地文件IO类似。存在2个基于流的Socket类:ServerSocket和Socket。
- ServerSocket用于服务器端,监听客户端连接
- Socket用于客户端与服务端交互
- 服务端accept()方法处于阻塞状态,直到有客户端连接,创建一个服务端Socket,与客户端交互
另外,当创建ServerSocket时,只需要提供一个端口号,IP信息为本机默认信息;创建Socket时,必须提供IP和端口号;由ServerSocket.accept( )创建的不需要,其已包含所有信息。
例子1:客户端发送信息给服务端,服务端将数据显示在控制台上
客户端:
public class demo {
public static void main(String[] args) {
Socket socket=null;
OutputStream os=null;
try{
//1.连接服务器的地址
InetAddress serverIP=InetAddress.getByName("127.0.0.1");
int port=8899;
//2.创建一个Socket
socket=new Socket(serverIP,port);
//3.创建一个输出流,向外写东西
os=socket.getOutputStream();
os.write("你好,欢迎学习狂神说Java".getBytes());
}catch(UnknownHostException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}finally{
//4.关闭资源
try{
if(os!=null){
os.close();
}
if(socket!=null){
socket.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
}
kotlin版
fun main() {
fun TcpClinet() {
var socket: Socket? = null
var os: OutputStream? = null
try {
// 1.连接服务器地址
var serverIP = InetAddress.getByName("127.0.0.1")
val port = 8899
// 2.创建一个socket
socket = Socket(serverIP, port)
// 3.创建一个输出流,向外写东西
os = socket.getOutputStream()
os.write("你好socket".toByteArray())
} catch (e: UnknownHostException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
os?.close()
socket?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
TcpClinet()
}
服务端:
public class demo {
public static void main(String[] args) {
ServerSocket serverSocket=null;
Socket accept=null;
InputStream is=null;
ByteArrayOutputStream baos=null;
try{
//1.开放服务器端口,创建ServerSocket
serverSocket=new ServerSocket(8899);
//2.等待客户端的连接
accept=serverSocket.accept();
//3.读入客户端的消息,
is=accept.getInputStream();
baos=new ByteArrayOutputStream();
byte[]buffer=new byte[1024];
int len;
while((len=is.read(buffer))!=-1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
System.out.println(" 数据来源地址:"+accept.getInetAddress().getHostName());
}catch(IOException e){
e.printStackTrace();
}finally{
//4.关闭资源
try{
if(baos!=null){
baos.close();
}
if(is!=null){
is.close();
}
if(accept!=null){
accept.close();
}
if(serverSocket!=null){
serverSocket.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
kotlin版
fun main() {
fun TcpServer(){
var serverSocket : ServerSocket? =null
var accept : Socket? =null
var iss : InputStream?=null
var baos = ByteArrayOutputStream()
try {
// 1.开放服务器端口,创建ServerSocket
serverSocket= ServerSocket(8899)
// 2.等待客户端的连接
accept=serverSocket.accept()
// 3.读入客户端的消息
iss=accept.getInputStream()
var buffer=ByteArray(1024)
var len=0
while (iss.read(buffer).also {len = it}!=-1){
baos.write(buffer,0,len)
}
println(baos.toString())
}catch (e:IOException){
e.printStackTrace()
}finally {
try {
baos?.close()
iss?.close()
accept?.close()
serverSocket?.close()
}catch (e:Exception){
e.printStackTrace()
}
}
}
TcpServer()
}
例子2:客户端发送文件给服务器,服务端将文件保存在本地。
我们需要准备一个图片,放在项目目录下
客户端
public class demo {
public static void main(String[] args) {
//1.创建socket连接
Socket socket=new Socket(InetAddress.getByName("127.0.0.1"), 9090);
//2.创建一个输出流
OutputStream os=socket.getOutputStream();
//3.读取文件
FileInputStream fis=new FileInputStream(new File("qinjiang.jpg"));
//4.写出文件
byte[]buffer=new byte[1024];
int len;
while((len=fis.read(buffer))!=-1){
os.write(buffer,0,len);
}
//5.资源关闭,应该使用try-catch-finally
fis.close();
os.close();
socket.close();
}
}
kotlin版
fun main() {
fun TcpClient2(){
// 1.创建 socket 连接
var socket = Socket(InetAddress.getByName("127.0.0.1"),9090)
// 2.创建一个输入流
var outputStream=socket.getOutputStream()
// 3.读取文件
var fis=FileInputStream(File("1903.jpg"))
// 4.写入文件
var buffer=ByteArray(1024)
var len=0
while (fis.read(buffer).also { len=it }!=-1){
outputStream.write(buffer,0,len)
}
// 5.关闭流
fis.close()
outputStream.close()
socket.close()
}
TcpClient2()
}
服务端
public class demo {
public static void main(String[] args) throws IOException {
//1.开启ServerSocket
ServerSocket serverSocket=new ServerSocket(9090);
//2.侦听客户端Socket
Socket socket=serverSocket.accept();
//3.获取输入流
InputStream is=socket.getInputStream();
//4.读取接收的文件并保存
FileOutputStream fos=new FileOutputStream(new File("receive.jpg"));
byte[]buffer=new byte[1024];
int len;
while((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//5.关闭资源,应该使用try-catch-finally
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}
kotlin版
fun main() {
fun TcpServer2(){
//1. 开启 ServerSocket
var serverSocket = ServerSocket(9090)
//2. 侦听 客户端 Socket
var socket=serverSocket.accept()
//3. 获取输入流
var iss=socket.getInputStream()
//4. 读取接收的文件并保存
var fos=FileOutputStream(File("2223.jpg"))
var buffer=ByteArray(1024)
var len=0
while (iss.read(buffer).also { len=it }!=-1){
fos.write(buffer,0,len)
}
fos.close()
iss.close()
socket.close()
serverSocket.close()
}
TcpServer2()
}
启动服务端,启动客户端,图片接收成功!
例子3:我们需要在案例二的基础上,接收成功后,返回给客户端,接收成功!然后客户端才关闭连接!
客户端
public class demo {
public static void main(String[] args) throws IOException {
//1.创建socket连接
Socket socket=new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.创建一个输出流
OutputStream os=socket.getOutputStream();
//3.读取文件
FileInputStream fis=new FileInputStream(new File("qinjiang.jpg"));
//4.写出文件
byte[]buffer=new byte[1024];
int len;
while((len=fis.read(buffer))!=-1){
os.write(buffer,0,len);
}
//告诉服务器,我传输完了,关闭数据的输出,不然就会一直阻塞!
socket.shutdownOutput();
//先别着急关,等待服务器响应,响应到控制台,注意重复的变量问题!
InputStream inputStream=socket.getInputStream();
ByteArrayOutputStream baos=new ByteArrayOutputStream();
byte[]buffer2=new byte[1024];
int len2;
while((len2=inputStream.read(buffer2))!=-1){
baos.write(buffer2,0,len2);
}
System.out.println(baos.toString());
//5.资源关闭,应该使用try-catch-finally
baos.close();
inputStream.close();
fis.close();
os.close();
socket.close();
}
}
服务器
public class demo {
public static void main(String[] args) throws IOException {
//1.开启ServerSocket
ServerSocket serverSocket=new ServerSocket(9090);
//2.侦听客户端Socket
Socket socket=serverSocket.accept();
//3.获取输入流
InputStream is=socket.getInputStream();
//4.读取接收的文件并保存
FileOutputStream fos=new FileOutputStream(new
File("receive2.jpg"));
byte[]buffer=new byte[1024];
int len;
while((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//通知客户端接收成功
OutputStream outputStream=socket.getOutputStream();
outputStream.write("文件已经成功收到,OK".getBytes());
//5.关闭资源,应该使用try-catch-finally
outputStream.close();
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}
UDP网络编程
例子1:接收,发送
发送方
public class demo {
public static void main(String[] args) throws IOException {
//1.建立DatagramSocket
DatagramSocket socket=new DatagramSocket();
//2.封装数据包
String msg="UDPSender==>";
byte[]data=msg.getBytes();
InetAddress inet=InetAddress.getByName("127.0.0.1");
int port=9090;
DatagramPacket packet=new DatagramPacket(data,0,data.length,inet,port);
//3.通过Socket发送packet
socket.send(packet);
//4.关闭socket
socket.close();
}
}
接收方
public class demo {
public static void main(String[] args) throws IOException {
//1.建立DatagramSocket,开放端口
DatagramSocket socket=new DatagramSocket(9090);
//2.接收数据
byte[]buffer=new byte[1024];
DatagramPacket packet=new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
//3.输出数据
//packet.getData():获取packet中的数据
System.out.println(new String(packet.getData(),0, packet.getLength()));
//4.关闭socket
socket.close();
}
}
例子2:在线咨询
客户端
public class demo {
public static void main(String[] args) throws IOException {
System.out.println("发送方启动中....");
//1.使用DatagramSocket指定端口,创建发送端
DatagramSocket socket=new DatagramSocket(8888);
//2.准备数据,转成字节数组
BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
while(true){
String data=reader.readLine();
byte[]datas=data.getBytes();
//3.封装成DatagramPacket包裹,需要指定目的地
DatagramPacket packet=new DatagramPacket(datas,0,datas.length, new InetSocketAddress("localhost",6666));
//4.发送包裹send
socket.send(packet);
//退出判断
if(data.equals("bye")){
break;
}
}
//5.释放资源
socket.close();
}
}
服务端
public class demo {
public static void main(String[] args) throws IOException {
DatagramSocket socket=new DatagramSocket(6666);
while(true){
try{
//准备接收包裹;
byte[]container=new byte[1024];
DatagramPacket packet=new DatagramPacket(container,0,
container.length);
socket.receive(packet);//阻塞式接收包裹
byte[]datas=packet.getData();
int len=packet.getLength();
String data=new String(datas,0,len);
System.out.println(data);
//退出判断
if(data.equals("bye")){
break;
}
}catch(Exception e){
e.printStackTrace();
}
}
socket.close();
}
}
问题:现在需要两遍需要接受和发送,我们可以使用多线程来解决!
发送端多线程
public class TalkSend implements Runnable {
private DatagramSocket socket;
private BufferedReader reader;
private String toIP;
private int toPort;
public TalkSend (int port, String toIP, int toPort) {
this.toIP = toIP;
this.toPort = toPort;
try {
socket = new DatagramSocket(port);
reader = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
String data = reader.readLine();
byte[] datas = data.getBytes();
//3.封装成DatagramPacket包裹,需要指定目的地
DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress(this.toIP, this.toPort));
//4.发送包裹send
socket.send(packet);
//退出判断
if (data.equals("bye")) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
//5.释放资源
socket.close();
}
}
接收端多线程
public class TalkReceive implements Runnable {
private DatagramSocket socket;
private String msgFrom;
public TalkReceive (int port,String msgFrom){
this.msgFrom=msgFrom;
try{
socket=new DatagramSocket(port);
}catch(SocketException e){
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
//准备接收包裹;
byte[] container = new byte[1024];
DatagramPacket packet = new DatagramPacket(container, 0,
container.length);
socket.receive(packet);//阻塞式接收包裹
byte[] datas = packet.getData();
int len = packet.getLength();
String data = new String(datas, 0, len);
System.out.println(msgFrom + ":" + data);
//退出判断
if (data.equals("bye")) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
socket.close();
}
}
学生端
public class TalkStudent{
public static void main(String[] args) {
new Thread(new TalkSend(7777,"localhost",9999)).start();
new Thread(new TalkReceive(8888,"老师")).start();
}
}
老师端
public class TalkTeacher{
public static void main(String[] args) {
new Thread(new TalkReceive(9999,"学生")).start();
new Thread(new TalkSend(5555,"localhost",8888)).start();
}
}
URL编程
统一资源定位符,它表示internet 上某一资源的地址。
实例化
public class demo{
public static void main(String[] args) {
try{
URL url=new URL("http://localhost:8080/helloworld/index.jsp? username=kuangshen&password=123");
System.out.println(url.getProtocol());//获取URL的协议名
System.out.println(url.getHost());//获取URL的主机名
System.out.println(url.getPort());//获取URL的端口号
System.out.println(url.getPath());//获取URL的文件路径
System.out.println(url.getFile());//获取URL的文件名
System.out.println(url.getQuery());//获取URL的查询名
}catch(MalformedURLException e){
e.printStackTrace();
}
}
}
例子1:下载tomcat下的文件
public class demo{
public static void main(String[] args) {
try{
//1.定位到服务器端的资源
URL url=new URL("http://localhost:8080/helloworld/qinjiang.jpg");
//2.创建连接
HttpURLConnection connection=(HttpURLConnection) url.openConnection();
//3.获取输入流
InputStream is=connection.getInputStream();
//4.写出文件
FileOutputStream fos=new FileOutputStream("qinjiang2.jpg");
byte[]buffer=new byte[1024];
int len;
while((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//关闭资源
fos.close();
is.close();
connection.disconnect();//断开连接
}catch(Exception e){
e.printStackTrace();
}
}
}