首页 > 其他分享 >16【TCP、UDP、网络通信】

16【TCP、UDP、网络通信】

时间:2022-12-21 11:31:28浏览次数:43  
标签:UDP java 16 TCP import new public 客户端



上一篇:​​15【IO流增强】​​



下一篇:​​17【测试单元、反射、注解、Lombok插件】​​

目录:​​【JavaSE零基础系列教程目录】​​


文章目录

  • ​​16【TCP、UDP、网络通信】​​
  • ​​一、基于网络编程​​
  • ​​1.1 网络编程概述​​
  • ​​1.2 网络分层​​
  • ​​1.2.1 OSI参考模型​​
  • ​​1.2.2 TCP/IP参考模型​​
  • ​​1.3 网络名词​​
  • ​​1.3.1 IP地址​​
  • ​​1.3.2 端口号​​
  • ​​1.4 网络协议​​
  • ​​1.4.1 UDP​​
  • ​​1)UDP传输过程​​
  • ​​2)UDP报文格式​​
  • ​​3)UDP总结​​
  • ​​1.4.2 TCP​​
  • ​​1)TCP报文格式​​
  • ​​2)三次握手​​
  • ​​3)四次挥手​​
  • ​​4)TCP总结​​
  • ​​二、Java实现UDP应用程序​​
  • ​​2.1 InetAddress类​​
  • ​​2.2 DatagramPacket类​​
  • ​​2.2.1 构造方法​​
  • ​​2.2.2 常用方法​​
  • ​​2.2 DatagramSocket类​​
  • ​​2.2.1 构造方法​​
  • ​​2.2.2 常用方法​​
  • ​​2.3 UDP实现数据发送与接收​​
  • ​​三、Java实现TCP程序​​
  • ​​2.1 Socket​​
  • ​​2.1.1 构造方法​​
  • ​​2.1.2 成员方法​​
  • ​​2.2 ServerSocket​​
  • ​​2.2.1 构造方法​​
  • ​​2.2.2 成员方法​​
  • ​​2.3 设计TCP应用程序​​
  • ​​四、综合案例​​
  • ​​4.1 TCP在线聊天案例​​
  • ​​4.2 TCP图片上传案例​​
  • ​​4.2.1 简单图片上传​​
  • ​​4.2.2 使用多线程改进​​

16【TCP、UDP、网络通信】

一、基于网络编程

1.1 网络编程概述

计算机网络是通过传输介质(网线)、通信设施(路由器、交换机等)和网络通信协议,把分散在不同地点的计算机设备互连起来的,用来实现数据共享。

网络编程就是编写程序使互联网的多个设备(如计算机)之间进行数据传输。Java语言对网络编程提供了良好的支持。通过其提供的接口我们可以很方便地进行网络编程。

1.2 网络分层

通过网络发送数据是一项复杂的操作,通过网络将数据从一台主机发送到另外的主机,这个过程是通过计算机网络通信来完成。

网络通信的不同方面被分解为多个层,层与层之间用接口连接。通信的双方具有相同的层次,层次实现的功能由协议数据单元来描述。不同系统中的同一层构成对等层,对等层之间通过对等层协议进行通信,理解批次定义好的规则和约定。将网络分层,这样就可以修改甚至替换某一层的软件,只要层与层之间的接口保持不变,就不会影响到其他层。

16【TCP、UDP、网络通信】_网络编程

1.2.1 OSI参考模型

世界上第一个网络体系结构在1974年由IBM公司提出,名为SNA。以后其他公司也相继提出自己的网络体系结构。为了促进计算机网络的发展,国际标准化组织ISO在现有网络的基础上,提出了不基于具体机型、操作系统或公司的网络体系结构,称为开放系统互连参考模型,即OSI/RM(Open System Interconnection Reference Model)。

16【TCP、UDP、网络通信】_网络编程_02

  • 物理层:为数据端设备提供原始比特流的传输的通路;网络通信的数据传输介质,由电缆与设备共同构成。常见:中继器,集线器(HUB)、网线、RJ-45标准等
  • 数据链路层:在通信的实体间建立数据链路连接;将数据分帧,并处理流控制、物理地址寻址、重发等。常见:网卡,网桥,二层交换机等
  • 网络层:为数据在结点之间传输创建逻辑链路,并分组转发数据;对子网间的数据包进行路由选择。常见:路由器、多层交换机、防火墙、IP、IPX、RIP、OSPF
  • 传输层:提供应用进程之间的逻辑通信;建立连接,处理数据包错误、数据包次序。常见:TCP、UDP、SPX、进程、端口(socket)
  • 会话层:建立端连接并提供访问验证和会话管理;为数据在结点之间传输创建逻辑链路,并分组转发数据;使用校验点可使会话在通信失效时从校验点恢复通信。常见:服务器验证用户登录、断点续传
  • 表示层:提供数据格式转换服务;解密与加密,图片解码和编码、数据的压缩和解压缩。常见:URL加密、口令加密、图片编解码
  • 应用层:访问网络服务的接口;为操作系统或网络应用程序提供访问网络服务的接口。常见:Telnet、FTP、 HTTP、 SNMP、DNS等

