目录

  • 一、网络编程套接字
  • 二、UDP Socket
    • 2.1 客户端服务器程序-回显服务(EchoServer)
      • 2.1.1 UdpEchoServer
      • 2.1.2 UdpEchoClient
      • 2.1.3 一个简单程序
  • 三、TCP 客户端服务器程序
    • 3.1 TCP API

一、网络编程套接字

网络编程套接字就是操作系统给应用程序提供的一组API(叫做socket API)。

socket 可以视为是应用层和传输层之间的通信桥梁。

传输层的核心协议有两种:TCP和UDP。

TCP:有连接;可靠传输;面向字节流;全双工;

UDP:无连接;不可靠传输;面向数据报;全双工;

  1. 有连接和无连接
    有连接:像打电话,得先接通,才能交互数据
    无连接:像发微信,不需要接通,直接就能发数据

  2. 可靠传输与不可靠传输
    可靠:传输过程中,发送方知道接收方有没有收到数据
    不可靠:参考无连接,微信直接可以发消息,不知道对方有没有看见这个消息。

  3. 面向字流/数据报
    面向字节流:以字节为单位进行传输,类似于文件操作中的字节流。
    面向数据报:以数据报为单位进行传输,一个数据报会明确大小,一次发送/接收一个完整的数据报,不能是半个数据报。

  4. 全双工/半双工
    全双工:一条链路,双向通信。
    半双工:一条链路,单向通信。

二、UDP Socket

UDP Socket 中主要涉及两个类:DatagramSocket 和 DatagramPacket.

DatagramSocket 的一个对象就对应操作系统中的一个socket文件。

DatagramPacket代表了一个UDP数据报,使用UDP传输数据的基本单位。

2.1 客户端服务器程序-回显服务(EchoServer)

回显:请求内容是啥,得到的响应就是啥。

这里在我们的idea下先建立一个包network,在这个包下建立两个类分别是服务器UdpEchoServer和客户端UdpEchoClient。

2.1.1 UdpEchoServer

  1. 进行网络编程,第一步就需要先准备好 socket实例,这是进行网络编程的大前提
private DatagramSocket socket = null;
  1. 此处在构造服务器这边的socket对象的时候,就需要显式的绑定一个端口号
    端口号是用来区分一个应用程序的,主机收到网卡上的数据的时候,这个数据该给哪个程序?
    抛出异常的原因:1.端口号可能已经被占用了。2.每个进程能够打开的文件个数是有限的。
public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}
  1. 启动服务器,服务器是被动接收请求的一方,主动发送请求的是客户端,DatagramPacket刚才说过是表示一个UDP数据报,发送一次数据就是发送DatagramPacket,接收一次数据也就是在收一个DatagramPacket。
