热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:搞了两周Socket通信,终于弄明白了!文末送书

篇首语:本文由编程笔记#小编为大家整理,主要介绍了搞了两周Socket通信,终于弄明白了!--文末送书相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了搞了两周Socket通信,终于弄明白了!--文末送书相关的知识,希望对你有一定的参考价值。






点击上方
[
全栈开发者社区
]

右上角
[...]

[设为星标⭐]





博客:https://www.jianshu.com/p/b04930d2b85e










传输层协议TCP、UDP,但它们毕竟只是协议,看不见摸不着,那我们怎们通过TCP、和UDP进行实际传输呢?不用着急,等看完这篇文章你一定会明白的。











Socket概述











Socket中文意思为插座的意思,专业术语称之为套接字,它把TCP/IP封装成了调用接口供开发者调用,也就是说开发者可以通过调用Socket相关API来实现网络通讯。在Java中也存在Socket相关API,主要分为两个,分别是基于UDP传输协议的Socket和基于TCP传输协议的Socket,本篇文章会对基于这两种传输协议的Socket进行详细描述。











UDP Socket














**
* 发送方UDP
*/
public class UDPSocketSend {
   public static void main(String[] args) throws IOException {
       System.out.println("Sender Start...");
       //1.创建socket服务
       DatagramSocket ds = new DatagramSocket();
       //2.封装数据
       String str = "Did you recite words today";
       byte[] bytes = str.getBytes();
       //地址
       InetAddress address =InetAddress.getByName("192.168.31.137");
       //参数:数据、长度、地址、端口
       DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,6666);
       //3.发送数据包
       ds.send(dp);
       //4.关闭socket服务
       ds.close();
   }
/**
* 接收方UDP
*/

public class UDPSocketReceive{
   public static void main(String[] args) throws IOException {
       System.out.println("Receiver Start...");
       //1.创建udp的socket服务,并声明端口号
       DatagramSocket ds = new DatagramSocket(6666);
       //2.创建接收数据的数据包
       byte[] bytes = new byte[1024];
       DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
       //3.将数据接收到数据包中,为阻塞式方法
       ds.receive(dp);
       //4.解析数据
       InetAddress address = dp.getAddress();//发送方IP
       int port = dp.getPort();//发送方端口
       String cOntent= new String(dp.getData(),0,dp.getLength());
       System.out.println("address:"+address+"---port:"+port+"---content:"+content);
       //关闭服务
       ds.close();
   }
}




分别启动发送方和接收方,我们来看一下打印结果



发送方:





Sender Start...




接收方:





Receiver Start...
address:/192.168.31.137---port:65037---content:Did you recite words today




成功接收到消息,并打印出发送方IP和端口,下面我来个大家撸一遍步骤


发送方:





  • 首先创建udp的socket服务


  • 将需要发送的数据放在数据包DatagramSocket中,DatagramSocket会根据UDP协议对数据包、IP、端口号进行封装


  • 通过udp的socket服务将数据包发送


  • 最后将udp服务关闭





接收方:





  • 创建udp的socket服务,并且明确自己的端口号


  • 创建DatagramSocket用来解析数据接收到的数据包


  • 将数据接收到数据包DatagramSocket中


  • 通过DatagramSocket解析数据


  • 关闭服务





整个UDP发送数据的流程就是这样




注意点:





  • 为UDP是无连接的不可靠传输,所以接收方需要在发送方发送数据之前就启动,否则会接收不到数据,也就是说必须先运行UDPSocketReceive再运行UDPSocketSend。





搞了两周Socket通信,终于弄明白了!--文末送书











聊天实例











把上面的例子进行一些小改动就可以实现聊天功能