1.2.2 TCP/IP参考模型

TCP/IP,即Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,是Internet最基本的协议,Internet国际互联网络的基础。

TCP/IP协议是一个开放的网络协议簇,它的名字主要取自最重要的网络层IP协议和传输层TCP协议。TCP/IP协议定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。TCP/IP参考模型采用4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求,这4个层次分别是:网络接口层、网络层(IP层)、传输层(TCP层)、应用层。

16【TCP、UDP、网络通信】_网络编程_03

1.3 网络名词

1.3.1 IP地址

IP(Internet Protocol Address):全称互联网协议地址,简称IP地址;IP地址用于标识互联网上的唯一一台机器,互联网就是通过IP地址锁定到我们的这台电脑,相当于家庭地址;

IP地址的分类

  • ​IPv4​​​:是一个32位的二进制数,通常被分为4个字节,表示成​​a.b.c.d​​​ 的形式,例如​​192.168.65.100​​ 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。
  • ​IPv6​​​:由于互联网的网民日益增多,IPv4的IP地址资源有限。为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成​​ABCD:EF01:2345:6789:ABCD:EF01:2345:6789​​,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

16【TCP、UDP、网络通信】_三次握手_04

  • 查看本机IP地址,在cmd控制台输入:
ipconfig
  • 检查网络是否连通,在cmd控制台输入:
ping 空格 IP地址
ping 163.177.151.109

tips:在每台计算机出厂时,都有一个用于标识自己电脑的地址:​​127.0.0.1​​​,和一个本机域名:​​localhost​

1.3.2 端口号

在两台计算机通信时,更准确的来说是两台计算机的某个进程(应用程序)在通信,IP地址可以唯一标识网络中的设备,那么端口号就是唯一标识计算机中的某个应用程序了;

tips:端口号的取值范围为065536。其中01023之间的端口号用于计算机内置的一些进程,我们自己的程序的端口号尽量设置在1023以上的端口,保证端口不会占用冲突;

1.4 网络协议

如同人与人之间相互交流是需要遵循一定的规则(如语言)一样,计算机之间能够进行相互通信是因为它们都共同遵守一定的规则,即网络协议。

OSI参考模型和TCP/IP模型在不同的层次中有许多不同的网络协议,如图所示:

16【TCP、UDP、网络通信】_Socket_05

我们今天主要讨论的是传输层的协议,即考虑应用程序之间的逻辑通信。简单来说就是数据该如何发送给其他机器;

1.4.1 UDP

UDP(User Datagram Protocol):用户数据报协议;UDP是面向无连接的通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

1)UDP传输过程

UDP是面向报文传递数据的;在UDP传输过程中,分别为发送端和接受端;

发送端使用UDP发送数据时,首先将其包裹成一个UDP报文(包含数据与首部格式)通过网络将其发送给接收端;接受端接收到UDP报文后,首先去掉其首部,将数据部分交给应用程序进行解析;

需要注意的是,UDP不保证数据传递的可靠性,在传递过程中可能出现丢包等情况,另外,即使接收方不存在报文依旧被发送出去(丢包)。但正是因为UDP不需要花费额外的资源来建立可靠的连接,因此UDP传输速度快,资源消耗小

16【TCP、UDP、网络通信】_网络编程_06

2)UDP报文格式

一个完整的UDP报文包含首部和载荷(数据)两部分,首部由 4 个 16 位(2 字节)长的字段,共8个字节组成,分别说明该报文的源端口、目的端口、报文长度和校验值。

16【TCP、UDP、网络通信】_网络编程_07

UDP 报文中每个字段的含义如下:

  • 源端口:发送端所使用应用程序的UDP端口,接受端的应用程序理由这个字段的值作为响应的目的地址;这个字段是可选的,所以发送端的应用程序不一定会把自己的端口号写入该字段中。如果不写入端口号,则把这个字段设置为 0。这样,接收端的应用程序就不能发送响应了。
  • 目的端口:接收端计算机上 UDP 软件使用的端口。
  • 长度:表示 UDP 数据报长度,单位:字节;包含 UDP 报文头+UDP 数据长度。因为 UDP 报文头长度是 8 个字节,所以这个值最小为 8。
  • 校验值:可以检验数据在传输过程中是否被损坏。
