跳转到主内容
思享编程网:思考分享,玩转编程世界!

四十三、网络编程(下)——TCP 编程与 HTTP 入门

😫 痛点引入 :UDP 发出去就不管了,万一丢包怎么办?

文件上传必须每字节都不能少!

TCP 协议应运而生——面向连接、可靠传输、三次握手确认!

☎️ 下篇手写 TCP 客户端-服务端、文件上传、多线程并发服务器,最后揭秘网页背后的 HTTP 协议!

一、TCP 协议——面向连接的「电话」☎️ 1.1 回顾:UDP vs TCP 对比项UDP(上篇)TCP(本篇) 连接性无连接 ❌面向连接 ✅可靠性不可靠(可能丢包)可靠(确认机制)✅效率高较低类比发短信、寄信 📮打电话 ☎️区分发送端 / 接收端客户端 / 服务端 1.2 TCP 三次握手(面试必问!

📝)

客户端 服务端

| | | ① SYN (我想连接) | | -----------------------------> | | | | ② SYN+ACK (可以,我也准备好了) | | <----------------------------- | | | | ③ ACK (收到,开始传数据!

) | | -----------------------------> | | | ✅ 三次握手完成,连接建立!

💡 为什么是三次?

两次可能死锁(服务端以为连上了,客户端其实没收到确认),三次才能保证双方都说清楚!

二、TCP 核心类 🔧 2.1 两个套接字 类名角色作用获取方式 Socket客户端套接字连接服务端、发送/接收数据new Socket(ip, port)ServerSocket服务端套接字监听端口、接收客户端连接new ServerSocket(port) 2.2 核心方法速查 Socket 常用方法 : 方法功能

getInputStream()获取输入流,读取对方发来的数据 📥getOutputStream()获取输出流,向对方发送数据 📤shutdownOutput()关闭输出流(发送结束标记)⚠️close()关闭连接

ServerSocket 常用方法 : 方法功能

accept()接收客户端连接,返回客户端 Socket(阻塞)

2.3 数据交互方式

客户端发送 → 服务端读取:

客户端:getOutputStream().write(...) 服务端:getInputStream().read(...)

客户端读取 ← 服务端发送: 客户端:getInputStream().read(...) 服务端:getOutputStream().write(...)

三、TCP 基本通信 💬 3.1 客户端代码

import java.net.Socket;

import java.io.OutputStream;

