TCP/IP 协议简介

IP

首先我们看 IP(Internet Protocol)协议。IP 协议提供了主机和主机间的通信。

为了完成不同主机的通信,我们需要某种方式来唯一标识一台主机,这个标识,就是著名的IP地址。通过IP地址,IP 协议就能够帮我们把一个数据包发送给对方。

TCP

前面我们说过,IP 协议提供了主机和主机间的通信。

TCP 协议在 IP 协议提供的主机间通信功能的基础上,完成这两个主机上进程对进程的通信。

有了 IP,不同主机就能够交换数据。但是,计算机收到数据后,并不知道这个数据属于哪个进程(简单讲,进程就是一个正在运行的应用程序)。TCP 的作用就在于,让我们能够知道这个数据属于哪个进程,从而完成进程间的通信。

为了标识数据属于哪个进程,我们给需要进行 TCP 通信的进程分配一个唯一的数字来标识它。这个数字,就是我们常说的端口号。

三次握手

TCP 的全称是 Transmission Control Protocol,大家对它说得最多的,大概就是面向连接的特性了。之所以说它是有连接的,是说在进行通信前,通信双方需要先经过一个三次握手的过程。三次握手完成后,连接便建立了。这时候我们才可以开始发送/接收数据。(与之相对的是 UDP,不需要经过握手,就可以直接发送数据)。

下面我们简单了解一下三次握手的过程。

首先,客户向服务端发送一个 SYN,假设此时 sequence number 为 x。这个 x 是由操作系统根据一定的规则生成的,不妨认为它是一个随机数。

服务端收到 SYN 后,会向客户端再发送一个 SYN,此时服务器的 seq number = y。与此同时,会 ACK x+1,告诉客户端“已经收到了 SYN,可以发送数据了”。

客户端收到服务器的 SYN 后,回复一个 ACK y+1,这个 ACK 则是告诉服务器,SYN 已经收到,服务器可以发送数据了。

经过这 3 步,TCP 连接就建立了。这里需要注意的有三点:

连接是由客户端主动发起的

在第 3 步客户端向服务器回复 ACK 的时候,TCP 协议是允许我们携带数据的。之所以做不到,是 API 的限制导致的。

TCP 协议还允许 “四次握手” 的发生,同样的,由于 API 的限制,这个极端的情况并不会发生

一、socket通信基本原理

Socket 是 TCP 层的封装,通过 socket,我们就能进行 TCP 通信。

socket 通信是基于TCP/IP协议的一种传送方式,实现网络间的双向通信,我们通常把TCP和UDP称为传输层。

如上图,在七个层级关系中,我们讲的socket属于传输层,

其中UDP是一种面向无连接的传输层协议。UDP不关心对端是否真正收到了传送过去的数据。

如果需要检查对端是否收到分组数据包,或者对端是否连接到网络,则需要在应用程序中实现。

UDP常用在分组数据较少或多播、广播通信以及视频通信等多媒体领域。

在这里我们不进行详细讨论,这里主要讲解的是基于TCP/IP协议下的socket通信。

socket是基于应用服务与TCP/IP通信之间的一个抽象,他将TCP/IP协议里面复杂的通信逻辑进行分装,

对用户来说,只要通过一组简单的API就可以实现网络的连接

首先,服务端初始化ServerSocket,然后对指定的端口进行绑定,接着对端口及进行监听,通过调用accept方法阻塞,

此时,如果客户端有一个socket连接到服务端,那么服务端通过监听和accept方法可以与客户端进行连接。

二  基本示例

服务端

1package socket.socket1.socket;2

3import java.io.BufferedReader;4import java.io.IOException;5import java.io.InputStreamReader;6import java.net.ServerSocket;7import java.net.Socket;8

9 public classServerSocketTest {10

11 public static voidmain(String[] args) {12 try{13 //初始化服务端socket并且绑定9999端口

14 ServerSocket serverSocket = new ServerSocket(9999);15 //等待客户端的连接

16 Socket socket =serverSocket.accept();17 //获取输入流

18 BufferedReader bufferedReader = new BufferedReader(newInputStreamReader(socket.getInputStream()));19 //读取一行数据

20 String str =bufferedReader.readLine();21 //输出打印

22 System.out.println(str);23 } catch(IOException e) {24e.printStackTrace();25}26}27 }

客户端

1package socket.socket1.socket;2

3import java.io.BufferedWriter;4import java.io.IOException;5import java.io.OutputStreamWriter;6import java.net.Socket;7