3)UDP总结

由于使用UDP协议消耗资源小,通信效率高;因此一般用于实时性要求比较高的场合如:音频、视频的传输等;例如视频会议都使用UDP协议,如果出现了网络丢包情况也只是造成卡帧现象,对整体影响不大

但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。

1.4.2 TCP

TCP(Transmission Control Protocol):传输控制协议;TCP协议是面向连接的通信协议;即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。

在TCP连接中必须要明确客户端(发送端)与服务器端(接收端),由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”;

16【TCP、UDP、网络通信】_Socket_08

1)TCP报文格式

一个完整的TCP报文同样也是由首部和数据载荷组成;TCP的全部功能都体现在它首部中各字段的作用。

16【TCP、UDP、网络通信】_UDP_09

  • 源端口:占2个字节,16个比特;表示发送该报文的应用程序端口号,用于接收端的响应;
  • 目的端口号:占2个字节,16个比特;标识接受该TCP报文的应用程序端口号;
  • 序号:数据载荷中的数据都是有顺序的,序号用于标识发送端向接收端发送的数据字节流的位置;

16【TCP、UDP、网络通信】_TCP_10

序号占4个字节,32个比特位,取值范围​​2^32-1​​,序号增加到最后一个时,下一个序列号又回到0;

  • 确认号:期望收到对方下一个TCP报文序号的起始位置,同时也是对之前收到的数据进行确认;

确认号和序号一样,占4个字节,32个比特位,取值范围​​2^32-1​​,确认号增加到最后一个时,下一个确认号又回到0;

A向B发送数据:

16【TCP、UDP、网络通信】_TCP_11

B向A发送数据:

16【TCP、UDP、网络通信】_TCP_12

若确认号=n,则表明,序号n-1为止的所有数据都已正确接收,期望接收序号为n的数据;

  • 本次的序列号:上次的确认号
  • 本次的确认号:上次的序列号+1
  • 数据偏移:占4个比特,用来指出数据载荷部分的起始处距离报文的起始处有多远;也就是TCP首部的长度。需要注意的是数据偏移以4个字节为单位;

如图:

16【TCP、UDP、网络通信】_Socket_13

  • 保留字段:占6个比特,保留为今后使用,但目前为0;
  • 窗口: 占2个字节,16个比特;用于流量控制和拥塞控制,表示当前接收缓冲区的大小。在计算机网络中,通常是用接收方的接收能力的大小来控制发送方的数据发送量。TCP连接的一端根据缓冲区大小确定自己的接收窗口值,告诉对方,使对方可以确定发送数据的字节数。
  • 校验和:占2个字节,16比特;检查报文的首部和数据载荷两部分,底层依赖于具体的校验算法;
  • 紧急指针:占2个字节,16比特;用来指明紧急数据的长度;当发送端有紧急数据时,可将紧急数据插队到发送缓存的最前面,并立刻封装到一个TCP报文段中进行发送。紧急指针会指出本报文段数据载荷部分包含了多长的紧急数据,紧急数据之后是普通数据
  • 选项:附加一些额外的首部信息;
  • 填充:由于选项的长度可变,因此用来填充的确认报文首部能被4整除(因为数据偏移字段,也就是首部长度字段,是以4字节为单位的);
  • 标志位
  • ACK(确认):取值为1时确认号字段才有效;取值为0时确认号字段无效,一般情况下都为1;
  • SYN(同步):在连接建立时用来同步序号
  • FIN(终止):为1时表明发送端数据发送完毕要求释放连接
  • RST(复位):用于复位TCP连接,值为1时说明连接出现了异常,必须释放连接,然后再重新建立连接,有时RST置1还用来拒绝一个非法的报文段或拒绝打开一个TCP连接;
  • PSH(推送):为1时接收方应尽快将这个报文交给应用层,而不必等到接受缓存都填满后再向上交付
  • URG(紧急):为1时表明紧急指针字段有效,取值为0时紧急字段无效;
2)三次握手

由于TCP是基于可靠通信的,在发送数据之前必须建立可靠的连接;TCP建立连接的过程分为三个步骤,我们称为"三次握手";

简单的过程如下图所示:

  • 1)第一次握手:发送端向接收端端发出连接请求,等待接受的响应。
  • 2)第二次握手,接收端向发送端响应,通知发送端收到了连接请求。
  • 3)第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。

16【TCP、UDP、网络通信】_Socket_14

我们结合TCP报文原理来具体分析一下三次握手的详细流程:

原理图如下:

