TCP/UDP

发布时间:2026/10/31 20:17:46
TCP/UDP 传输层端口号0~65535 但是1~1023基本被占用了而且占用的程序好像活着的不多了用的话基本就是从1024开始往后排。常用的端口号ssh:22 Liunx的远程连接http:80https:443ftp:21文件传输协议telnet:23(明文远程连接)DNS53常问的:一个进程可以bind多个端口号一个进程里面是有好多个线程每一个线程bind一个 端口号是可以的。一个端口号是不可以被多个进程bind的。五元组源ip 目的ip 源端口 目的端口 协议类型tcp/udpUDP无连接不可靠传输面向数据报全双工┌─────────┬──────────┬─────────┬────────────┐│ 16位源端口 │16位目的端口 │ 16位长度 │ 16位校验和 │ ← 首部 8 字节├─────────┴──────────┴─────────┴────────────┤│ 数据payload │└────────────────── ─────────────────────────┘16位长度就是传输的数据的大小 16位bit—64kb 可以传的东西大小非常小如果需要传输大的数据大于64kb只能分多次去发送去拆包再组包。16位校验和数学公式验证这个传输前后的数据有没有受损坏。UDP 报头中的校验和是个 2 字节的数据使用于不容易丢包的地方对传输效率高的地方传小的数据的地方。代码实现服务端UdpEchoServerpackage Socket; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class UdpEchoServer { // 服务端使用的 DatagramSocket 对象 private DatagramSocket socket null; // 构造方法端口号 public UdpEchoServer(int prot) throws SocketException { socket new DatagramSocket(prot); } //启动服务端 //处理客户端请求 public void start() throws IOException { System.out.println(Server start); while (true){ //作为服务器先接收信息 DatagramPacket requsetPacket new DatagramPacket(new byte[1024] , 1024); socket.receive(requsetPacket); //得到的数据报转化为string类型的 String request new String(requsetPacket.getData() ,0, requsetPacket.getLength()); //在服务端去处理这个数据 String response possen(request); //重新封装返回的数据报 DatagramPacket responsePacket new DatagramPacket(response.getBytes(),response.getBytes().length, requsetPacket.getSocketAddress()); //发送数据报 socket.send(responsePacket); //打印日志 System.out.printf([%s:%d] res:%s rep:%s \n , requsetPacket.getAddress().toString(),requsetPacket.getPort(), request,response); } } //处理业务逻辑 public String possen(String request) { return request; } //主方法 public static void main(String[] args) throws IOException { UdpEchoServer udpEchoServer new UdpEchoServer(9999); udpEchoServer.start(); } }客户端UdpEchoClientpackage Socket; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.util.Scanner; public class UdpEchoClient { // 服务端使用的 DatagramSocket 对象 private DatagramSocket socket null; // 服务器端口 private int serverPort; // 服务器ip private String serverIp; // 构造方法 创建客户端 需要服务器的端口和ip public UdpEchoClient(String serverIp ,int serverPort ) throws SocketException { socket new DatagramSocket(); this.serverPort serverPort; this.serverIp serverIp; } // 启动客户端的 public void start() throws IOException { Scanner scanner new Scanner(System.in); System.out.println(client start); while (true){ //作为客户端需要先发送 System.out.printf(-); //读取用户输入 String text scanner.nextLine(); //封装成数据包发送 需要服务器的ip和端口 DatagramPacket response new DatagramPacket(text.getBytes(),text.getBytes().length, InetAddress.getByName(this.serverIp),this.serverPort); //发送数据包 socket.send(response); //接收服务器回显的数据包 需要一个数据包来接收 DatagramPacket resquestPath new DatagramPacket(new byte[1024] , 1024); socket.receive(resquestPath); //将数据包中的数据转换成字符串 String ressquest new String(resquestPath.getData(),0, resquestPath.getLength()); //打印输出 System.out.println(ressquest); } } // 程序的入口的 public static void main(String[] args) throws IOException { UdpEchoClient udpEchoClient new UdpEchoClient(127.0.0.1,9999); udpEchoClient.start(); } }TCP有连接可靠传输面向字节流全双工0 15 16 31 ------------------------------------------------ | 16位源端口号 | 16位目的端口号 | 第1行 ------------------------------------------------ | 32位序号 (seq) | 第2行 --------------------------------------------------- | 32位确认序号 (ack) | 第3行 ------------------------------------------- |4位首部 |保留(6)|URG|ACK|PSH|RST|SYN|FIN|16位窗口 | 第4行 | 长度 | | | | | | | | 大小 | ------------------------------------------- | 16位检验和 | 16位紧急指针 | 第5行 ------------------------------------------------ | 选项长度可变0~40字节 | 第6行 --------------------------------------------------- | 数据 (payload) | 第7行 ---------------------------------------------------16位检验和和UDP一样4位报头长度4bit范围是0~15 也就是0~4*1560必须的4的倍数啊6位标识符ack(确认报文) ret(复位报文) syn(同步报文) fin(结束报文)基于可靠性的特性1.确认应答每次的发送数据都会收到一个ack也会携带下一个的起始点。2.超时重传 --- 可靠传输丢包数据丢了ack丢了重新传报都是因为没有传回的ack而需要重传的超时时间指数递增累计到一定次数 → 认为网络/对端异常强制关闭连接如果是数据丢了重新传无事发生如果是ack丢了重新传数据会重复是根据传数据的序列号去重3.连接管理连接的时候三次握手也可以是四次握手 发ack 和 syn 分开发listen准备就绪establisten:连接完毕可以通信了syn_sent:发送连接很快syn_rcvd:接收连接很快断开连接的时候四次挥手服务器应用层是否还有未发送的数据决定了是不是可以 合并SYN和ACKFIN_WAIT_1:主动关闭方发出的FIN后还没有收到ACK的状态。FIN_WAIT_2:主动关闭方收到ACK还没有收到FIN的状态TIME_WAIT:主动关闭方收到FIN的状态等待 2MSL 后进入 CLOSEDCLOSE_WAIT:被动关闭方收到FIN的进入还没有调用CLOSE()的方法状态。LAST_ACK:已发FIN等最后ACKCLOSED:关闭连接的状态。4.滑动窗口每次发一个数据就需要恢复一次ACK,影响效率会变慢可以一组一组发ACK返回下 一个数据的序号。会出现的问题ACK丢失虽然传输期间有一部分ACK丢失但是由于ACK返回的都是下一个的序号后面最后的 ACK如果是正常的那就是其实证明了数据的传输没有问题数据丢失数据丢失ACK返回的一直都是确实的的序号。(问题不大)处理数据的时候都是先进入接收方的数据缓冲区当需要不不连续的时 候后面的数 据就先放到缓存器等待等正确需要数据序号来了再继续处理排序去重 也是都是在数 据缓冲区里面处理的快重传发送端连续收到几次相同的ACK如好几次ACK1001立刻重传丢失的数 据不用等超时。补齐后回复的是总的数据的下一个序号。5.流量控制16位窗口大小里面的就是ACK也返回的是数据缓存器剩余的空间。问题如果此时的缓存器里面的空间为0了就不发了那发送方咋知道啥时候有空间。A会每隔一段时间发送一个空的报文窗口探测{去“敲门”问 B“哥们腾出空间了吗现在能收多少”}。发给BB如果有空间就回复空间 了如果还是没有B还是返回0.6.拥塞控制传输数据嘛一下子太大的话会导致数据丢失。先设定一个阈值最开始从1份开始 -----份不是固定值 是自己设置的纵坐标嘛就是阻塞窗口 实际发送量 min(拥塞窗口, 接收窗口)到了阈值之前发送的数据大小都是以指数增长。到了阈值还没有阻塞的话后面开始匀速增长。如果期间数据缓存器产生了阻塞开始缩小窗口设定新的阈值为产生阻塞位置一半老办法直接回到1份继续上述的过程新办法到达新的阈值的位置开始匀速增长7.延迟应答ACK不是看到一组就返回一次而且延迟回复让数据多传几次再去返回可以提高效率减少了ACK报的数量延迟 时间后窗口变大了发送方就能发更多吞吐量就上去了限制不能死等受“每2个包回一次”和“最大延迟200ms”的限制。8.捎带应答类似建立连接的时候三次握手第二次是就是服务端返回了syn和ack 就是捎带应答。每次回答的时候如果还是别的数据可以顺带一个发送。9.面向字节流---粘包问题TCP是面向字节流的不像UDP是有自己的DatagramPacket以至于发送的时候会边界丢失不知道那些字节组合是一个完整的数据解决分隔符去限制当输入到什么时候就判定为输入完毕就是一组数据处理的时候也是当出现这个符号的时候前面就是完整的数据。比如\n或者\r\n作为边界数据开头属性在每个发的数据前面加上字符的长度处理就按长度处理。10.异常处理1.进程奔溃退出的状态就是和正常的四次挥手状态一样。虽然是进程没有了但是连接还是在的。2.主机正常关机强制结束所有的进程电脑关机会显示有一个正在阻止关机下面的强制关 机正常的四次挥手关闭是为了让双方都删除对方的信息服务端发好多了FIN后发现一直都没有回应那就算了直接单方面断开删除客户端的连接信息。3.主机断电关机当接收方的直接断电跑路此时发送方发FIN或者别的什么都不会有回应的无论多少次都是超时红色感叹号就需要放弃了最后再发RST复位报文就算放弃了都是陌生人了。4.网线断开属于是双方无法正常通信 但是双方不知道是什么原因陌生人了还是有事 了发送方会发一个”心跳包“ --什么都没有只为了让对方返回ACK如果没有回复就断开连接删除信息不理我算了删了代码实现服务端TcpEchoServerpackage Socket; import jdk.jshell.spi.ExecutionControl; 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; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TcpEchoServer { // 服务端使用的 ServerSocket 对象 private ServerSocket serverSocket null; //构造方法需要服务器端口 public TcpEchoServer(int port) throws IOException { serverSocket new ServerSocket(port); } public void start() throws IOException { System.out.println(TCPserver.start); // ExecutorService cached Executors.newCachedThreadPool(); while (true){ //把已经连接的客服端弄成socker Socket socket serverSocket.accept(); //进入这个方法 cached.submit(() - processConnection(socket)); //也可以用多线程来运行 // Thread thread new Thread(() - { // processConnection(socket); // }); } } private void processConnection(Socket socket) { System.out.printf([%s:%d]上线了\n ,socket.getInetAddress(),socket.getPort()); try { //负责读写的 InputStream inputStream socket.getInputStream(); OutputStream outputStream socket.getOutputStream(); //负责发送和接收的 Scanner scanner new Scanner(inputStream); PrintWriter writer new PrintWriter(outputStream); while (true){ if (!scanner.hasNext()){ System.out.printf([%s:%d]下线了\n ,socket.getInetAddress(),socket.getPort()); break; } //接收的东西 String request scanner.next(); //处理一下 准备回显回去 String response process(request); //发回去从服务器到客户端 writer.println(response); // 刷新缓冲区 确保数据被发送出去 writer.flush(); System.out.printf([%s:%d] req:%s res:%s\n , socket.getInetAddress(),socket.getPort(),request,response); } } catch (IOException e) { throw new RuntimeException(e); } finally { try { // 关闭连接 socket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } public String process(String request ) { return request; } public static void main(String[] args) throws IOException { TcpEchoServer tcpEchoServer new TcpEchoServer(9999); tcpEchoServer.start(); } }客户端TcpEchoClientpackage Socket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; import java.util.ServiceConfigurationError; public class TcpEchoClient { // 客户端使用的 Socket 对象 private Socket socket null; //构造方法 需要服务器的ip和端口 public TcpEchoClient(String serverIp , int serverPort) throws IOException { socket new Socket(serverIp , serverPort); } public void start(){ System.out.println(client start!); try{ //负责读写的 InputStream inputStream socket.getInputStream(); OutputStream outputStream socket.getOutputStream(); //负责处理读写的 Scanner scannerRead new Scanner(inputStream); PrintWriter writer new PrintWriter(outputStream); //负责输入的 Scanner scanner new Scanner(System.in); while (true){ System.out.printf(-); String request scanner.next(); //发给服务器的东西 writer.println(request); writer.flush(); //接收回显 if (!scannerRead.hasNext()){ break; } //读回显打印 String response scannerRead.next(); System.out.println(response); } }catch (IOException E){ E.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpEchoClient tcpEchoClient new TcpEchoClient(127.0.0.1, 9999); tcpEchoClient.start(); } }

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询