8 public classClientSocket {9 public static voidmain(String[] args) {10 try{11 Socket socket = new Socket("127.0.0.1", 9999);12 BufferedWriter bufferedWriter = new BufferedWriter(newOutputStreamWriter(socket.getOutputStream()));13 String str = "你好,这是我的第一个socket";14bufferedWriter.write(str);15 } catch(IOException e) {16e.printStackTrace();17}18}19 }

先启动服务端

再启动客户端

发现客户端启动正常后,马上执行完后关闭。同时服务端控制台报错:

服务端报错

然后好多童鞋,就拷贝这个java.net.SocketException: Connection reset上王查异常,查询解决方案,搞了半天都不知道怎么回事。

解决这个问题我们首先要明白,socket通信是阻塞的,他会在以下几个地方进行阻塞。

第一个是accept方法,调用这个方法后,服务端一直阻塞在哪里,直到有客户端连接进来。

第二个是read方法,调用read方法也会进行阻塞。通过上面的示例我们可以发现,该问题发生在read方法中。

有朋友说是Client没有发送成功,其实不是的,我们可以通debug跟踪一下,发现客户端发送了,并且没有问题。

而是发生在服务端中,当服务端调用read方法后,他一直阻塞在哪里,因为客户端没有给他一个标识,告诉是否消息发送完成,

所以服务端还在一直等待接受客户端的数据,结果客户端此时已经关闭了,就是在服务端报错:java.net.SocketException: Connection reset

那么理解上面的原理后,我们就能明白,客户端发送完消息后,需要给服务端一个标识,告诉服务端,我已经发送完成了,服务端就可以将接受的消息打印出来。

通常大家会用以下方法进行进行结束:

调用socket.close() 或者socket.shutdownOutput()方法。

调用这俩个方法,都会结束客户端socket。但是有本质的区别。

socket.close() 将socket关闭连接,那边如果有服务端给客户端反馈信息,此时客户端是收不到的。

socket.shutdownOutput()是将输出流关闭,此时,如果服务端有信息返回,则客户端是可以正常接受的。

现在我们将上面的客户端示例修改一下啊,增加一个标识告诉流已经输出完毕:

客户端

1 packagesocket.socket1.socket;2

3 importjava.io.BufferedWriter;4 importjava.io.IOException;5 importjava.io.OutputStreamWriter;6 importjava.net.Socket;7

8 public classClientSocket {9 public static voidmain(String[] args) {10 try{11 Socket socket = new Socket("127.0.0.1", 9999);12 BufferedWriter bufferedWriter = new BufferedWriter(newOutputStreamWriter(socket.getOutputStream()));13 String str = "你好,这是我的第一个socket";14bufferedWriter.write(str);15 //刷新输入流

16bufferedWriter.flush();17 //关闭socket的输出流

18socket.shutdownOutput();19 } catch(IOException e) {20e.printStackTrace();21}22}23 }

在看服务端控制台:

通过上面示例,我们可以基本了解socket通信原理,掌握了一些socket通信的基本api和方法,实际应用中,都是通过此处进行实现变通的。

但上面示例,其实不够完整,比如我们每次发送都要new 一个socket ,也只支持一次发送消息,所以我们用另外一个例子,实现1个比较完整的demo

三  手写完整示例

例用Socket实现客户端和服务端通信,要求客户发送数据后回显相同的数据

服务端 socket

packagecom.differ.jackyun.examples.javabasisc.socket;importjava.io.BufferedReader;importjava.io.InputStreamReader;importjava.io.PrintWriter;importjava.net.ServerSocket;importjava.net.Socket;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;/*** 服务端soccket 测试

*

*@authorhup

* @data 2020-05-31 14:30

**/

public class MyServerSocket implementsRunnable {

@Overridepublic voidrun() {//创建一个线程池

ExecutorService executorService = Executors.newFixedThreadPool(100);try{

ServerSocket server= new ServerSocket(10001);while(true)

{//阻塞等待

Socket socket =server.accept();//为了支持并发,所以每来1次消息,都弄个新线程处理

Runnable runnable = () ->{//字符输入流

BufferedReader reader = null;//字符输出流

PrintWriter pw = null;try{//读取接收到的内容

reader = new BufferedReader(newInputStreamReader(socket.getInputStream()));//接收到的数据

String readResult =reader.readLine();

System.out.println("服务端接收到数据=" +readResult);//数据发回客户端

pw = new PrintWriter(socket.getOutputStream(), true);

pw.println(readResult);

}catch(Exception e) {

}finally{//关闭流

try{if (reader != null) {

reader.close();

}if (pw != null) {

pw.close();

}

}catch(Exception e) {

}

}

};//线程池提交线程任务

executorService.submit(runnable);

}

}catch(Exception ex) {

ex.printStackTrace();

}

}

}