public class UDPSocket1 {
   public static void main(String[] args) {
       try {
           new Thread(new Runnable() {
               @Override
               public void run()
{
                   try {
                       receive();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
           }).start();
           send();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
   //接收消息方法
   private static void receive() throws IOException {
       System.out.println("UDOSocket1 Receiver Start...");
       //1.创建udp的socket服务,并声明端口号
       DatagramSocket ds = new DatagramSocket(6666);
       //无限循环,一直处于接收状态
       while (true) {
           //2.创建接收数据的数据包
           byte[] bytes = new byte[1024];
           DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
           //3.将数据接收到数据包中
           ds.receive(dp);
           //4.解析数据
           String cOntent= new String(dp.getData(), 0, dp.getLength());
           System.out.println("UDPSocket1 Receive:" + content);
       }
   }
   private static void send() throws IOException {
       //1.创建socket服务
       DatagramSocket ds = new DatagramSocket();
       //将键盘输入的信息转换成输入流再放入到缓冲区
       BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
       String line = null;
       while ((line=br.readLine())!=null){
           //2.封装数据
           byte[] bytes = line.getBytes();
           //地址
           InetAddress address =InetAddress.getByName("192.168.31.137");
           //参数:数据、长度、地址、端口
           DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,7777);
           //3.发送数据包
           ds.send(dp);
       }
       //4.关闭socket服务
       ds.close();
   }
}
public class UDPSocket2 {
   public static void main(String[] args){
       try {
           new Thread(new Runnable() {
               @Override
               public void run()
{
                   try {
                       receive();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
           }).start();
           send();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
   //接收消息方法
   private static void receive() throws IOException {
       System.out.println("UDOSocket2 Receiver Start...");
       //1.创建udp的socket服务,并声明端口号
       DatagramSocket ds = new DatagramSocket(7777);
       //无限循环,一直处于接收状态
       while (true) {
           //2.创建接收数据的数据包
           byte[] bytes = new byte[1024];
           DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
           //3.将数据接收到数据包中
           ds.receive(dp);
           //4.解析数据
           String cOntent= new String(dp.getData(), 0, dp.getLength());
           System.out.println("UDPSocket2 Receive:" + content);
       }
       //关闭服务
//        ds.close();
   }
   private static void send() throws IOException {
       //1.创建socket服务
       DatagramSocket ds = new DatagramSocket();
       //将键盘输入的信息转换成输入流再放入到缓冲区
       BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
       String line = null;
       while ((line=br.readLine())!=null){
           //2.封装数据
           byte[] bytes = line.getBytes();
           //地址
           InetAddress address =InetAddress.getByName("192.168.31.137");
           //参数:数据、长度、地址、端口
           DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,6666);
           //3.发送数据包
           ds.send(dp);
       }
       //4.关闭socket服务
       ds.close();
   }
}




在接收方方法写一个无限循环让其处于持续接收状态,发送发通过对键盘录入加回车进行消息的发送,并且两个端点都具备发送和接收功能。需要注意的是receive()会执行一个无限循环,所以receive()和send()必须位于不同线程,否则会导致无法发送消息从而也接收不到消息。




来看打印结果:
UDPSocket1



UDPSocket Receiver Start...
UDPSocket Receive:hello udp1
heelo udp2




UDPSocket2



UDPSocket2 Receiver Start...
hello udp1
UDPSocket2 Receive:hello udp2




我在UDPSocket1 的控制台中输入了:“hello udp2”、在UDPSocket2 的控制台中输入了:“hello udp1”,接收内容和发送内容完全一致,一个简单的聊天功能就实现了,希望不熟悉的朋友也可以敲一遍代码练一遍。











TCP Socket











TCP基于client-server是面向连接的可靠传输,上篇文章我们也讲解了TCP安全传输机制,可谓是相当复杂,如果需要个人对TCP协议进行封装显然大多数开发者是很难实现的,所以Java也为开发者提供了基于TCP的Socket,不同于UDP,TCP Socket分为Socket和ServerSocket对应着client和server,下面我来用代码实现一个简单的TCP通讯功能:



客户端:





//客户端
public class TCPClient {
   public static void main(String[] args) throws IOException {
       //1.创建TCP客户端Socket服务
       Socket client = new Socket();
       //2.与服务端进行连接
       InetSocketAddress address = new InetSocketAddress("192.168.31.137",10000);
       client.connect(address);
       //3.连接成功后获取客户端Socket输出流
       OutputStream outputStream = client.getOutputStream();
       //4.通过输出流往服务端写入数据
       outputStream.write("hello server".getBytes());
       //5.关闭流
       client.close();
   }
}




首先创建一个Socket和InetSocketAddress ,然后通过Socket的connect()方法进行连接,连接成功后可以获取到输出流,通过该输出流就可以向服务端传输数据。




服务端:





public class TCPServer {
   public static void main(String[] args) throws IOException {
       //1.创建服务端Socket并明确端口号
       ServerSocket serverSocket = new ServerSocket(10000);
       //2.获取到客户端的Socket
       Socket socket = serverSocket.accept();
       //3.通过客户端的Socket获取到输入流
       InputStream inputStream = socket.getInputStream();
       //4.通过输入流获取到客户端传递的数据
       BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
       String line = null;
       while ((line = bufferedReader.readLine())!=null){
           System.out.println(line);
       }
       //5.关闭流
       socket.close();
       serverSocket.close();
   }




首先创建一个服务端Socket并明确端口号,通过accept()方法获取到链接过来的客户端Socket,从客户端Socket中获取输入流,最后由输入流读取客户端传输来的数据。



我们来看看服务端的打印结果:





hello server




成功接收到客户端发来的数据




注意点:





  • 一个服务端是可以同时和多个客户端进行通信的,那么它是如何区分不同客户端呢?从上面代码我们可以看到,服务端首先通过accept()获取到客户端Socket,然后通过客户端的Socket获取的流进行通讯,这也让服务端得以区分每个客户端。















文件传输实例











流程:客户端上传一个文件到服务端,服务端收到文件后进行保存,保存成功后给客户端一个响应。



客户端代码





public class TCPUploadClient {
   public static void main(String[] args) throws IOException {
       //1.创建TCP客户端Socket服务
       Socket client = new Socket();
       //2.与服务端进行连接
       InetSocketAddress address = new InetSocketAddress("192.168.31.137",10001);
       client.connect(address);
       //3.读取客户端文件
       FileInputStream fis = new FileInputStream("E://girl.jpg");
       //4.获取输出流
       OutputStream outputStream = client.getOutputStream();
       //5.将文件写入到服务端
       byte[] bytes = new byte[1024];
       int len = 0;
       while ((len = fis.read(bytes))!=-1){
           outputStream.write(bytes,0,len);
       }
       //6.通知服务器数据写入完毕
       client.shutdownOutput();
       //7.读取服务端响应的数据
       InputStream inputStream = client.getInputStream();
       BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
       String line = br.readLine();
       System.out.println(line);
       //8.关闭流
       inputStream.close();
       fis.close();
       client.close();
   }
}




创建Socket并连接,读取本地文件输入流,将文件写入到服务端,写入成功后读取服务端返回的数据。




服务端代码





public class TCPUploadServer {
   public static void main(String[] args) throws IOException {
       //1.创建客户端Socket
       ServerSocket serverSocket = new ServerSocket(10001);
       
       //2.获取到客户端Socket
       Socket socket = serverSocket.accept();
       
       //3.通过客户端Socket获取到输入流
       InputStream is = socket.getInputStream();
       
       //4.将流以文件的形式写入到磁盘
       File dir = new File("F://tcp");
       //如果文件夹不存在就创建文件夹
       if(!dir.exists()){
           dir.mkdirs();
       }
       File file = new File(dir,"girl.jpg");
       FileOutputStream fos = new FileOutputStream(file);
       byte[] bytes = new byte[1024];
       int len = 0;
       while ((len = is.read(bytes))!=-1){
           fos.write(bytes,0,len);
       }
       
       //5.通知客户端文件保存完毕
       OutputStream os = socket.getOutputStream();
       os.write("success".getBytes());
       
       //6.关闭流
       fos.close();
       os.close();
       socket.close();
   }
}




创建Socket并获取到客户端Socket和输入流,在F盘的tcp目录下创建一个文件,将从客户端读取到的数据输出到文件中,保存成功后给客户端返回''success''




这样我们就实现了一哥客户端上传文件的功能,还是比较简单的,希望大家也能够跟着代码敲一遍。











TCP Socket多线程应用











细心的同学可能已经发现,上面我们的实例中一个服务端只能接收一个客户端的一次请求,这显然是不符合实际的,那么如何使一个服务端同时服务多个客户端呢?接着撸代码





//客户端1
public class TCPClient1 {
   public static void main(String[] args) throws IOException {
       System.out.println("TCPClient1 Start...");
       //1.创建TCP客户端Socket服务
       Socket client = new Socket();
       //2.与服务端进行连接
       InetSocketAddress address = new InetSocketAddress("192.168.31.137",10004);
       client.connect(address);
       //3.连接成功后获取客户端Socket输出流
       OutputStream outputStream = client.getOutputStream();
       //4.通过输出流往服务端写入数据
       outputStream.write("Hello my name is Client1".getBytes());
       //5.告诉服务端发送完毕
       client.shutdownOutput();
       //6.读取服务端返回数据
       InputStream is = client.getInputStream();
       byte[] bytes = new byte[1024];
       int len = is.read(bytes);
       System.out.println(new String(bytes,0,len));
       //7.关闭流
       client.close();
   }
}
//客户端2
public class TCPClient2 {
   public static void main(String[] args) throws IOException {
       System.out.println("TCPClient2 Start...");
       //1.创建TCP客户端Socket服务
       Socket client = new Socket();
       //2.与服务端进行连接
       InetSocketAddress address = new InetSocketAddress("192.168.31.137",10004);
       client.connect(address);
       //3.连接成功后获取客户端Socket输出流
       OutputStream outputStream = client.getOutputStream();
       //4.通过输出流往服务端写入数据
       outputStream.write("Hello my name is Client2".getBytes());
       //5.告诉服务端发送完毕
       client.shutdownOutput();
       //6.读取服务端返回数据
       InputStream is = client.getInputStream();
       byte[] bytes = new byte[1024];
       int len = is.read(bytes);
       System.out.println(new String(bytes,0,len));
       //7.关闭流
       client.close();
   }
}
//服务端
public class TCPServer {
   public static void main(String[] args) throws IOException {
       receive();
   }
   private static void receive() throws IOException {
       System.out.println("Server Start...");
       //创建服务端Socket并明确端口号
       ServerSocket serverSocket = new ServerSocket(10004);
       while (true){
           //获取到客户端的Socket
           Socket socket = serverSocket.accept();
           //通过客户端的Socket获取到输入流
           InputStream is = socket.getInputStream();
           //通过输入流获取到客户端传递的数据
           byte[] bytes = new byte[1024];
           int len = is.read(bytes);
           System.out.println(new String(bytes,0,len));
           //将客户端发来的数据原封不动返回
           OutputStream os = socket.getOutputStream();
           os.write(new String(bytes,0,len).getBytes());
           //关闭连接
           socket.close();
       }
   }
}




客户端1控制台





TCPClient1 Start...
Hello my name is Client1




客户端2控制台





TCPClient2 Start...
Hello my name is Client2




这样就可以实现一个服务端服务多个客户端


细心的同学可能又发现了,上面的写法是存在问题的,由于服务端始终都在主线程中处理请求,所以客户端的请求需要被服务端排队处理,举个例子:Client1对服务端进行了一次请求,服务端在响应Client1之前是不会接受其他请求的,显然这是不符合逻辑的,真正的服务器是要具备并发处理的。而多线程恰好能解决这个问题,我们来看修改之后的服务端代码





public class TCPServer {
   public static void main(String[] args) throws IOException {
       receive();
   }
   private static void receive() throws IOException {
       System.out.println("Server Start...");
       //创建服务端Socket并明确端口号
       ServerSocket serverSocket = new ServerSocket(10004);
       while (true){
           //获取到客户端的Socket
           final Socket socket = serverSocket.accept();
           //通过线程执行客户端请求
           new Thread(new Runnable() {
               @Override
               public void run()
{
                   try {
                       //通过客户端的Socket获取到输入流
                       InputStream is = socket.getInputStream();
                       //通过输入流获取到客户端传递的数据
                       byte[] bytes = new byte[1024];
                       int len = is.read(bytes);
                       System.out.println(new String(bytes,0,len));
                       //将客户端发来的数据原封不动返回
                       OutputStream os = socket.getOutputStream();
                       os.write(new String(bytes,0,len).getBytes());
                       //关闭连接
                       socket.close();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
           }).start();
       }
   }
}



运行效果适合加线程之前是一样的,但这种方式效率更高。




本篇文章叙述了基于UDP和TCP的Socket,UDP是无连接的,所以UDP Socket在发送数据的时候只需要目标IP和端口即可发送。TCP是面向连接的并且是基于client-server模式,在传输数据前需要进行连接,可以通过多线程技术实现并发处理客户端请求。本篇文章内容还是比较简单的,希望大家能把文章中代码自己敲一遍,掌握Socket的同时也能让你自己UDP和TCP的理解更加深刻。







推荐:Python数据分析与大数据处理从入门到精通

编辑推荐:通过核心概念剖析、45个“新手问答”、17个章节的“实训”、3个项目综合实战、50道Python面试题精选,教你轻松玩转数据分析与大数据处理。

如何购买:点击小程序购买或阅读全文了解更多,也可坚持留言打卡获得!


如何赠送:留言集赞数大于30赞且排名第一的同学赠送一本,集赞截止时间2019.11.19  23:00,定价89RMB。

觉得本文对你有帮助?请分享给更多人

关注「全栈开发者社区」加星标,提升全栈技能



好文章,我在看❤️


推荐阅读
  • 本文基于Java官方文档进行了适当修改,旨在介绍如何实现一个能够同时处理多个客户端请求的服务端程序。在前文中,我们探讨了单客户端访问的服务端实现,而本篇将深入讲解多客户端环境下的服务端设计与实现。 ... [详细]
  • 使用Java计算两个日期之间的月份数
    本文详细介绍了利用Java编程语言计算两个指定日期之间月份数的方法。文章通过实例代码讲解了如何使用Joda-Time库来简化日期处理过程,旨在为开发者提供一个高效且易于理解的解决方案。 ... [详细]
  • Hadoop MapReduce 实战案例:手机流量使用统计分析
    本文通过一个具体的Hadoop MapReduce案例,详细介绍了如何利用MapReduce框架来统计和分析手机用户的流量使用情况,包括上行和下行流量的计算以及总流量的汇总。 ... [详细]
  • Java连接MySQL数据库的方法及测试示例
    本文详细介绍了如何安装MySQL数据库,并通过Java编程语言实现与MySQL数据库的连接,包括环境搭建、数据库创建以及简单的查询操作。 ... [详细]
  • Java中提取字符串的最后一部分
    本文介绍了如何使用Java中的substring()和split()方法来提取字符串的最后一部分,特别是在处理包含特殊字符的路径时的方法与技巧。 ... [详细]
  • 本文旨在探讨Swift中的Closure与Objective-C中的Block之间的区别与联系,通过定义、使用方式以及外部变量捕获等方面的比较,帮助开发者更好地理解这两种机制的特点及应用场景。 ... [详细]
  • 最近遇到了一个关于单链表的编程问题,这是来自福富公司的笔试题目。以往我通常使用C语言来解决这类问题,但这次决定尝试用Java来实现。该题目要求实现一个单链表,并完成特定的方法。 ... [详细]
  • 设计一个算法,用于计算给定字符串中出现的不同ASCII字符数量。该任务将重点考察字符串处理、集合操作以及基础的输入输出技术。 ... [详细]
  • Java多线程售票案例分析
    本文通过一个售票系统的实例,深入探讨了Java中的多线程技术及其在资源共享和并发控制中的应用。售票过程涉及查询、收款、找零和出票等多个步骤,其中对总票数的管理尤为关键。 ... [详细]
  • 编码unicode解决了语言不通的问题.但是.unicode又有一个新问题.由于unicode是万国码.把所有国家的文字都编进去了.这就导致一个unicode占用的空间会很大.原来 ... [详细]
  • 1、编写一个Java程序在屏幕上输出“你好!”。programmenameHelloworld.javapublicclassHelloworld{publicst ... [详细]
  • 深入理解线程池及其基本实现
    本文探讨了线程池的概念、优势及其在Java中的应用。通过实例分析不同类型的线程池,并指导如何构建一个简易的线程池。 ... [详细]
  • 探讨 try-finally 结构中 finally 块的执行情况
    本文深入分析了 Java 中 try-finally 结构的执行机制,特别是探讨了在不同情况下 finally 块是否会得到执行。 ... [详细]
  • D17:C#设计模式之十六观察者模式(Observer Pattern)【行为型】
    一、引言今天是2017年11月份的最后一天,也就是2017年11月30日,利用今天再写一个模式,争取下个月(也就是12月份& ... [详细]
  • 想把一组chara[4096]的数组拷贝到shortb[6][256]中,尝试过用循环移位的方式,还用中间变量shortc[2048]的方式。得出的结论:1.移位方式效率最低2. ... [详细]
author-avatar
可卡因
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有