Java网络编程二:Socket详解
Socket又称套接字,是连接运行在网络上两个程序间的双向通讯的端点。
一、使用Socket进行网络通信的过程
服务端:服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户端的连接请求。
客户端:客户端程序根据你服务器所在的主机名和端口号发出连接请求。
两者之间的通信是通过Socket完成的,我们可以认为Socket是两个城市之间的交通工具,有了它,就可以在两个城市之间穿梭了。
Socket通信示例
主机A的应用程序和主机B的应用程序通信,必须通过Socket建立连接,而建立Socket必须由底层的TCP/IP协议来建立TCP连接。建立TCP连接需要底层IP协议来寻址网络中的主机。IP地址只能帮助我们找到目标主机,但是一个主机上面有多个应用程序,如何才能找到我们需要的应用程序,这个时候就可以通过端口号来指定了。
二、简易服务端、客户端模拟
服务器端:
public static void main(String[] args) throws IOException
{
//创建一个ServerSocket,用于监听客户端Socket连接请求
ServerSocket ss = new ServerSocket(8888);
System.out.println("server start");
//采用循环方式监听客户端的请求
while(true)
{
//侦听并接受到此套接字的连接。此方法在连接传入之前一直阻塞。
Socket socket = ss.accept();
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.print("您好,您收到了来自服务端的中秋祝福");
ps.close();
os.close();
socket.close();
}
}
执行结果:
server start
客户端:
public static void main(String[] args) throws IOException, Exception
{
Socket socket = new Socket("localhost",8888);
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = br.readLine();
System.out.println(str);
br.close();
is.close();
socket.close();
}
执行结果:
您好,您收到了来自服务端的中秋祝福
1、上面展示的是一个简易的服务端和客户端通信的建立过程。
2、我们通过交互图来详细介绍这个过程:
3、首先在server端,指定端口号创建serverSocket对象,通过serverSocket的accpet方法获取套接字,这个方法的特点是:侦听并接受到此套接字的连接,此方法在连接传入之前一直阻塞。这也就意味着,如果没有客户端连接请求过来,服务端会一致阻塞在这里。
4、后面的代码就是通过套接字socket可以得到输入输出流,到此为止,就是I/O的内容了。
5、在客户端这边,通过指定的服务器主机名和服务器监听的端口号,得到套接字Socket,这个时候就表示服务端和客户端的连接已经建立了,然后通过输入输出流来进行通信了。
三、半关闭的socket
在上面的Demo中,我们是以行作为通信的最小数据单位,服务器端也是逐行进行处理的。但是我们在大多数场景下,通信的数据单位是多行的,这时候Socket的输出流如何表达输出的数据已经结束?
在IO学习过程中提到过,如何要表示输出已经结束,则通过关闭输出流来实现,但是在socket中是行不通的,因为关闭socket,会导致无法再从该socket中读取数据了。为了解决这种问题,java提供了两个半关闭的方法:
1、shutdownInput():关闭该Socket的输入流,程序还可以通过该Socket的输出流输出数据。
2、shutdownOutput():关闭该Socket的输出流,程序还可以通过该Socket的输入流读取数据。
如果我们对同一个Socket实例先后调用shutdownInput和shutdownOutput方法,该Socket实例依然没有被关闭,只是该Socket既不能输出数据,也不能读取数据。
服务器端:
ServerSocket ss = new ServerSocket(5555);
Socket socket = ss.accept();
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("服务器端:开源中国杭州论坛");
ps.println("服务器端:杭州G20峰会");
//关闭输出流,表明输出已经结束
socket.shutdownOutput();
//判断该socket是否关闭
System.out.println(socket.isClosed());
Scanner scan = new Scanner((socket.getInputStream()));
while(scan.hasNextLine())
{
System.out.println(scan.nextLine());
}
scan.close();
socket.close();
ss.close();
客户端:
Socket s = new Socket("localhost", 5555);
InputStream is = s.getInputStream();
byte[] buffer = new byte[1024];
int flag = 0;
while(-1 != (flag = is.read(buffer,0,buffer.length)))
{
String str = new String(buffer,0,flag);
System.out.print(str);
}
PrintStream ps = new PrintStream(s.getOutputStream());
ps.println("客户端:欢迎参加开源中国论坛");
ps.println("客户端:欢迎参加G20峰会");
is.close();
ps.close();
s.close();
执行结果:
在服务器端程序中可以看到,在输出两段字符串之后,调用了shutdownOutput方法,表示输出已经结束。随即又去判断了socket是否关闭,执行的结果为false,表示socket并未关闭。
但是在调用了这两个半关闭的方法关闭了输出输入流之后,该socket无法再次打开该输出流或者输入流。因此这种场景不适合保持持久通信状态的交互使用,只适合一站式的通信协议.例如http协议:客户端连接到服务器之后,开始发送数据,发送完成之后无须再次发送数据,只需要读取服务器响应数据即可,读取数据完毕之后,该socket连接也被关闭了。
四、基于UDP协议的网络编程
前面介绍的socket编程都是基于TCP协议的,现在来看下基于UDP协议的编程,TCP和UDP的区别在上一章已经有过介绍。
UDP协议的主要作用就是完成网络数据流和数据报之间的转换-----在信息的发送端,UDP协议将网络数据流封装到数据报,然后将数据报发送出去;在信息的接收端,UDP协议将数据报转换成实际数据报内容。
1、首先在UDP网络编程中没有服务器端和客户端这种说法,两个socket之间没有虚拟链路,只是接收和发送数据报文而已。
2、这里面有两个重要的类:DatagramSocket 和DatagramPacket。前者是用来发送和接收数据包的套接字,后者表示数据包,每条报文仅根据该包中的包含的信息从一台机器 路由到另一台机器。
3、DatagramSocket 的两个构造函数:
DatagramSocket():构造数据报套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口。
在我们下面的DEMO中,UDPServerTest类中先发送数据报,使用的是套接字的无参构造器,而UDPClientTest类中先接收数据报,必须监听某一个端口,所以使用的是套接字的有参构造器。
4、DatagramPacket:创建的时候分为接收和发送两种
DatagramPacket(byte[] buf, int length):用来接收长度为
length
的数据包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port):用来将长度为
length
的包发送到指定主机上的指定端口号。
public class UDPServerTest
{
public static void main(String[] args) throws IOException
{
DatagramSocket ds = new DatagramSocket();
String str = "hello world";
//构造用于发送的数据包,指定主机和端口号
DatagramPacket packet = new DatagramPacket(str.getBytes(),
str.length(), InetAddress.getByName("localhost"), 5555);
ds.send(packet); //读取从客户端发送过来的响应
byte[] buffer = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(buffer,buffer.length);
ds.receive(packet2);
String str2 = new String(buffer,0,packet2.getLength());
System.out.println(str2);
ds.close();
}
}
public class UDPClientTest
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds = new DatagramSocket(5555);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
String str = new String(buffer, 0, packet.getLength());
System.out.println(str); // 接收到数据包之后,客户端返回响应回去
String str2 = "welcome";
DatagramPacket packet2 = new DatagramPacket(str2.getBytes(), str2
.length(), packet.getAddress(), packet.getPort());
ds.send(packet2);
ds.close();
}
}
执行过程:
1、上面的程序中,第一步是服务器端(暂且以这种叫法来区分这两个类)创建一个UDP套接字,没有指定端口,使用的是系统分配的端口。然后构建了一个数据包,包中指定 了目标机器的ip和端口号。
2、作为客户端,创建了一个UDP套接字,并且绑定了端口,如果想要接收到服务端发送过来的报文,绑定的端口必须和服务器端发送的包中指定的端口一致。
3、客户端打印了包中的内容之后,想要返回一些内容回去。这个时候,服务器端的ip和端口号可以从之前发送过来的数据包中获取。
DatagramPacket packet2 = new DatagramPacket(str2.getBytes(), str2.length(), packet.getAddress(), packet.getPort());
4、在服务器接收数据包的时候,已经不需要再像客户端创建套接字一样去绑定端口了,因为目前监听的端口和客户端发送的包中指定的端口是一样的。
5、打印看下服务器端的ip和监听的端口号:
serverIp =/127.0.0.1;serverPort=62965
6、其中DatagramSocket的receive(DatagramPacket p)方法在接收到数据包前一直阻塞。
Java网络编程二:Socket详解的更多相关文章
- Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制
Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制 JAVA 中原生的 socket 通信机制 摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.co ...
- Java网络编程和NIO详解8:浅析mmap和Direct Buffer
Java网络编程与NIO详解8:浅析mmap和Direct Buffer 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NI ...
- Java网络编程和NIO详解6:Linux epoll实现原理详解
Java网络编程和NIO详解6:Linux epoll实现原理详解 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO h ...
- Java网络编程和NIO详解开篇:Java网络编程基础
Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...
- Java网络编程和NIO详解9:基于NIO的网络编程框架Netty
Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...
- Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...
- Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO
Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO Java 非阻塞 IO 和异步 IO 转自https://www.javadoop.com/post/nio-and-aio 本系 ...
- Java网络编程和NIO详解4:浅析NIO包中的Buffer、Channel 和 Selector
Java网络编程与NIO详解4:浅析NIO包中的Buffer.Channel 和 Selector 转自https://www.javadoop.com/post/nio-and-aio 本系列文章首 ...
- Java网络编程和NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型
Java网络编程与NIO详解2:JAVA NIO一步步构建IO多路复用的请求模型 知识点 nio 下 I/O 阻塞与非阻塞实现 SocketChannel 介绍 I/O 多路复用的原理 事件选择器与 ...
- Java网络编程和NIO详解3:IO模型与Java网络编程模型
Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32 ...
随机推荐
- JavaScript总结(八)
表单验证 表单验证是JavaScript最常用.最有用的功能之一.在表单内容提交之前进行验证,可以降低服务器处理器的压力,缩短用户等待的时间.表单校验中第一个要考虑的问题是:什么时候捕获表单的录入错误 ...
- Python 学习计划
时间分为4周,全部自学,仅提供大纲.适用于Web方向: 1.Week1:读完<简明Python教程>,适应Python开发环境 2.Week2:写个爬虫,需要深入了解re.urllib2. ...
- 一维码EAN 13简介及其解码实现(zxing-cpp)
一维码EAN 13:属于国际标准条码, 由13个数字组成,为EAN的标准编码型式(EAN标准码). 依结构的不同,EAN条码可区分为: 1. EAN 13码: 由13个数字组成,为EAN的标准编码型 ...
- 用C实现单隐层神经网络的训练和预测(手写BP算法)
实验要求:•实现10以内的非负双精度浮点数加法,例如输入4.99和5.70,能够预测输出为10.69•使用Gprof测试代码热度 代码框架•随机初始化1000对数值在0~10之间的浮点数,保存在二维数 ...
- 日志模块logging介绍
一.日志的级别 日志一般分为5个级别,分别如下: CRITICAL = 50 #FATAL = CRITICAL ERROR = 40 WARNING = 30 #WARN = WARNING INF ...
- 使用electron开发一个h5的客户端应用创建http服务模拟后台接口mock
使用electron开发一个h5的客户端应用创建http服务模拟后端接口mock 在上一篇<electron快速开始>里讲述了如何快速的开始一个electron的应用程序,既然electr ...
- Siki_Unity_3-7_AssetBundle从入门到掌握
Unity 3-7 AssetBundle从入门到掌握 任务1&2&3:课程介绍 AssetBundle -- 用于资源的更新 为了之后的xLua (Lua热更新的框架)打下基础 任务 ...
- NO--14 微信小程序,左右联动二
上一篇讲解了左=>右联动,那个还比较简单,本篇写剩下比较核心的部分,也是本次开发过程中遇到最难的部分,右=>左联动,先简单看一下演示 右左联动.gif 一.关键技术: (1) 小程序 ...
- mysql删除表中的记录
大家都知道,在MySQL中删除一个表中的记录有两种方法,一种是DELETE FROM TABLENAME WHERE... , 还有一种是TRUNCATE TABLE TABLENAME. DELET ...
- 你也可以手绘二维码(二)纠错码字算法:数论基础及伽罗瓦域GF(2^8)
摘要:本文讲解二维码纠错码字生成使用到的数学数论基础知识,伽罗瓦域(Galois Field)GF(2^8),这是手绘二维码填格子理论基础,不想深究可以直接跳过.同时数论基础也是 Hash 算法,RS ...