客户端 socket

packagecom.differ.jackyun.examples.javabasisc.socket;importjava.io.BufferedReader;importjava.io.InputStreamReader;importjava.io.PrintWriter;importjava.net.Socket;/*** 客户端socket

*

*@authorhup

* @data 2020-05-31 14:30

**/

public class MySocket implementsRunnable {

@Overridepublic voidrun() {//输出字符流

PrintWriter pw = null;//输入字符流

BufferedReader reader = null;try{//输出字符流

Socket socket = new Socket("localhost", 10001);

pw= new PrintWriter(socket.getOutputStream(), true);//向服务端发送消息

pw.println("我是客户端消息,今天天气真好");//等待服务器端的消息

reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));while (true) {

String result=reader.readLine();if (result != null) {

System.out.println("客户端接收到服务端消息=" +result);break;

}

}

}catch(Exception ex) {

}finally{//关闭流

try{if (pw != null) {

pw.close();

}if (reader != null) {

reader.close();

}

}catch(Exception e) {

}

}

}

}

测试类

1 packagecom.differ.jackyun.examples.javabasisc.socket;2

3 importorg.junit.Test;4

5 /**

6 * 套接字测试类7 *8 *@authorhup9 * @data 2020-05-31 15:1410 **/

11 public classsocketTest {12 @Test13 public voidtest() {14 //启动服务端

15 MyServerSocket myServerSocket = newMyServerSocket();16 newThread(myServerSocket).start();17

18 try{19 Thread.currentThread().sleep(5000);20 } catch(Exception ex) {21 System.out.println(ex);22 }23

24 //启动客户端1

25 MySocket mySocket = newMySocket();26 newThread(mySocket).start();27

28 //启动客户端2

29 MySocket mySocket2 = newMySocket();30 newThread(mySocket2).start();31

32 try{33 Thread.currentThread().sleep(10000);34 } catch(Exception ex) {35 System.out.println(ex);36 }

38 }

40 }

测试输出结果

服务端接收到数据=我是客户端消息,今天天气真好

客户端接收到服务端消息=我是客户端消息,今天天气真好

服务端接收到数据=我是客户端消息,今天天气真好

客户端接收到服务端消息=我是客户端消息,今天天气真好

根据结果可以知道: 多个客户端给服务端发消息,服务端都能处理(用到了多线程)

四   看完上面例子,可能有同学有疑问了,为什么你输入流(读取)的时候用的是BufferedReader, 输出流(写)的时候用的是PrintWriter 不应该用与BufferedReader 配套的BufferedWriter吗?

Socket编程中,尽量用PrintWriter取代BufferedWriter,下面是PrintWriter的优点:

1. PrintWriter的print、println方法可以接受任意类型的参数,而BufferedWriter的write方法只能接受字符、字符数组和字符串;

2. PrintWriter的println方法自动添加换行,BufferedWriter需要显示调用newLine方法;

3. PrintWriter的方法不会抛异常,若关心异常,需要调用checkError方法看是否有异常发生;

4. PrintWriter构造方法可指定参数,实现自动刷新缓存(autoflush);

5. PrintWriter的构造方法更广。

在使用BufferedReader中的readLine方法接收BufferedWriter中的字符流时,由于readLine是在读取到换行符的时候才将整行字符返回,所以BufferedWriter方法在录入一段字符后要使用newLine方法进行一次换行操作,然后再把字符流刷出去。而PrintWriter由于可以开启自动刷新,并且其中的println方法自带换行操作。所以代码实现起来要比BufferedWriter简单一些。

————————————————

版权声明:最后面这部分总结 来源于下面链接

原文链接:https://blog.csdn.net/arno_dzl/java/article/details/76601852