// 3. 把响应写回到客户端。DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());socket.send(responsePacket);System.out.printf("[%s:%d] req:%s,resp:%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);}}// 由于是回显服务,响应就和请求一样了// 实际上对于一个真实的服务器来说,这个过程是复杂的,为了实现这个过程,可能需要几行甚至几万行private String process(String request) {return request;}
  • 这里requestPacket.getLength()这个长度不一定是1024,可能此处的UDP数据报最长是1024,实际的数据可能不够1024。
  • 注意这里send方法的参数,也是DatagramPacket,需要把响应数据先构造成一个DatagramPacket再进行发送。
  • response.getByte()这里的参数也不再是一个空的数组,response是刚才根据请求计算得到的响应。DatagramPacket里面的数据就是String response的数据。
  • requestPacket.getSocketAddress();这个参数的作用就是表示要把数据发给哪个地址+端口。
  • 完整代码:

    package network;import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.SocketException;@SuppressWarnings({"all"})
    public class UdpEchoServer {// 进行网络编程,第一步就需要先准备好 socket实例,这是进行网络编程的大前提private DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}// 启动服务器public void start() throws IOException {System.out.println("启动服务器");// UDP 不需要建立连接,直接接收从客户端来的数据即可~while (true) {// 1. 读取客户端发来的请求DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);socket.receive(requestPacket);// 为了接收数据,需要先准备一个空的DatagramPacket对象,由receive来进行填充数据// 把 DatagramPacket 解析成一格StringString request = new String(requestPacket.getData(), 0, requestPacket.getLength(), "utf-8");// 2. 根据请求计算响应( 由于是一个回显服务器,2省略)String response = process(request);// 3. 把响应写回到客户端。DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());socket.send(responsePacket);System.out.printf("[%s:%d] req:%s,resp:%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);}}// 由于是回显服务,响应就和请求一样了// 实际上对于一个真实的服务器来说,这个过程是复杂的,为了实现这个过程,可能需要几行甚至几万行private String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer server = new UdpEchoServer(9090);server.start();}
    }

    2.1.2 UdpEchoClient

    在客户端构造 socket 对象的时候,就不再手动指定端口号,使用无参版本的构造方法。
    不指定端口号,是让操作系统自己分配一个空闲的端口号。

    package network;import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.SocketException;
    import java.util.Scanner;@SuppressWarnings({"all"})
    public class UdpEchoClient {private DatagramSocket socket = null;private String serverIp;private int serverPort;public UdpEchoClient(String ip, int port) throws SocketException {socket = new DatagramSocket();serverIp = ip;serverPort = port;}public void start() throws IOException {Scanner scanner = new Scanner(System.in);while (true) {// 1. 先从控制台读取一个用户输入的字符串System.out.println("-> ");String request = scanner.next();// 2. 把这个用户输入的内容构造成一格UDP请求,并发送//    构造的请求里包含两部分信息://    1) 数据的内容,request 字符串//    2) 数据要发送给谁  服务器的IP + 端口DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,InetAddress.getByName(serverIp),serverPort);socket.send(requestPacket);// 3. 从服务器读取响应数据,并解析DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);socket.receive(responsePacket);// 4. 把响应结果显示到控制台上。String response = new String(responsePacket.getData(),0,responsePacket.getLength(),"UTF-8");System.out.printf("req: %s, resp: %s\n",request,response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);client.start();}
    }

    2.1.3 一个简单程序

    写一个翻译程序(英译汉)

    package network;import java.io.IOException;
    import java.net.SocketException;
    import java.util.HashMap;@SuppressWarnings({"all"})
    public class UdpDictServer extends UdpEchoServer{private HashMap<String, String> dict = new HashMap<>();public UdpDictServer(int port) throws SocketException {super(port);// 简单构造几个词dict.put("cat","小猫");dict.put("dog","小狗");dict.put("hello","你好");dict.put("pig","小猪");}@Overridepublic String process(String request) {return dict.getOrDefault(request, "该词无法被翻译");}public static void main(String[] args) throws IOException {UdpEchoServer server = new UdpEchoServer(9090);server.start();}
    }

    三、TCP 客户端服务器程序

    3.1 TCP API

    在TCP API 中,也是涉及到两个核心的类,
    ServerSocket(专门给TCP服务器用)
    Socket(既需要给服务器用,又需要给客户端用)

    前面基本都和UDP的差不多,区别就是这里的start方法,由于tcp是有连接的,不是一上来就能读数据,需要先建立连接(打电话);accept返回了一个socket对象,accept可以理解为接电话,那么接电话的前提就是有人给你打电话,如果无,那么这里accept就会阻塞。

    TcpEchoServer

    // listen -> 监听// 但是在 Java socket 中是体现不出来"监听"的含义的// 之所以这么叫,是操作系统原生的API 中有一个操作叫做listenprivate ServerSocket listenSocket = null;public TcpEchoServer(int port) throws IOException {listenSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");while (true) {// 由于 Tcp是有连接的,不能一上来就读取数据,而要先建立连接(接电话)// accept 就是在"接电话",前提是有人给你打电话// accept 返回了一个socket 对象,称为clientSocket,后续和客户端的沟通都是通过clientSocket来完成的// 进一步讲,listenSocket就干了一件事,就是接电话Socket clientSocket =  listenSocket.accept();processConnection(clientSocket);}}
    

    接下来我们来写processConnection()方法的代码。

    private void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 客户端建立连接!", clientSocket.getInetAddress().toString(), clientSocket.getPort());// 接下来处理请求和响应// 这里的针对 TCP socket 的读写就和文件读写是一模一样的try (InputStream inputStream = clientSocket.getInputStream()) {try (OutputStream outputStream = clientSocket.getOutputStream()) {// 循环的处理每个请求,分别返回响应Scanner scanner = new Scanner(System.in);while (true) {// 1. 读取请求if (!scanner.hasNext()) {System.out.printf("[%s:%d] 客户端断开连接!", clientSocket.getInetAddress().toString(), clientSocket.getPort());break;}// 此处用 Scanner 更方便,如果不用Scanner 就用原生的InputStream的readString request = scanner.next();// 2. 根据请求,计算响应String response = process(request);// 3. 把这个响应返回客户端// 为了方便响应,可以使用PrintWriter 把OutputStream包裹一下PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);// 刷新缓冲区,如果没有这个刷新,可能客户端就不能第一时间看到响应printWriter.flush();System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(), request, response);}}} catch (IOException e) {throw new RuntimeException(e);} finally {clientSocket.close();}}private String process(String request) {return request;}
    

    完整代码:

    package network;import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;@SuppressWarnings({"all"})
    public class TcpEchoServer {// listen -> 监听// 但是在 Java socket 中是体现不出来"监听"的含义的// 之所以这么叫,是操作系统原生的API 中有一个操作叫做listenprivate ServerSocket listenSocket = null;public TcpEchoServer(int port) throws IOException {listenSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");while (true) {// 由于 Tcp是有连接的,不能一上来就读取数据,而要先建立连接(接电话)// accept 就是在"接电话",前提是有人给你打电话// accept 返回了一个socket 对象,称为clientSocket,后续和客户端的沟通都是通过clientSocket来完成的// 进一步讲,listenSocket就干了一件事,就是接电话Socket clientSocket = listenSocket.accept();processConnection(clientSocket);}}private void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 客户端建立连接!", clientSocket.getInetAddress().toString(), clientSocket.getPort());// 接下来处理请求和响应// 这里的针对 TCP socket 的读写就和文件读写是一模一样的try (InputStream inputStream = clientSocket.getInputStream()) {try (OutputStream outputStream = clientSocket.getOutputStream()) {// 循环的处理每个请求,分别返回响应Scanner scanner = new Scanner(System.in);while (true) {// 1. 读取请求if (!scanner.hasNext()) {System.out.printf("[%s:%d] 客户端断开连接!", clientSocket.getInetAddress().toString(), clientSocket.getPort());break;}// 此处用 Scanner 更方便,如果不用Scanner 就用原生的InputStream的readString request = scanner.next();// 2. 根据请求,计算响应String response = process(request);// 3. 把这个响应返回客户端// 为了方便响应,可以使用PrintWriter 把OutputStream包裹一下PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);// 刷新缓冲区,如果没有这个刷新,可能客户端就不能第一时间看到响应printWriter.flush();System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(), request, response);}}} catch (IOException e) {throw new RuntimeException(e);} finally {clientSocket.close();}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
    }

    TcpEchoClient

    package network;import jdk.internal.util.xml.impl.Input;
    import org.omg.Messaging.SYNC_WITH_TRANSPORT;import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.util.Scanner;@SuppressWarnings({"all"})
    public class TcpEchoClient {// 用普通的 socket 即可,不用 ServerSocket了// 此处也不用手动给客户端指定端口号,让系统自由分配private Socket socket = null;public TcpEchoClient(String sreverIP, int sreverPort) throws IOException {// 其实这里是可以给的,但是这里给了之后,含义是不同的// 这里传入的ip和端口号的含义表示的不是自己绑定,而是表示和这个ip端口建立连接口socket = new Socket(sreverIP, sreverPort);}public void start() {System.out.println("和服务器连接成功!");Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream()) {try (OutputStream outputStream = socket.getOutputStream()) {while (true) {// 要做的事情,仍然是四个步骤// 1. 从控制台读取字符串System.out.println("-> ");String request = scanner.next();// 2. 根据读取的字符串, 构造请求,把请求发给服务器PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(request);printWriter.flush();// 如果不刷新,服务器可能不能及时看到数据// 3. 从服务器读取响应,并解析Scanner respScanner = new Scanner(System.in);String response = respScanner.next();// 4. 把结果显示到控制台上System.out.printf("req: %s, resp: %s\n",request, response);}}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);client.start();}
    }

    先启动服务器,再启动客户端

    服务器启动成功,我们启动客户端,可以发现和服务器连接成功!
    然后我们点到服务器这边观察一下,可以发现服务器已经和客户端建立连接,系统已经分配了端口号。



    那么我们结束客户端,看服务器这边的响应。


    我们可以发现这里的代码第一次accept结束之后,就会进入processConnection,在processConnection中又有一个循环,若processConnection里面的循环不停,processConnection就无法完成,就会导致外层循环无法进入下一轮,也就无法第二次调用accept了。

    我们可以通过多线程的方式来解决这个问题,让主线程循环调用accept,当有客户端连接上来的时候就让主线程创建一个新线程,由新线程负责客户端的若干个请求,这个时候多个线程看上去是同时执行的。

    这里我们新写一个类TcpThreadEchoServer,在原有的TcpEchoServer基础上修改以下部分代码即可。

         Thread t = new Thread(()->{processConnection(clinetSocket);});t.start();
    

    TcpThreadEchoServer

    package NetWork;import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;public class TcpThreadEchoServer {//    private ServerSocket listenSocket = null;private   ServerSocket serverSocket = null;public  TcpThreadEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");while (true){Socket clinetSocket = serverSocket.accept();Thread t = new Thread(()->{processConnection(clinetSocket);});t.start();}}private void processConnection(Socket clinetSocket) {System.out.printf("[%s:%d客户端建立连接!\n",clinetSocket.getInetAddress().toString(),clinetSocket.getPort());try(InputStream inputStream = clinetSocket.getInputStream()){try(OutputStream outputStream = clinetSocket.getOutputStream()){Scanner scanner = new Scanner(inputStream);//读取请求while (true){if(!scanner.hasNext()){System.out.printf("[%s:%d]客户端断开连接!\n",clinetSocket.getInetAddress().toString(),clinetSocket.getPort());break;}String request = scanner.next();String response = process(request);PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);//刷新缓冲区,如果没有这个刷新,可能客户端就不能第一时间看到响应结果。printWriter.flush();System.out.printf("[%s:%d] rep:%s,resp:%s\n",clinetSocket.getInetAddress().toString(),clinetSocket.getPort(),request,response);}}} catch (IOException e) {e.printStackTrace();}finally {//记得关闭操作try {clinetSocket.close();} catch (IOException e) {e.printStackTrace();}}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpThreadEchoServer server = new TcpThreadEchoServer(9090);server.start();}
    }

    那么之前为什么UDP版本的程序就不需要多线程就可以处理多个请求呢?

    因为UDP不需要连接,只需要一个循环就可以处理所有客户端的请求,但是TCP即需要处理连接,又需要处理一个连接中的多个请求。

JavaEE-网络编程相关推荐

  1. JavaEE 网络编程示例1 UDP套接字数据报编程 == 一发一收

    鲁吼呀,胶己人! 文章目录 JavaEE & 网络编程示例1 & UDP套接字数据报编程 ==> 一发一收 1. 协议分层(回顾) 2. 套接字 Socket 2.1 UDP与T ...

  2. JavaEE:网络编程套接字

    文章目录 一.网络编程基础 1.为什么需要网络编程 2.什么是网络编程 3.网络编程中的基本概念 ① 发送端和接收端 ② 请求和相应 ③ 客户端和服务端 ④ 常见的客户端服务端模型 二.Socket套 ...

  3. 【javaEE】网络编程套接字

    To u&me: 努力经营当下,直至未来明朗 文章目录 前言 一.网络编程(没时间可以跳过) 一)网络编程了解 二)相关基本概念 二.Socket套接字 三.数据报套接字通信(UDP) 写一个 ...

  4. 网络编程原理进阶___TCP/IP(javaee)

    点击跳转 本章重点 网络编程原理进阶 应用层 DNS 传输层 UDP TCP面试重点 `TCP`原理 确认应答 超时重传 连接管理(面试重点) 3次握手 4次挥手 滑动窗口 流量控制 拥塞控制 延时应 ...

  5. Android应用开发:网络编程-2

    网络编程 Java基础:网络编程 Uri.URL.UriMatcher.ContentUris详解 Android应用开发:网络编程1 Android应用开发:网络编程2 1. 使用HttpClien ...

  6. 你所需要的java网络编程大总结

    好好学java java知识分享/学习教程免费分享 关注 精彩内容 你所需要的java全套视频教程 你所需要的java电子图书 你所需要的大数据视频教程 你所需要的java练习项目 如 / 梦 上个月 ...

  7. java网络编程(七)

    戳上面的蓝字关注我们哦! 精彩内容 精选java等全套视频教程 精选java电子图书 大数据视频教程精选 java项目练习精选 猜数字小游戏 下面这个示例是一个猜数字的控制台小游戏.该游戏的规则是:当 ...

  8. java网络编程(六)

    戳上面的蓝字关注我们哦! 精彩内容 精选java等全套视频教程 精选java电子图书 大数据视频教程精选 java项目练习精选 网络编程示例 "实践出真知",所以在进行技术学习时, ...

  9. 第六周 Java语法总结_设计原则_工厂模式_单例模式_代理模式(静态代理_动态代理)_递归_IO流_网络编程(UDP_TCP)_反射_数据库

    文章目录 20.设计原则 1.工厂模式 2.单例模式 1)饿汉式 2)懒汉式 3.Runtime类 4.代理模式 1)静态代理 2)动态代理 动态代理模板 21.递归 22.IO流 1.File 2. ...

  10. 【JavaSE】网络编程(606~630)

    606.IO流与网络编程-每天一考 说明流的三种方式 流向:输入流.输出流 数据单位:字节流.字符流 流的单位:节点流.处理流 写出4个IO流中抽象基类,4个文件流,4个缓冲流 抽象基类:InputS ...