16【TCP、UDP、网络通信】_UDP_15

  • 1)第一次握手:首先A使用TCP连接向B发送报文(此报文不携带载荷,只有首部),报文中的SYN记为1(如果发送失败,重新发送记为2),序号记为x
  • 2)第二次握手:B接受到A发送的报文后,给A发送一个确认报文(该报文也仅有首部),报文中SYN和ACK都取值为1,用于标志这是一个确认报文段;同时此次响应的序号记为y,确认号为上一次序号x加1的值,表示已经成功接收到x+1之前的所有内容,下一次开始接受x+1之后的数据(包含x+1的数据);
  • 3)第三次握手:A再次向B发送TCP确认,序号为x+1,代表发送序号x之后的数据;确认号为y+1,代表已经接收了来自B的响应的全部数据;
3)四次挥手

TCP建立连接时需要"三次握手",断开连接时则需要"四次挥手";

原理图如下:

16【TCP、UDP、网络通信】_Socket_16

  • 1)A向B发送连接释放请求,标记FIN=1(表示需要断开连接);序号记为u(u为之前已发送的最后一个字节数据的序号+1),确认号记为v(v为之前已接受的最后一个字节数据的序号+1)
  • 2)B收到A发送的断开连接请求后开始响应,标记序号为v,确认号为u+1(代表已经成功接收A发送的数据,接下来开始接受u+1及之后序号的数据)
  • 3)主机B如果仍有数据需要传输依旧可以传输,如果B没有数据需要发送时,需给A发送连接释放报文。标记FIN=1,序号为w(同理,此时w是B之前已发送最后一个字节数的序号+1),确认号为u+1;
  • 4)A接收到B发送的连接释放报文后,最终给B发送一个TCP确认报文,代表确定接受到B需要连接断开。序号为u+1,确认号为w+1(代表B发送的数据以及全部接受完毕)
4)TCP总结

使用TCP协议传输数据时,必须要建立可靠连接(三次握手),当连接关闭时还需要四次挥手,对性能损耗较大,如果频繁的创建和关闭TCP连接性能势必会有很大影响。但是由于TCP的可靠传输,对一些数据完整性要求较高的场合比较适用,如文件上传下载等;

二、Java实现UDP应用程序

2.1 InetAddress类

​java.net.InteAddress​​类是用于描述IP地址和域名的一个Java类;

常用方法如下:

  • ​public static InetAddress getByName(String host)​​:根据主机名获取InetAddress对象
  • ​public String getHostName()​​:获取该对象对应的主机名
  • ​public String getHostAddress()​​获取该对象对应的IP地址

示例代码:

package com.dfbz.demo01;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) throws UnknownHostException {

// 根据域名获取InetAddress对象(获取本机对象)
InetAddress t1 = InetAddress.getByName("localhost");
System.out.println(t1.getHostName()); // localhost
System.out.println(t1.getHostAddress()); // 127.0.0.1

System.out.println("------------------------");
// 根据域名获取InetAddress对象(获取百度的InetAddress对象)
InetAddress t2 = InetAddress.getByName("www.baidu.com");
System.out.println(t2.getHostName()); // www.baidu.com
System.out.println(t2.getHostAddress()); // 163.177.151.109
}
}

2.2 DatagramPacket类

​java.net.DatagramPacket​​类用于封装一个UDP数据报文

2.2.1 构造方法

  • ​public DatagramPacket(byte[] buf, int length, InetAddress address, int port)​​:创建一个数据包对象
  • ​buf​​:要发送的内容
  • ​length​​:要发送的内容⻓度,单位字节
  • ​address​​:接收端的ip地址
  • ​port​​:接收端⼝号
  • ​public DatagramPacket(byte buf[], int length)​​:创建一个数据包对象

示例代码:

package com.dfbz.demo01;

import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) throws UnknownHostException {
byte[] data1 = "hello~".getBytes();
DatagramPacket packet1 = new DatagramPacket(data1, data1.length);

byte[] data2 = "你好".getBytes();
InetAddress address = InetAddress.getByName("localhost");
DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, 6868);
}
}

2.2.2 常用方法

  • ​public synchronized int getLength()​​:获取此UDP数据包载荷的数据长度(单位字节)
  • ​public synchronized int getPort()​​:获取此UDP数据包的目的端口号
  • ​public synchronized byte[] getData() ​​:获取此UDP数据包的载荷部分(数据)

示例代码:

package com.dfbz.demo01;

import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
public static void main(String[] args) throws UnknownHostException {
byte[] data = "hello".getBytes();
DatagramPacket packet = new DatagramPacket(
data,
data.length,
InetAddress.getByName("localhost"),
6868
);

System.out.println(packet.getLength()); // 5
System.out.println(packet.getPort()); // 6868
System.out.println(new String(packet.getData())); // hello
}
}