java socket唯一标识符_java Socket相关推荐

  1. java socket 线程池_java socket编程的一个例子(线程池)

    服务器程序EchoServer.java: import java.io.*; import java.net.*; import java.util.concurrent.*; public cla ...

  2. java socket远空_JAVA Socket超时浅析

    套接字或插座(socket)是一种软件形式的抽象,用于表达两台机器间一个连接的"终端".针对一个特定的连接,每台机器上都有一个"套接字",可以想象它们之间有一条 ...

  3. java socket 循环读取_java socket tcp(服务器循环检测)

    刚才看了下以前写了的代码,tcp通信,发现太简单了,直接又摘抄了一个,运行 博客:http://blog.csdn.net/zhy_cheng/article/details/7819659 优点是服 ...

  4. java socket编程心跳_Java Socket编程心跳包创建实例解析

    1.什么是心跳包? 心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令字,按照一定的时间间隔发送,类似于心跳,所以叫做心跳包. 用来判断对方(设备,进程或其它网元)是否正常运行,采用 ...

  5. java socket负载均衡_Java Socket分发服务负载均衡

    1 1. 设备请求分发服务器,分发服务器返回有效的socket服务器ip与port,然后断开连接.2 a) 设备与服务器建立连接.3 b) 服务器接收到连接请求后,立即将分配好的socket服务器ip ...

  6. java关键字和标识符_Java数据类型和标识符

    java关键字和标识符 在本教程中,我们将了解Java中的数据类型和标识符. Java语言具有丰富的数据类型实现. 数据类型指定大小和可以存储在标识符中的值的类型. Java数据类型分为两类: 原始数 ...

  7. java类作用域标识符_java入门 (二) 标识符、数据类型、类型转换、变量、常量、作用域...

    java入门(二) 标识符 数据类型 类型转换 变量.常量.作用域 本次笔记引用B站:狂神说,虽然早就会了,现在回头来敲下基础,加深印象 1.标识符: java所有的组成部分都需要名字.类名丶变量名丶 ...

  8. java 获得唯一 数字_java生成唯一数字

    java生成唯一数字 用UUID类生成唯一标识的时候,会生成一个十六进制的整数,但是不能作为数据库long型字段的唯一标识,用下面的办法可以实现数据库long型标识的生成: public class ...

  9. java什么是标识符_java什么是标识符

    java中的标识符是用户编程时使用的名字,用于给变量.常量.函数.语句块等命名. Java标识符由数字,字母和下划线(_),美元符号($)组成.在Java中是区分大小写的,而且还要求首位不能是数字.最 ...

  10. java socket通信 客户端_JavaのSocket编程之简单客户端与服务器端通信

    Socket编程之简单客户端与服务器端通信 socket 通常用来实现客户端和服务端的连接,socket 是Tcp/Ip协议的一个十分流行的编程界面,一个socket 由一个Ip地址和一个端口号唯一确 ...

最新文章

  1. 互联网产品mysql数据库设计总结
  2. Nodejs学习笔记之复制文件
  3. 利用iptables来配置linux禁止所有端口登陆和开放指定端口
  4. 你知道 Redis 可以实现延迟队列吗?
  5. 2021社区居家养老现状与未来趋势报告
  6. 作者:熊贇(1980-),女,博士,复旦大学计算机科学技术学院教授
  7. 游戏3d建模师的待遇及发展
  8. yapi 界面修改_当Swagger遇上YApi,瞬间高大上了!(一个好用的接口管理工具)...
  9. 设计干货栅格系统素材 | UI设计师应用好帮手
  10. 开发技术理论学习与实践的关系
  11. 【js】js中const,var,let区别
  12. 如何把图片与压缩包合并成可改后缀名的图片文件及原理
  13. python爬虫英文单词_Python_爬虫百度英文学习词典
  14. java类 家族成员 姓氏_中国史上十大家族,占据中国九大姓,看看有没有你的姓氏...
  15. 日期插件中中文乱码修改
  16. android 公式编辑器,公式编辑器
  17. 展锋芒,一遇风云便化龙
  18. HTML5-坦克大战一完成坦克上下左右移动的功能(一)
  19. Nginx+lua开发(OpenResty生态)
  20. 微任务,宏任务,DOM渲染的执行顺序

热门文章

  1. 解决同一办公环境局域网下无法添加打印机的情况
  2. js数组按中文拼音排序_这才是选择排序正确的打开方式!
  3. 图片文字识别 mysql_有道智云OCR图片识别文字+返回数据处理技巧(实现语言-按键精灵脚本请求识别+java服务端处理数据)...
  4. AI Studio 数据集
  5. 去泰国,怎能错过这些零食!
  6. 安静的秋千 ,晚上不睡早晨不起精彩回帖汇总
  7. 世界各个国家或地区国际域名缩写(Countries and Regions)
  8. 使用Scrapy框架爬取网页并保存到Mysql
  9. 电脑硬盘怎么分区?C盘/D盘/E盘......快来创建自己的DIY磁盘吧!
  10. 日语动词变形(概念明确篇)