public class TCP_Client { public static void main(String[] args) throws Exception { // ⚠️ new Socket() 就会触发三次握手!

// 成功说明连接建立 ✅,失败抛出异常 Socket s = new Socket("192.168.26.23", 8888);

// 发送消息给服务端 OutputStream os = s.getOutputStream(); os.write("江总你好!

".getBytes());

s.close(); System.out.println("客户端发送完成!

☎️"); } }

3.2 服务端代码

import java.net.ServerSocket;

import java.net.Socket; import java.io.InputStream;

public class TCP_Server { public static void main(String[] args) throws Exception { System.out.println("服务端启动,等待连接...📞");

// 1. 创建服务端,指定端口 ServerSocket ss = new ServerSocket(8888);

// 2. accept() 阻塞等待客户端连接 Socket client = ss.accept(); System.out.println("客户端已连接:" + client.getInetAddress());

// 3. 读取客户端消息 InputStream is = client.getInputStream(); byte[] buf = new byte[1024]; int len = is.read(buf); // read() 阻塞,直到读完 String msg = new String(buf, 0, len); System.out.println("收到:" + msg);

client.close(); } }

3.3 ⚠️ TCP 编程注意点 服务端必须先启动 !

否则客户端连接失败

new Socket()触发三次握手

,服务端没启动就抛异常

accept()

阻塞 ,直到有客户端连接

read()

阻塞 ,直到读到数据或对方关闭流 四、TCP 双向通信 💬 4.1 服务端(收消息 + 回复)

import java.net.*;

import java.io.*; import java.util.Scanner;

public class TCP_ServerPro { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(8888); Scanner sc = new Scanner(System.in); System.out.println("服务端启动...📞");

Socket client = ss.accept(); System.out.println("客户端连接:" + client.getInetAddress());

while (true) { // 1. 读取客户端消息 InputStream is = client.getInputStream(); byte[] buf = new byte[1024]; int len = is.read(buf); String msg = new String(buf, 0, len); System.out.println("客户端:" + msg);

// 2. 回复客户端 System.out.print("请输入回复:"); String reply = sc.next(); OutputStream os = client.getOutputStream(); os.write(reply.getBytes()); } } }

4.2 客户端(发消息 + 收回复)

import java.net.*;

import java.io.*; import java.util.Scanner;

public class TCP_ClientPro { public static void main(String[] args) throws Exception { Socket s = new Socket("192.168.26.23", 8888); Scanner sc = new Scanner(System.in);

while (true) { // 1. 发送消息 System.out.print("请输入消息:"); String msg = sc.next(); OutputStream os = s.getOutputStream(); os.write(msg.getBytes());

// 2. 接收服务端回复 InputStream is = s.getInputStream(); byte[] buf = new byte[1024]; int len = is.read(buf); System.out.println("服务端回复:" + new String(buf, 0, len)); } } }

五、TCP 文件上传 📤 5.1 需求 客户端上传图片到服务端,服务端保存后给客户端响应。

5.2 客户端(读文件 + 上传)

import java.net.*;

import java.io.*;

public class TCP_FileClient { public static void main(String[] args) throws Exception { Socket s = new Socket("127.0.0.1", 9999);

// 1. 读取本地文件 FileInputStream fis = new FileInputStream("D:/1.jpg"); OutputStream os = s.getOutputStream();

// 2. 循环写出(上传)文件数据 byte[] buf = new byte[1024]; int len; while ((len = fis.read(buf)) != -1) { os.write(buf, 0, len); }

// ⚠️ 关键!

告诉服务端"我传完了" s.shutdownOutput();

fis.close();

// 3. 读取服务端响应 InputStream is = s.getInputStream(); byte[] respBuf = new byte[1024]; int respLen = is.read(respBuf); System.out.println("服务端:" + new String(respBuf, 0, respLen));

s.close(); } }

5.3 服务端(收文件 + 保存 + 响应)

import java.net.*;

import java.io.*; import java.util.Random;

public class TCP_FileServer { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(9999); System.out.println("文件服务器启动...📤");

Socket client = ss.accept(); System.out.println("客户端上传:" + client.getInetAddress());

// 1. 读取客户端上传数据 InputStream is = client.getInputStream();

// 2. 生成随机文件名(防止覆盖) Random r = new Random(); FileOutputStream fos = new FileOutputStream( "D:/upload/" + r.nextInt(Integer.MAX_VALUE) + ".jpg" );

byte[] buf = new byte[1024]; int len; while ((len = is.read(buf)) != -1) { fos.write(buf, 0, len); // 保存到磁盘 } fos.close();

// 3. 给客户端响应 OutputStream os = client.getOutputStream(); os.write("上传成功!

✅".getBytes());

client.close(); } }

5.4 ⚠️ shutdownOutput() —— 文件上传的灵魂

不用 shutdownOutput():

服务端 read() 永远阻塞,不知道客户端传完了 😱

用了 shutdownOutput(): 客户端调用后,发送一个"结束标记" 服务端 read() 收到 -1,跳出循环 ✅

一句话 :

shutdownOutput()

= 告诉对方"我说完了,你可以处理了"!

六、TCP 多线程并发服务器 🧵 6.1 为什么需要多线程?

单线程服务器:

客户端A 连接 → 服务器处理A → 处理完才能处理B → 客户端B 等着,体验极差 ❌

多线程服务器: 客户端A 连接 → 开线程1 处理A 客户端B 连接 → 开线程2 处理B → 同时处理,互不影响 ✅

6.2 多线程服务端代码

import java.net.*;

import java.io.*; import java.util.Random;

public class TCP_MultiThreadServer { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(9999); System.out.println("多线程服务器启动...🧵");

while (true) { Socket client = ss.accept(); // 等待客户端 System.out.println("新客户端:" + client.getInetAddress());

// 为每个客户端开启独立线程!

new Thread(() -> { try { // 接收文件 InputStream is = client.getInputStream(); Random r = new Random(); FileOutputStream fos = new FileOutputStream( "D:/upload/" + r.nextInt(Integer.MAX_VALUE) + ".jpg" );

byte[] buf = new byte[1024]; int len; while ((len = is.read(buf)) != -1) { fos.write(buf, 0, len); } fos.close();

// 响应客户端 OutputStream os = client.getOutputStream(); os.write("上传成功!

✅".getBytes());

client.close(); System.out.println("客户端上传完成!

"); } catch (Exception e) { e.printStackTrace(); } }).start(); // 启动线程!

} } }

6.3 启动多个客户端测试

public class TCP_MultiClientTest {

public static void main(String[] args) { // 同时启动 3 个客户端,并发上传 for (int i = 0; i < 3; i++) { new Thread(() -> { try { Socket s = new Socket("127.0.0.1", 9999); FileInputStream fis = new FileInputStream("D:/1.jpg"); OutputStream os = s.getOutputStream();

byte[] buf = new byte[1024]; int len; while ((len = fis.read(buf)) != -1) { os.write(buf, 0, len); } fis.close(); s.shutdownOutput();

// 接收响应 InputStream is = s.getInputStream(); byte[] resp = new byte[1024]; int respLen = is.read(resp); System.out.println(Thread.currentThread().getName() + ":" + new String(resp, 0, respLen)); s.close(); } catch (Exception e) { e.printStackTrace(); } }).start(); } } }

💡 多线程上传优势 : 多个客户端 同时上传 ✅ 每个客户端独占线程,互不干扰 服务端 持续运行 ,不用重启 七、HTTP 协议入门——网页背后的原理 🌐 7.1 HTTP 是什么 HTTP(HyperText Transfer Protocol) :超文本传输协议,应用层最常用的协议。

7.2 HTTP 请求格式

GET /index.html HTTP/1.1 ← 请求行(方法 + 路径 + 版本)

Host: www.example.com ← 请求头 User-Agent: Mozilla/5.0 ← 空行(必须!

) [请求体] ← GET 请求通常没有

7.3 HTTP 响应格式

HTTP/1.1 200 OK ← 状态行(版本 + 状态码 + 消息)

Content-Type: text/html ← 响应头 Content-Length: 1234 ← 空行(必须!

... ← 响应体(网页内容)

7.4 常见状态码 状态码含义 200请求成功 ✅301永久重定向302临时重定向404资源未找到 ❌500服务器内部错误 ⚠️ 7.5 💡 TCP 与 HTTP 的关系

TCP 是传输层协议 → 负责可靠传输数据 ☎️

↓ HTTP 是应用层协议 → 定义数据格式(请求头/响应头)🌐 ↓ HTTP 底层使用 TCP 传输!

一句话 :HTTP = 带格式的 TCP!

本篇总结 📝 TCP 协议 ☎️:面向连接、可靠传输、三次握手确认 三次握手 📝:SYN → SYN+ACK → ACK,保证双方都确认连接 Socket vs ServerSocket 🔧:客户端

new Socket(ip,port)

、服务端

new ServerSocket(port)

+

accept()

数据交互 📥📤:

getInputStream()

读、

getOutputStream()

写 TCP 双向通信 💬:客户端发→服务端收→服务端回→客户端收 文件上传 📤:客户端读文件写服务端 +

shutdownOutput()

发送结束标记 多线程服务器 🧵:

while(true) { accept(); new Thread(...).start(); }

支持并发 HTTP 协议 🌐:应用层协议,定义请求/响应格式,底层用 TCP 传输 作者 :书源丶 发布平台 :CSDN

相关文章