2.2 DatagramSocket类

​java.net.DatagramSocket​​类用于描述一个UDP发送端或接收端;

2.2.1 构造方法

  • ​public DatagramSocket(int port)​​:通过端口构建一个发送端/接收端

示例代码:

DatagramSocket socket = new DatagramSocket(6969);

2.2.2 常用方法

  • ​public void send(DatagramPacket p)​​:发送一个UDP数据包
  • ​public synchronized void receive(DatagramPacket p)​​:接收一个UDP数据包
  • ​public void close()​​:释放该Socket占用的资源

2.3 UDP实现数据发送与接收

  • 发送端:
package com.dfbz.demo01;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
* @author lscl
* @version 1.0
* @intro: 发送端
*/
public class Demo04 {
public static void main(String[] args) throws Exception {
String str = "你好";

// 准备一个UDP数据包
DatagramPacket packet = new DatagramPacket(
str.getBytes(),
str.getBytes().length,
InetAddress.getLocalHost(),
6868);

// 套接字
DatagramSocket socket = new DatagramSocket();

// 发送数据包
socket.send(packet);
socket.close();
}
}
  • 接收端:
package com.dfbz.demo01;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
* @author lscl
* @version 1.0
* @intro: 接收端
*/
public class Demo05 {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(666);

byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);

//接受数据字节的长度
System.out.println("接收端端:");

// 接受一个UDP数据包
socket.receive(packet);

int len = packet.getLength();
System.out.println("已经接收到:" + len + "个字节");

// 转换为字符串打印
System.out.println(new String(bytes, 0, len));
socket.close();
}
}

三、Java实现TCP程序

在TCP通信中,分为数据的发送端(客户端)和接收端(服务器),当建立连接成功后(三次握手),才可以进行数据的发送;

在Java中,提供了两个类用于实现TCP通信程序:

  • 1)客户端:​​java.net.Socket​​ 类表示;用于与服务器端建立连接,向服务器端发送数据报文等;
  • 2)服务端:​​java.net.ServerSocket​​ 类表示;用于与客户端的交互;

2.1 Socket

​java.net.Sokcet​​用于封装一个TCP应用程序的客户端;

2.1.1 构造方法

  • ​public Socket(String host, int port)​​ :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为本机地址。

示例代码:

Socket client = new Socket("127.0.0.1", 6868);

2.1.2 成员方法

  • ​public InputStream getInputStream()​​ : 返回此套接字的输入流。关闭生成的InputStream也将关闭相关的Socket。
  • ​public OutputStream getOutputStream()​​ : 返回此套接字的输出流。关闭生成的OutputStream也将关闭相关的Socket。
  • ​public void close()​​ :关闭此套接字。关闭此socket也将关闭相关的InputStream和OutputStream 。
  • ​public void shutdownOutput()​​ : 禁用此套接字的输出流。任何先前写出的数据将被发送,随后终止输出流。

2.2 ServerSocket

​ServerSocket​​类:这个类实现了服务器套接字,该对象等待通过网络的请求。

2.2.1 构造方法

  • ​public ServerSocket(int port)​​ :使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。

构造举例,代码如下:

ServerSocket server = new ServerSocket(6666);

2.2.2 成员方法

  • ​public Socket accept()​​ :监听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。

2.3 设计TCP应用程序

  • 客户端代码:
package com.dfbz.demo01;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
* @author lscl
* @version 1.0
* @intro: 客户端程序
*/
public class Demo01_Client {
public static void main(String[] args) throws IOException {
// 创建一个客户端,指定要连接服务器的IP与端口
Socket socket = new Socket("127.0.0.1", 6969);

// 获取与此服务器的输入流(用于读取该服务器的数据)
InputStream is = socket.getInputStream();

// 获取与此服务器的输出流(用于向该服务器发送数据)
OutputStream os = socket.getOutputStream();

// 向服务器发送数据
os.write("在吗?".getBytes());

// 准备一个字节数组用于接收数据
byte[] bytes = new byte[1024];

// 读取服务器发送过来的数据(若服务器一直未发送数据,则程序阻塞在此)
int len = is.read(bytes);

System.out.println(new String(bytes, 0, len));

// 向服务器写出数据
os.write("买糖".getBytes());

// 读取服务器的数据
len = is.read(bytes);

System.out.println(new String(bytes, 0, len));

// 释放连接资源
socket.close();
}
}
  • 服务端代码:
