四十三、网络编程(下)——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 客户端代码
importjava.net.Socket;importjava.io.OutputStream;publicclassTCP_Client{publicstaticvoidmain(String[]args)throwsException{// ⚠️ new Socket() 就会触发三次握手!// 成功说明连接建立 ✅,失败抛出异常Sockets=newSocket("192.168.26.23",8888);// 发送消息给服务端OutputStreamos=s.getOutputStream();os.write("江总你好!".getBytes());s.close();System.out.println("客户端发送完成!☎️");}}3.2 服务端代码
importjava.net.ServerSocket;importjava.net.Socket;importjava.io.InputStream;publicclassTCP_Server{publicstaticvoidmain(String[]args)throwsException{System.out.println("服务端启动,等待连接...📞");// 1. 创建服务端,指定端口ServerSocketss=newServerSocket(8888);// 2. accept() 阻塞等待客户端连接Socketclient=ss.accept();System.out.println("客户端已连接:"+client.getInetAddress());// 3. 读取客户端消息InputStreamis=client.getInputStream();byte[]buf=newbyte[1024];intlen=is.read(buf);// read() 阻塞,直到读完Stringmsg=newString(buf,0,len);System.out.println("收到:"+msg);client.close();}}3.3 ⚠️ TCP 编程注意点
- 服务端必须先启动!否则客户端连接失败
new Socket()触发三次握手,服务端没启动就抛异常accept()阻塞,直到有客户端连接read()阻塞,直到读到数据或对方关闭流
四、TCP 双向通信 💬
4.1 服务端(收消息 + 回复)
importjava.net.*;importjava.io.*;importjava.util.Scanner;publicclassTCP_ServerPro{publicstaticvoidmain(String[]args)throwsException{ServerSocketss=newServerSocket(8888);Scannersc=newScanner(System.in);System.out.println("服务端启动...📞");Socketclient=ss.accept();System.out.println("客户端连接:"+client.getInetAddress());while(true){// 1. 读取客户端消息InputStreamis=client.getInputStream();byte[]buf=newbyte[1024];intlen=is.read(buf);Stringmsg=newString(buf,0,len);System.out.println("客户端:"+msg);// 2. 回复客户端System.out.print("请输入回复:");Stringreply=sc.next();OutputStreamos=client.getOutputStream();os.write(reply.getBytes());}}}4.2 客户端(发消息 + 收回复)
importjava.net.*;importjava.io.*;importjava.util.Scanner;publicclassTCP_ClientPro{publicstaticvoidmain(String[]args)throwsException{Sockets=newSocket("192.168.26.23",8888);Scannersc=newScanner(System.in);while(true){// 1. 发送消息System.out.print("请输入消息:");Stringmsg=sc.next();OutputStreamos=s.getOutputStream();os.write(msg.getBytes());// 2. 接收服务端回复InputStreamis=s.getInputStream();byte[]buf=newbyte[1024];intlen=is.read(buf);System.out.println("服务端回复:"+newString(buf,0,len));}}}五、TCP 文件上传 📤
5.1 需求
客户端上传图片到服务端,服务端保存后给客户端响应。
5.2 客户端(读文件 + 上传)
importjava.net.*;importjava.io.*;publicclassTCP_FileClient{publicstaticvoidmain(String[]args)throwsException{Sockets=newSocket("127.0.0.1",9999);// 1. 读取本地文件FileInputStreamfis=newFileInputStream("D:/1.jpg");OutputStreamos=s.getOutputStream();// 2. 循环写出(上传)文件数据byte[]buf=newbyte[1024];intlen;while((len=fis.read(buf))!=-1){os.write(buf,0,len);}// ⚠️ 关键!告诉服务端"我传完了"s.shutdownOutput();fis.close();// 3. 读取服务端响应InputStreamis=s.getInputStream();byte[]respBuf=newbyte[1024];intrespLen=is.read(respBuf);System.out.println("服务端:"+newString(respBuf,0,respLen));s.close();}}5.3 服务端(收文件 + 保存 + 响应)
importjava.net.*;importjava.io.*;importjava.util.Random;publicclassTCP_FileServer{publicstaticvoidmain(String[]args)throwsException{ServerSocketss=newServerSocket(9999);System.out.println("文件服务器启动...📤");Socketclient=ss.accept();System.out.println("客户端上传:"+client.getInetAddress());// 1. 读取客户端上传数据InputStreamis=client.getInputStream();// 2. 生成随机文件名(防止覆盖)Randomr=newRandom();FileOutputStreamfos=newFileOutputStream("D:/upload/"+r.nextInt(Integer.MAX_VALUE)+".jpg");byte[]buf=newbyte[1024];intlen;while((len=is.read(buf))!=-1){fos.write(buf,0,len);// 保存到磁盘}fos.close();// 3. 给客户端响应OutputStreamos=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 多线程服务端代码
importjava.net.*;importjava.io.*;importjava.util.Random;publicclassTCP_MultiThreadServer{publicstaticvoidmain(String[]args)throwsException{ServerSocketss=newServerSocket(9999);System.out.println("多线程服务器启动...🧵");while(true){Socketclient=ss.accept();// 等待客户端System.out.println("新客户端:"+client.getInetAddress());// 为每个客户端开启独立线程!newThread(()->{try{// 接收文件InputStreamis=client.getInputStream();Randomr=newRandom();FileOutputStreamfos=newFileOutputStream("D:/upload/"+r.nextInt(Integer.MAX_VALUE)+".jpg");byte[]buf=newbyte[1024];intlen;while((len=is.read(buf))!=-1){fos.write(buf,0,len);}fos.close();// 响应客户端OutputStreamos=client.getOutputStream();os.write("上传成功!✅".getBytes());client.close();System.out.println("客户端上传完成!");}catch(Exceptione){e.printStackTrace();}}).start();// 启动线程!}}}6.3 启动多个客户端测试
publicclassTCP_MultiClientTest{publicstaticvoidmain(String[]args){// 同时启动 3 个客户端,并发上传for(inti=0;i<3;i++){newThread(()->{try{Sockets=newSocket("127.0.0.1",9999);FileInputStreamfis=newFileInputStream("D:/1.jpg");OutputStreamos=s.getOutputStream();byte[]buf=newbyte[1024];intlen;while((len=fis.read(buf))!=-1){os.write(buf,0,len);}fis.close();s.shutdownOutput();// 接收响应InputStreamis=s.getInputStream();byte[]resp=newbyte[1024];intrespLen=is.read(resp);System.out.println(Thread.currentThread().getName()+":"+newString(resp,0,respLen));s.close();}catch(Exceptione){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 ← 空行(必须!) <html>...</html> ← 响应体(网页内容)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