最新文章

  1. PCE:南农张瑞福组揭示微生物肥料菌种芽孢杆菌应对植物免疫防卫实现根际定殖的新策略...
  2. 手动创建swap分区
  3. How to enable coredump on centos 7.6
  4. dell存储Linux配置,Dell MD3200远程存储的特殊配置
  5. 我是如何用机器学习技术帮助 HR 省时间的
  6. (需求实战_进阶_01)SSM集成RabbitMQ 关键代码讲解、开发、测试
  7. MySQL “error C3646: 'fd': 未知重写说明符”
  8. ECharts三维图表
  9. pyspider all 只启动了_我是如何让微博绿洲的启动速度提升30%的(二)
  10. 运筹优化(十一)--无约束非线性规划
  11. PCL中的点云ICP配准(附源代码和数据)
  12. hexo version control
  13. C语言实现编译原理的LR分析法,编译原理LR(0)分析器(C语言).pdf
  14. DOTween 使用方法
  15. Mac itunes安装旧版本步骤
  16. autocad.net 画多段线_AutoCAD2016快速入门:绘制多段线
  17. 移动硬盘在计算机中不显示数据能恢复,移动硬盘无法访问提示'此卷不包含可识别的文件系统'怎么办?...
  18. 用友U9sv服务打开时报错内存入口检查失败,因为可用内存(371662848 字节)少于总内存的 5%
  19. 跨境电商支付方式之如何玩转跨境支付
  20. linux syscall

热门文章

  1. FOR循环及基础应用方式(输出一组字符串)
  2. 关于正则表达式的简单记忆
  3. 超级终端调用短信猫发送短信说明
  4. 信号测试点的选择与信号质量的评估
  5. 关于“996”的个人看法
  6. 随机密码生成。编写程序,在26个字母大小写和9个数字组成的列表中随机生成10个8位密码
  7. 正确采取Xss攻击防御措施
  8. Visual C++网络编程经典案例详解 第9章 实用播放器 多线程通信 线程间通信 根据播放列表音乐序号判断mp3播放顺序
  9. Vue下拉框动态加载数据
  10. PTA_7-1 2020年12月13日