package com.dfbz.demo01;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author lscl
* @version 1.0
* @intro: 服务端程序
*/
public class Demo02_Server {
public static void main(String[] args) throws IOException {

// 创建一个服务器,并指定该TCP程序的端口(IP地址就是本机)
ServerSocket serverSocket=new ServerSocket(6969);

System.out.println("等待客户端: ");

// 接收一个客户端(若没有客户端来连接服务器,则程序阻塞在此)
Socket client = serverSocket.accept();

// 获取与客户端的输入流(用于读取客户端的数据)
InputStream is = client.getInputStream();

// 获取与客户端的输出流(用于向客户端发送数据)
OutputStream os = client.getOutputStream();

byte[] bytes=new byte[1024];

// 读取客户端发送过来的数据
int len = is.read(bytes);

System.out.println(new String(bytes,0,len));

// 向客户端写出数据
os.write("在哦!亲~,想买点什么?".getBytes());

// 读取客户端的数据
len = is.read(bytes);

System.out.println(new String(bytes,0,len));

// 向客户端写出数据
os.write("糖没了,不卖!".getBytes());

client.close();
serverSocket.close();
}
}

上述程序中,发送内容都是写死在代码中,我们使用Scanner来接受键盘录入的数据进行发送;

  • 改造客户端程序:
package com.dfbz.demo02;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
* @author lscl
* @version 1.0
* @intro: 客户端程序
*/
public class Demo01_Client {
public static void main(String[] args) throws IOException {
// 创建一个客户端,指定要连接服务器的IP与端口
Socket socket = new Socket("127.0.0.1", 6969);

// 获取与此服务器的输入流(用于读取该服务器的数据)
InputStream is = socket.getInputStream();

// 获取与此服务器的输出流(用于向该服务器发送数据)
OutputStream os = socket.getOutputStream();

// 获取scanner扫描器
Scanner scanner = new Scanner(System.in);

// 死循环进行发送
while (true) {

System.out.println("请输入要发送给服务器的信息: ");
String sendInfo = scanner.next();

// 如果输入end代表程序结束
if ("end".equals(sendInfo)) {
System.out.println("程序结束...");
break;
}
// 向服务器发送数据
os.write(sendInfo.getBytes());

// 准备一个字节数组用于接收数据
byte[] bytes = new byte[1024];

// 读取服务器发送过来的数据(若服务器一直未发送数据,则程序阻塞在此)
int len = is.read(bytes);
System.out.println("接收到来自服务器" + socket.getInetAddress().getHostAddress() + "的信息: " + new String(bytes, 0, len));
}

// 释放连接资源
socket.close();
}
}
  • 改造服务端程序:
package com.dfbz.demo02;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
* @author lscl
* @version 1.0
* @intro: 服务端程序
*/
public class Demo02_Server {
public static void main(String[] args) throws IOException {

// 创建一个服务器,并指定该TCP程序的端口(IP地址就是本机)
ServerSocket serverSocket = new ServerSocket(6969);

System.out.println("等待客户端: ");

// 接收一个客户端(若没有客户端来连接服务器,则程序阻塞在此)
Socket client = serverSocket.accept();

// 获取与客户端的输入流(用于读取客户端的数据)
InputStream is = client.getInputStream();

// 获取与客户端的输出流(用于向客户端发送数据)
OutputStream os = client.getOutputStream();

Scanner scanner = new Scanner(System.in);

while (true) {
byte[] bytes = new byte[1024];

// 读取客户端发送过来的数据
int len = is.read(bytes);

String receiveInfo = new String(bytes, 0, len);

// 如果客户端发送过来的是end则程序结束
if ("end".equals(receiveInfo)) {
System.out.println("程序结束...");
break;
}
System.out.println("接受到来自客户端" + client.getInetAddress().getHostAddress() + "的信息: " + receiveInfo);

String sendInfo = scanner.next();
// 向客户端写出数据
os.write(sendInfo.getBytes());
}
client.close();
serverSocket.close();
}
}

四、综合案例

4.1 TCP在线聊天案例

我们刚刚使用了TCP完成了聊天功能的编写;我们会发现我们的程序是由问题的,就是读写是串行的!

我们整个应用程序只有一个线程,那就mian线程,代码都是从上往下执行,如果main线程当前在读操作,那么就不能写。而且如果此时一方如果没有发送信息给另一方,那么另一方的read方法将会一直处于阻塞状态,代码不会往下执行;此时想往对方写出数据肯定是不行的;

16【TCP、UDP、网络通信】_TCP_17

我们利用多线程技术来改造我们之前的代码,让我们的代码既可以一直读,又可以一直写;

  • 多线程改进客户端:
package com.dfbz.demo01;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
* @author lscl
* @version 1.0
* @intro: 多线程实现客户端
*/
public class Demo01_Client {

public static void main(String[] args) throws Exception {
// 获取到一个新的客户端
Socket socket = new Socket("127.0.0.1", 6969);

// 读取客户端数据
InputStream is = socket.getInputStream();

// 缓冲字符输入流(读取客户端数据更加方便)
BufferedReader br = new BufferedReader(new InputStreamReader(is));

// 读线程
new Thread() {
@Override
public void run() {
try {
String info;
while (true) {

// 死循环读取客户端的信息
info = br.readLine();

if (info.equals("end")) {

socket.close();

// 退出应用程序
System.exit(0);
System.out.println("bye bye....");
break;
} else {
System.out.println("接收到: " + socket.getInetAddress().getHostAddress() + "来自的消息: " + info);
}
}

} catch (IOException e) {
e.printStackTrace();
}
}
}.start();


// 给客户端写回数据
OutputStream os = socket.getOutputStream();

// 缓冲字符输出流(往客户端写数据更加方便)
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));

// 写线程
new Thread() {
@Override
public void run() {
try {

Scanner scanner = new Scanner(System.in);
String info;

while (true) {
// 获取键盘输入的信息
info = scanner.nextLine();

// 往客户端写数据
bw.write(info);
bw.newLine();
bw.flush();
}

} catch (IOException e) {
e.printStackTrace();
}

}
}.start();
}

}
  • 多线程改进服务器:
package com.dfbz.demo01;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
* @author lscl
* @version 1.0
* @intro: 多线程实现服务器
*/
public class Demo02_Server {
public static void main(String[] args) throws Exception {
// 创建一台服务器
ServerSocket serverSocket = new ServerSocket(6969);

while (true) {
// 获取到一个新的客户端
Socket socket = serverSocket.accept();

System.out.println(socket.getInetAddress().getHostAddress() + "已与您连接成功");

// 读取客户端数据
InputStream is = socket.getInputStream();

// 缓冲字符输入流(读取客户端数据更加方便)
BufferedReader br = new BufferedReader(new InputStreamReader(is));

// 读线程
new Thread() {
@Override
public void run() {
try {
String info;
while (true) {

// 死循环读取客户端的信息
info = br.readLine();

if (info.equals("end")) {

// 关闭与客户端的连接
socket.close();
System.out.println(socket.getInetAddress().getHostAddress() + "已与您断开连接: ");
break;
} else {
System.out.println("接收到: " + socket.getInetAddress().getHostAddress() + "来自的消息: " + info);
}
}

} catch (IOException e) {
e.printStackTrace();
}
}
}.start();


// 给客户端写回数据
OutputStream os = socket.getOutputStream();

// 缓冲字符输出流(往客户端写数据更加方便)
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));

// 写线程
new Thread() {
@Override
public void run() {
try {

Scanner scanner = new Scanner(System.in);
String info;

while (true) {
// 获取键盘输入的信息
info = scanner.nextLine();

// 往客户端写数据
bw.write(info);
bw.newLine();
bw.flush();
}

} catch (IOException e) {
e.printStackTrace();
}

}
}.start();
}

}
}

4.2 TCP图片上传案例

4.2.1 简单图片上传

图片上传流程:

16【TCP、UDP、网络通信】_Socket_18

1)客户端首先通过输入流将自己磁盘中的图片读取到内存中

2)客户端通过TCP连接的输出流,向服务器写出刚刚读取到的图片数据

3)服务器通过TCP连接的输入流,将客户端刚刚发送过来的图片数据读取到内存中

4)服务器通过输出流将内存中的数据写入到服务器的磁盘中

tips:我们学习过程中,将服务器和客户端放在同一台机器。但实际开发中服务器和客户端不是在同一台机器,

图片上传代码实现:

  • 服务器代码实现:
package com.dfbz.demo02;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;

/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_Server {
public static void main(String[] args) throws Exception {

// 声明服务器
ServerSocket serverSocket = new ServerSocket(8888);

// 接收到一个客户端
Socket client = serverSocket.accept();

System.out.println(client.getInetAddress().getHostAddress() + "连接成功");

// 读取客户端传递过来的数据
InputStream is = client.getInputStream();

// 随机生成一个文件名写出到磁盘
FileOutputStream fos = new FileOutputStream(UUID.randomUUID().toString() + ".png");

byte[] data = new byte[1024];

int len;
while ((len = is.read(data)) != -1) {

// 写出到磁盘
fos.write(data, 0, len);
}

// 释放资源
fos.close();
client.close();
}
}
  • 客户端代码实现:
package com.dfbz.demo02;

import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_Client {
public static void main(String[] args) throws Exception{
Socket socket = new Socket("localhost", 8888);

// 读取磁盘中的图片
FileInputStream fis = new FileInputStream("C:\\Users\\Horizon\\Desktop\\001.png");

// 往服务器传递数据
OutputStream os = socket.getOutputStream();

byte[] data=new byte[1024];

int len;

while ((len=fis.read(data))!=-1){
// 写出到服务器端
os.write(data,0,len);
}

fis.close();
socket.close();
}
}

4.2.2 使用多线程改进

实际开发中一个服务器对应N多个客户端,其他客户端均可以上传图片。我们的代码在同一时间只允许一个人上传图片,如果这个人上传的文件较大,那么势必会造成其他用户处于等待状态;针对这种情况我们可以使用多线程来解决。

服务器每次接受到一个客户端时,都开启一个线程来独立处理这个客户端的上传任务。这样在很多人同时来上传文件时,都可以一起上传。

  • 多线程改进服务器:
package com.dfbz.demo03;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author lscl
* @version 1.0
* @intro: 多线程改进图片上传服务器
*/
public class Demo01_Server {
public static void main(String[] args) throws Exception {

// 声明服务器
ServerSocket serverSocket = new ServerSocket(8888);

while (true) {
// 接收到一个客户端
Socket socket = serverSocket.accept();

// 每接收到一个客户端都开启一个独立的线程为该客户端提供服务
new Thread(){
@Override
public void run() {
System.out.println(socket.getInetAddress().getHostAddress() + "连接成功");

try {
// 读取客户端传递过来的数据
InputStream is = socket.getInputStream();

// 写出到文件
FileOutputStream fos = new FileOutputStream(UUID.randomUUID().toString() + ".png");

byte[] data = new byte[1024];

int len;
while ((len = is.read(data)) != -1) {

// 写出到磁盘
fos.write(data, 0, len);
}

System.out.println("文件上传成功!");
// 释放资源
fos.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
}

上一篇:​​15【转换流、缓冲流、序列流、打印流】​​



下一篇:​​17【测试单元、反射、注解、Lombok插件】​​

目录:​​【JavaSE零基础系列教程目录】​​



标签:UDP,java,16,TCP,import,new,public,客户端
From: https://blog.51cto.com/u_15919174/5959203

相关文章

  • 基于Socket编程,模拟TCP部分协议字段编程
    注意:先启动服务端,再运行客户端Client.java客户端packagesdut.ligong.demo;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamRea......
  • 使用SocketServer 创建TCP服务端
    **Java能够接受其他通信实体连接请求的类是ServerSocket,如果没有连接,他将一直处于等待状态,线程也被堵塞。ServerSocket包含一个监听来自客户端请求连接的方法。Socke......
  • Linux 防火墙之TCP Wrappers
    1、TCPWrappers 原理Telnet、SSH、FTP、POP和SMTP等很多网络服务都会用到TCPWrapper,它被设计为一个介于外来服务请求和系统服务回应的中间处理软件。  基本处理过......
  • CF1694E
    【题意】给出一张\(n\)个点\(m\)条边的有向图,有一个人要从\(1\)走到\(n\)。每天,你可以给他其中一种提示:封锁某一条边,即告诉他这条边之后都不能走。告诉他移动,他......
  • 16. 定位
    一、定位  定位是一种更加高级的布局手段。通过定位可以将一个元素摆放到页面的任意位置。使用position属性设置定位。  posotion属性的可选值:static:默认值,元素......
  • TCP、UDP 网络编程
    TCP、UDP网络编程实验目的1.使用TCP进行套接字通信2.使用UDP进行套接字通信实验原理1.TCP2.UDP3.用到的API(1)intsocket(intdomain,inttype,intprotocol);根......
  • TCP 实现跨平台文件传输
    TCP实现跨平台文件传输实验目的利用TCP完成linux和windows平台的文件传输。实验原理windows与linux上实现tcp文件传输本质上是相同的,只有一些函数调用方式不一......
  • 利用 TCP 完成文件传输的设计
    利用TCP完成文件传输的设计实验目的输入文件路径,利用TCP实现客户文件向服务器的传输,并实现对TCP的基本封装实验原理1.服务器(1)建立socket(2)确定服务器scokaddr_i......
  • TCP那些你需要掌握的知识
    什么是TCP/IP?TCP/IP协议模型(TransmissionControlProtocol/InternetProtocol),包含了一系列构成互联网基础的网络协议,是Internet的核心协议。并不只有TCP与IP两种协......
  • 智能卡 7816协议【转】
    本文转载自:​​smartcardT0T1T14协议区别​​1)T0异步半双工字符传输协议停止位是2,T1是1。2)T1是异步半双工块传输协议。有起始域,信息域,中止域组成一个块。3)T......