一、引言
在当今数字化时代,互联网已经成为人们生活中不可或缺的一部分。而在互联网的背后,TCP/IP 协议扮演着至关重要的角色,堪称互联网的基石。
TCP/IP 协议是一组用于数据通信的协议集合,它的名字来源于其中最重要的两个协议:传输控制协议(TCP)和网际协议(IP)。自 20 世纪 70 年代末期以来,TCP/IP 协议已经成为全球互联网通信的通用语言,它定义了数据如何在网络上进行传输和路由。
TCP/IP 协议遵循分层模型,将网络通信分为四个层次:链路层、网络层、传输层和应用层。每层负责不同的功能,通过协同工作,确保数据从源头安全、高效地传输到目的地。
链路层负责在物理媒介上发送和接收数据,如以太网、Wi-Fi 等。网络层负责数据包的路由和转发,确保数据包能够跨越多个网络到达目的地。IP 协议是这一层的核心,它通过给每个数据包分配一个唯一的 IP 地址,确保数据能够正确地路由到目标计算机。
传输层负责提供端到端的数据传输服务。TCP 和 UDP 是这一层的两个主要协议。TCP 是一种面向连接的、可靠的传输层协议,它确保数据包按顺序到达,并且允许接收方确认数据包的接收。UDP 则是一种无连接的、不可靠的传输层协议,它不保证数据包的顺序或完整性,但速度更快,适用于对实时性要求高的应用。
应用层为应用软件提供网络服务,如 HTTP、FTP、SMTP 等。这些协议定义了客户端和服务器之间的通信规则,使得用户能够在互联网上进行各种活动,如浏览网页、发送电子邮件、下载文件等。
总之,TCP/IP 协议是现代互联网通信的基础,它的重要地位不可替代。
二、TCP/IP 协议栈详细解释
(一)什么是计算机网络
计算机网络是根据应用的需要发展而来的,它是将地理位置分散的计算机系统和通信设备连接起来,实现资源共享和信息传递的系统。计算机网络的功能主要表现在硬件资源共享、软件资源共享和用户间信息交换三个方面。
硬件资源共享可以在全网范围内提供对处理资源、存储资源、输入输出资源等昂贵设备的共享,从而使用户节省投资,也便于集中管理和均衡分担负荷。软件资源共享使得互联网上的用户可以远程访问各类大型数据库,可以通过网络下载某些软件到本地机上使用,可以在网络环境下访问一些安装在服务器上的公用网络软件,也可以通过网络登录到远程计算机上使用该计算机上的软件,这样可以避免软件研制上的重复劳动以及数据资源的重复存储,也便于集中管理。用户间信息交换是计算机网络最基本的功能,主要完成计算机网络中各个节点之间的系统通信,用户可以在网上传送电子邮件、发布新闻消息、进行电子购物、电子贸易、远程电子教育等。
(二)TCP/IP 网络世界的规则
- OSI 参考模型具有简化相关的网络操作、提供设备间的兼容性和标准接口、促进标准化工作、结构上可以分隔易于实现和维护等优点。
- TCP/IP 协议栈可以看成是 OSI 参考模型的简化,分为四层:网络接入层、网络层、传输层、应用层。TCP/IP 协议栈与 OSI 参考模型存在一定的对应关系,应用层对应 OSI 模型的应用层、表示层和会话层;传输层对应 OSI 模型的传输层;网络层对应 OSI 模型的网络层;网络接入层对应 OSI 模型的数据链路层和物理层。
- TCP/IP 协议栈每一层的功能如下:
- 网络接入层:位于 TCP/IP 协议栈的最底层,包括各种网络技术,如以太网、Wi-Fi 等,并处理数据帧的传输。它基本上包括了 ISO/OSI 模型中的数据链路层和物理层的所有功能,定义了如何利用网络来传送 IP 数据报,必须知道物理网络的各种细节,以便准确地格式化传输的数据,使其遵守网络规定。
- 网络层:由互连网协议(IP)、互连网控制报文协议(ICMP)和互连网组管理协议(IGMP)等协议组成。IP 是 TCP/IP 的核心,也是网络互连层中最重要的协议,它提供基本的分组传送服务,定义数据报,定义 Internet 地址系统,把数据报分解或重组成易于在网络中传输的结构,在网络存取层和传输层之间传递数据,给远端主机的数据报指定路由,完成数据报的拥挤控制和信息控制。
- 传输层:为应用层提供可靠的或不可靠的端到端服务。TCP(传输控制协议)是一种可靠的、面向连接的、字节流协议,利用端到端错误检测与纠正功能提供可靠的数据传输服务;UDP(用户数据报协议)是一个不可靠的无连接数据报协议,为应用程序提供低开销的无连接数据报传输服务。
- 应用层:位于 TCP/IP 协议栈的顶层,为用户提供各种网络服务,如文件传输、远程登录、电子邮件等,同时包含与具体应用程序相关的所有细节。
(三)TCP/IP 模型的层间通信与数据封装
- 数据包在网络设备之间进行传输的过程中,为了保证数据包准确的发送到目的地,发送端会对数据包进行封装。在发送的数据包上附加 TCP 或者是 UDP 的包头形成数据段(segment),网络层会添加 IP 包头形成数据包(Packet),数据链路层会给数据添加以太网包头和 FCS 包尾,形成数据帧(Frame),最后转换成二进制的比特流通过物理线路传到接收方。这个操作过程就叫做数据封装,而对数据包进行处理时通信双方所遵循和协商好的规则称为协议。
- 接收端收到数据后会进行解封装,从物理层开始,进行与发送端相反的操作,一层层去掉包头,最终使应用层程序获取到数据信息,使得发送方和接收方数据通信完成。
(四)抓包了解数据结构
通过 Wireshark 抓取 HTTP 协议的报文,可以分析传输层、网络层和数据链路层封装的信息。
传输层封装的是 TCP 协议,可以看到源端口号,目标端口号。以访问百度为例,先通过三次握手建立连接,第一次握手:客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SEND 状态,等待服务器确认;第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK (ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。
网络层封装的是 IP 包头,包括 IPV4 的版本,首部长度,协议类型是 TCP 协议,源 IP 地址,目标 IP 地址等。
数据链路层,协议类型是 OX0800 代表三层使用的是 IPV4 协议,源主机的 MAC 地址,目标主机的 MAC 地址等。
三、TCP/IP 各层功能和重要性
(一)应用层
- 列举应用层的各种协议,如文件传输类(HTTP、FTP、TFTP)、远程登录类(Telnet)、电子邮件类(SMTP)、网络管理类(SNMP)、域名解析类(DNS)等。
-
- 文件传输类:
-
-
- HTTP(超文本传输协议):适用于分布式超媒体信息系统,是 WWW 服务器使用的主要协议,允许用户在统一的界面下,采用不同的协议访问不同服务。
-
-
-
- FTP(文件传输协议):用于简化 IP 网络上系统之间文件传输,用户可以高效从 Internet 上的 FTP 服务器下载大量的数据文件,实现资源共享和传递信息。FTP 包含控制连接和数据连接两种连接模式,控制连接用于传递用户端的命令和服务器端对命令的响应,使用服务器的 21 端口;数据连接用于传输文件和其他数据,如目录列表等,这种连接在需要数据传输时建立,每次使用的端口不一定相同,且数据连接既可能是服务器端发起,也可能是客户端发起。FTP 服务器数据连接有主动模式和被动模式,主动模式从服务器端向客户端发起连接,被动模式是客户端向服务器端发起连接。
-
-
-
- TFTP(简单文件传输协议):是基于 UDP 的应用,对内存和处理器的要求很低,速度快,但功能不如 FTP 丰富,只能从文件服务器获得或写入文件,而不能列出目录,也不能进行认证,没有建立连接的过程及错误恢复的功能,适用范围不如 FTP 广泛。常见的应用例子是使用 TFTP 服务器来备份或恢复 Cisco 路由器、Catalyst 交换机的 IOS 镜像和配置文件。
-
-
- 远程登录类:Telnet,通过一个终端登陆到其他服务器,建立在可靠的传输协议 TCP 之上。但 Telnet 协议所有数据(包括用户名和密码)均以明文形式发送,有潜在的安全风险,如今已被更安全的 SSH 协议所取代。
-
- 电子邮件类:
-
-
- SMTP(简单邮件传输协议):基于 TCP 协议,用来发送电子邮件。当用户发送邮件时,通过 SMTP 协议将邮件交给自己的邮箱服务器,邮箱服务器发现目标邮箱是其他服务器后,使用 SMTP 协议将邮件转发到目标邮箱服务器。
-
-
-
- POP3/IMAP:邮件接收的协议,IMAP 协议相比于 POP3 更新,为用户提供的可选功能更多,几乎所有现代电子邮件客户端和服务器都支持 IMAP。
-
-
- 网络管理类:SNMP(简单网络管理协议),允许第三方的管理系统集中采集来自许多网络设备的数据,为网络管理系统提供了底层网络管理的框架。利用 SNMP,一个管理工作站可以远程管理所有支持这种协议的网络设备,包括监视网络状态、修改网络设备配置、接收网络事件警告等。
-
- 域名解析类:DNS(域名服务协议),基于 UDP,使用端口号 53。在浏览器中输入一个域名后,会有 DNS 服务器将域名解析为对应的 IP 地址。
- 分别介绍这些协议的作用,如 HTTP 是超文本传输协议,用于网页设计和数据传输等。
-
- HTTP:主要是为 Web 浏览器与 Web 服务器之间的通信而设计,当使用浏览器浏览网页时,网页就是通过 HTTP 请求进行加载的。HTTP 协议基于 TCP 协议,发送 HTTP 请求之前首先要建立 TCP 连接,即经历三次握手。目前使用的 HTTP 协议大部分都是 1.1,在 1.1 的协议里,默认开启了 Keep-Alive,建立的连接可以在多次请求中被复用。HTTP 协议是 “无状态” 的协议,无法记录客户端用户的状态,一般通过 Session 来记录客户端用户的状态。
-
- FTP:用于在 IP 网络上系统之间进行文件传输,用户可以高效下载或上传大量数据文件,实现资源共享和信息传递。FTP 服务器可以设置为公用、私有或两者兼之,用户可以为 FTP 帐号定义权限,访问特定区域。
-
- TFTP:基于 UDP 的应用,对内存和处理器要求低,速度快,常用于备份或恢复网络设备的 IOS 镜像和配置文件。
-
- Telnet:通过终端登录到其他服务器,进行远程操作。
-
- SMTP:用于发送电子邮件,将用户写好的邮件交给邮箱服务器,由邮箱服务器转发到目标邮箱服务器。
-
- POP3/IMAP:负责接收电子邮件,IMAP 协议功能更丰富,支持更多可选功能。
-
- SNMP:为网络管理系统提供底层框架,管理工作站可以远程管理支持该协议的网络设备。
-
- DNS:将域名解析为对应的 IP 地址,方便用户使用域名访问网站,而无需记住数字组成的 IP 地址。
(二)运输层
- 介绍运输层的主要协议 TCP 和 UDP 的特点和区别,如 TCP 是面向连接的可靠协议,UDP 是无连接的不可靠协议。
-
- TCP(传输控制协议)是面向连接的协议,需要在传输数据之前建立连接。它是一种可靠的协议,可以保证数据包的可靠传输,确保数据在传输过程中不丢失、不乱序、不重复。当网络出现拥塞或丢包时,TCP 会进行流量控制和拥塞控制,根据网络状况调整发送速率,避免网络拥塞。TCP 的数据包结构相对复杂,包括头部和序列号等字段,采用字节流传输,保证数据按顺序到达接收方。TCP 只能是 1 对 1 的连接,首部较大为 20 字节。TCP 适用于要求数据传输可靠的场景,如文件传输、邮件等。
-
- UDP(用户数据报协议)是无连接的协议,不需要建立连接,可以直接发送数据包。它是不可靠的协议,不保证数据包的可靠传输,可能会出现数据包丢失、重复、乱序等问题。UDP 不进行流量控制和拥塞控制,直接发送数据包,如果网络出现拥塞,UDP 数据包可能会丢失或延迟,甚至导致网络更加拥塞。UDP 的数据包结构相对简单,只包括源端口、目的端口、长度、校验和和数据等字段,采用数据报传输,不保证数据按顺序到达。UDP 适用于实时应用场景,如视频、音频、游戏等,对数据传输的实时性和延迟要求较高。UDP 支持 1 对 1、1 对多的连接。
- 详细解释 TCP 的可靠性实现方式,如三次握手建立连接和改进的三次握手断开连接。
-
- 三次握手建立连接:
- 第一次握手,客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SEND 状态,等待服务器确认;
- 第二次握手,服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;
- 第三次握手,客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK (ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。
-
- 改进的三次握手断开连接:当一方想要断开连接时,会发送 FIN 包表示结束数据传输,另一方收到 FIN 包后回复 ACK 确认,然后也发送 FIN 包表示自己也准备断开连接,最后发起方再回复 ACK 确认,完成连接断开。
(三)网络层
- 引入 IP 协议,解释其作用是制定新地址(IP 地址),以便区分两台主机是否同属一个网络。
-
- IP 协议(Internet Protocol)是互联网中最基础的协议之一,负责在网络中寻址和路由数据包,将数据包从源主机传输到目标主机。它定义了数据包的格式、寻址方式和路由规则,是互联网通信的基础。IP 地址就是用来标识网络中的设备的地址,类似于现实生活中的门牌号码,通过 IP 地址可以唯一标识网络中的每个设备。IP 协议负责将数据包分割成适合网络传输的小块(分片),并在目标主机上将这些小块重新组合成完整的数据包(重组),以适应不同网络的传输需求,确保数据能够顺利传输到目标主机。IP 协议采用数据包交换的方式进行数据传输,将数据包从源主机传输到目标主机,而不需要建立专门的物理连接。IP 协议支持不同类型的网络,包括以太网、无线网络、广域网等,使得不同类型的网络能够互相通信和交换数据。
- 介绍网络层的其他协议,如 ARP 协议用于获取目标 MAC 地址,路由协议用于数据包的转发。
-
- ARP(Address Resolution Protocol)协议用于获取目标 MAC 地址。当一个设备要向另一个设备发送数据时,如果只知道目标设备的 IP 地址,就需要通过 ARP 协议来获取目标设备的 MAC 地址。ARP 协议通过广播的方式发送请求,询问目标 IP 地址对应的 MAC 地址,目标设备收到请求后会回复自己的 MAC 地址。
-
- 路由协议用于数据包的转发。路由器根据路由协议来确定数据包的转发路径,将数据包从一个网络转发到另一个网络。常见的路由协议有 OSPF、BGP 等。
(四)网络接口层
- 说明以太网协议对电信号进行分组并形成数据帧的过程。
-
- 当以太网软件从网络层接收到数据报之后,需要根据需要把网际层的数据分解为较小的块,以符合以太网帧数据段的要求。以太网帧的整体大小必须在 64~1518 字节之间(不包含前导码)。有些系统支持更大的帧,最大可以支持 9000 字节。然后把数据块打包成帧,每一帧都包含数据及其他信息,这些信息是以太网网络适配器处理帧所需要的。最后把数据帧传递给对应于 OSI 模型物理层的底层组件,后者把帧转换为比特流,并且通过传输介质发送出去。
- 强调 MAC 地址的唯一性和以太网采用广播形式发送数据包的方式。
-
- MAC 地址是 Media Access Control Address 的简称,由 IEEE 制定的一种网络通信协议,用以确定网络上各节点的位置。MAC 地址是用来标识网络设备唯一性的,它是一个网络层协议,可以用于确定设备通信的传输介质和节点。MAC 地址长度为 48 位,及 6 个字节,一般用 16 进制数字加上冒号的形式来表示。在网卡出厂时就确定了,不能修改,通常是唯一的(虚拟机中的 MAC 地址不是真实的 MAC 地址,可能会冲突;也有些网卡支持用户配置 MAC 地址)。
-
- 以太网采用广播形式发送数据包。当一个设备发送数据时,会将数据帧中的目的 MAC 地址设置为目标设备的 MAC 地址,然后将数据帧发送到网络中。网络中的其他设备接收到数据帧后,会检查其中的目的 MAC 地址。如果目的 MAC 地址与自己的 MAC 地址相匹配,适配器软件就会处理接收到的帧,把数据传递给协议栈中较高的层;如果目的 MAC 地址与自己的 MAC 地址不匹配,设备就会丢弃该数据帧。
四、TCP/IP 协议的实际应用案例
(一)文件传输案例
- 介绍使用 Java 中的 Socket 通信基于 TCP/IP 协议进行文件传输的案例。
Java 中可以使用ServerSocket和Socket类实现基于 TCP/IP 协议的文件传输。服务端创建一个ServerSocket对象,绑定到指定端口,等待客户端的连接请求。客户端创建一个Socket对象,指定服务端的 IP 地址和端口,发出连接请求。连接建立后,客户端通过文件输入流读取本地文件,然后通过Socket的输出流向服务端发送文件数据。服务端通过Socket的输入流接收文件数据,并保存到本地文件中。
- 展示服务端和客户端的代码示例,解释文件传输的过程。
服务端代码:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws IOException {
// 获取 ServerSocket 对象,提供 TCP 连接服务
ServerSocket serverSocket = new ServerSocket(8888);
// 等待接收客户端的 TCP 连接申请
Socket socket = serverSocket.accept();
// 保存客户端发送的文件
// 获取 TCP 连接提供的字节输入流
InputStream is = socket.getInputStream();
// 把数据存入指定文件
FileOutputStream fos = new FileOutputStream(new File("D://receivedFile.txt"));
byte[] b = new byte[1024];
int len;
// 读取数据,保存数据
while ((len = is.read(b))!= -1) {
fos.write(b, 0, len);
}
// 关闭资源
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}
客户端代码:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
// 创建了一个匿名的 InetAddress 独享,创建了一个 socket,进行连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 8888);
// 创建输出流,用来发送字节流
OutputStream os = socket.getOutputStream();
// 本身我需要获取到本地的一个文件,所以我需要 input
FileInputStream fis = new FileInputStream(new File("D://originalFile.txt"));
// 具体读和写的过程
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer))!= -1) {
// 再次提醒这样是为了防止读取到重复数据
os.write(buffer, 0, len);
}
// 关闭资源
fis.close();
os.close();
socket.close();
}
}
文件传输过程:首先服务端创建一个ServerSocket对象并绑定到指定端口,然后进入等待状态,等待客户端的连接请求。客户端创建一个Socket对象,指定服务端的 IP 地址和端口,发出连接请求。当服务端接收到客户端的连接请求后,建立连接,服务端通过accept()方法返回一个Socket对象。接着,客户端通过文件输入流读取本地文件,并通过Socket的输出流向服务端发送文件数据。服务端通过Socket的输入流接收文件数据,并保存到本地文件中。
(二)Python 通信案例
- 使用 Python 实现 TCP/IP 协议通信的示例,包括服务器端和客户端的代码。
服务器端代码:
import socket
import threading
# 创建一个 socket 对象
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取本地主机名
host = socket.gethostname()
# 设置端口号
port = 9999
# 绑定端口号
serversocket.bind((host, port))
# 设置最大连接数,超过后排队
serversocket.listen(5)
client_list = []
# 用于存储所有连接的客户端 socket
def handle_client(clientsocket):
data = clientsocket.recv(1024).decode()
print(f"收到数据:{data}")
clientsocket.send("已收到您的消息".encode())
clientsocket.close()
client_list.remove(clientsocket)
while True:
# 建立客户端连接
clientsocket, addr = serversocket.accept()
print(f"连接地址:{str(addr)}")
client_list.append(clientsocket)
# 为每个新连接创建一个线程来处理
threading.Thread(target=handle_client, args=(clientsocket,)).start()
客户端代码:
import socket
# 创建一个 socket 对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取本地主机名
host = socket.gethostname()
# 设置端口号
port = 9999
# 连接服务,指定主机和端口
s.connect((host, port))
# 发送数据
s.send("Hello, server".encode())
# 接收响应数据
response = s.recv(1024).decode()
print(f"收到响应:{response}")
# 关闭连接
s.close()
- 扩展功能的介绍,如多用户支持、消息广播、客户端身份验证和持久化连接等。
-
- 多用户支持:服务器现在可以同时处理多个客户端的连接。通过使用线程或异步编程,可以为每个新连接的客户端创建一个独立的处理线程或任务,从而实现多用户支持。
-
- 消息广播:服务器可以将消息广播给所有连接的客户端。可以创建一个函数,遍历所有连接的客户端并发送消息。例如:
def broadcast_message(message):
for client in client_list:
client.send(message.encode())
- 客户端身份验证:服务器可以对客户端进行身份验证,只有通过验证的客户端才能发送消息。在handle_client函数中添加一个验证步骤,例如要求客户端在连接时发送一个特定的令牌或用户名,然后服务器可以检查该令牌或用户名是否有效。如果验证失败,服务器可以关闭连接或拒绝接收消息。
- 持久化连接:服务器和客户端可以保持连接打开,以便于连续通信。可以使用长轮询或心跳机制来保持连接打开。这意味着服务器和客户端应该定期发送消息以保持连接活动状态。如果服务器或客户端在一段时间内没有收到消息,它们可以认为连接已断开并采取适当的行动。
(三)公共聊天室案例
- 功能说明,如服务端设定客户端连接个数上限,客户端可与服务端单独通信。
公共聊天室的服务端可以设定客户端连接个数上限,当达到上限时,新的客户端连接将被拒绝。客户端可以向服务端发送消息,服务端可以将消息广播给所有连接的客户端,客户端也可以接收服务端广播的消息。同时,客户端可以与服务端单独通信,即客户端发送给服务端的消息只有服务端可以看到,服务端回复的消息也只有发送消息的客户端可以看到。
- 展示服务端和客户端的代码,解释公共聊天室的实现过程。
服务端代码:
// MyServer.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
#include <winsock2.h>
// 引用头文件
#pragma comment(lib,"ws2_32.lib")
// 最大连接数
#define g_MaxConnect 20
int g_Connect = 0;
struct sock_params {
SOCKET hsock;
int nSockIndex;
};
// 线程实现函数
DWORD WINAPI threadpro(LPVOID pParam) {
sock_params* sockPam = (sock_params*)pParam;
SOCKET hsock = sockPam->hsock;
int nSockIndex = sockPam->nSockIndex;
char aIndex[4];
_itoa_s(nSockIndex, aIndex, 10);
char buffer[1024];
char sendBuffer[1024];
if (hsock!= INVALID_SOCKET) {
std::cout << "客户端 " << nSockIndex << " 加入服务器!" << std::endl;
}
while (1) {
// 循环接收发送的内容
int num = recv(hsock, buffer, 1024, 0);
// 阻塞函数,等待接收内容
if (num > 0) {
std::cout << "客户端 " << nSockIndex << ": " << buffer << std::endl;
memset(sendBuffer, 0, 1024);
char strValue[30] = "服务器 ";
strcat_s(strValue, aIndex);
strcpy_s(sendBuffer, strValue);
char strSpace[30] = " 收到数据如下: ";
strcat_s(sendBuffer, strSpace);
strcat_s(sendBuffer, buffer);
int ires = send(hsock, sendBuffer, sizeof(sendBuffer), 0);
// 回送消息
// cout << "Send to Client: " << sendBuffer << endl;
}
else {
std::cout << "客户端 " << nSockIndex << " 关闭!" << std::endl;
// cout << "Server Process " << nSockIndex << " Closed" << endl;
return 0;
}
}
return 0;
}
// 主函数
void main() {
WSADATA wsd;
// 定义 WSADATA 对象
WSAStartup(MAKEWORD(2, 2), &wsd);
SOCKET m_SockServer;
sockaddr_in serveraddr;
sockaddr_in serveraddrfrom;
SOCKET m_Server[g_MaxConnect];
serveraddr.sin_family = AF_INET;
// 设置服务器地址
serveraddr.sin_port = htons(4600);
// 设置端口号
serveraddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
m_SockServer = socket(AF_INET, SOCK_STREAM, 0);
int nStatus = bind(m_SockServer, (sockaddr*)&serveraddr, sizeof(serveraddr));
if (nStatus == 0) {
std::cout << "服务端启动成功!" << std::endl;
}
else {
std::cout << "服务端启动失败!" << std::endl;
return;
}
int iLisRet = 0;
int len = sizeof(sockaddr);
while (1) {
iLisRet = listen(m_SockServer, 0);
// 进行监听
m_Server[g_Connect] = accept(m_SockServer, (sockaddr*)&serveraddrfrom, &len);
// 同意连接
if (m_Server[g_Connect]!= INVALID_SOCKET) {
if (g_Connect > g_MaxConnect - 1) {
char WarnBuf[50] = "客户端连接个数:超限!";
int ires = send(m_Server[g_Connect], WarnBuf, sizeof(WarnBuf), 0);
}
else {
char cIndex[4];
_itoa_s(g_Connect, cIndex, 10);
char buf[50] = "你的服务端 ID: ";
strcat_s(buf, cIndex);
int ires = send(m_Server[g_Connect], buf, sizeof(buf), 0);
// 发送字符过去
// cout << buf << endl;
HANDLE m_Handel;
// 线程句柄
DWORD nThreadId = 0;
// 线程 ID
sock_params sockPam;
sockPam.hsock = m_Server[g_Connect];
sockPam.nSockIndex = g_Connect;
m_Handel = (HANDLE)::CreateThread(NULL, 0, threadpro, &sockPam, 0, &nThreadId);
CloseHandle(m_Handel);
}
++g_Connect;
}
}
WSACleanup();
}
客户端代码:
// MyClient.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "winsock2.h"
#include <time.h>
#pragma comment(lib,"ws2_32.lib")
void main() {
WSADATA wsd;
// 定义 WSADATA 对象
WSAStartup(MAKEWORD(2, 2), &wsd);
SOCKET m_SockClient;
sockaddr_in clientaddr;
clientaddr.sin_family = AF_INET;
// 设置服务器地址
clientaddr.sin_port = htons(4600);
// 设置服务器端口号
clientaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
m_SockClient = socket(AF_INET, SOCK_STREAM, 0);
if (m_SockClient == INVALID_SOCKET) {
printf("Sock 初始化失败: %d\n", WSAGetLastError());
WSACleanup();
return;
}
// 获取发送缓冲区和接送缓冲区大小
{
int optlen = 0;
int optval = 0;
optlen = sizeof(optval);
getsockopt(m_SockClient, SOL_SOCKET, SO_SNDBUF, (char*)&optval, &optlen);
printf("send buf len is %d\n", optval);
// 64 位 默认发送缓冲区 64k
getsockopt(m_SockClient, SOL_SOCKET, SO_RCVBUF, (char*)&optval, &optlen);
printf("Recv buf len is %d\n", optval);
// 64 位 默认接收缓冲区 64k
}
// 设定发送缓冲区大小
// optval = 1024 * 2; // setsockopt(ConnectSocket, SOL_SOCKET, SO_SNDBUF, (char*)&optval, optlen);
int nSuccess = connect(m_SockClient, (sockaddr*)&clientaddr, sizeof(clientaddr));
// 连接超时
if (nSuccess == 0) {
std::cout << "连接服务器成功!" << std::endl;
}
else {
std::cout << "连接服务器失败!" << std::endl;
return;
}
char buffer[1024];
char inBuf[1024];
int num = 0;
num = recv(m_SockClient, buffer, 1024, 0);
// 阻塞
if (num > 0) {
std::cout << buffer << std::endl;
char* pResult = strstr(buffer, "超限");
if (pResult!= NULL) {
std::cout << "服务器连接个数超限,不可用!" << std::endl;
system("pause");
return;
}
while (1) {
std::cout << "请输入要发送的消息:" << std::endl;
std::cin >> inBuf;
if (strcmp(inBuf, "exit") == 0) {
send(m_SockClient, inBuf, sizeof(inBuf), 0);
// 发送退出指令
return;
}
int send_len = send(m_SockClient, inBuf, sizeof(inBuf), 0);
if (send_len < 0) {
std::cout << "发送失败!请检查服务器是否开启!" << std::endl;
system("pause");
return;
}
int recv_len = recv(m_SockClient, buffer, 1024, 0);
// 接收客户端发送过来的数据
if (recv_len >= 0) {
std::cout << buffer << std::endl;
}
}
}
}
公共聊天室的实现过程:服务端首先初始化WSADATA对象,创建一个SOCKET对象并绑定到指定的端口号。然后,服务端进入一个无限循环,监听来自客户端的连接请求。当一个客户端请求连接时,服务端接受请求并创建一个新的SOCKET对象来与该客户端通信。服务端为每个连接的客户端创建一个线程来处理通信,线程函数接收客户端发送的消息,并将消息加上服务器的标识后回送给客户端。如果客户端关闭连接,线程函数退出。服务端还可以设置客户端连接个数上限,当达到上限时,新的客户端连接将被拒绝。客户端首先初始化WSADATA对象,
五、结论
TCP/IP 协议作为互联网的基石,其重要性不言而喻。从分层模型来看,各层功能明确且相互协作,共同确保了数据在网络中的高效、准确传输。
应用层的众多协议为用户提供了丰富多样的网络服务,无论是文件传输、远程登录、电子邮件还是网络管理和域名解析,都极大地满足了人们在互联网时代的各种需求。这些协议的存在使得用户能够轻松地进行各种网络活动,实现资源共享和信息交流。
运输层的 TCP 和 UDP 协议各具特点,满足了不同应用场景的需求。TCP 的可靠性保证了数据传输的准确性和完整性,适用于对数据传输质量要求较高的场景;而 UDP 的高效性则使其在实时性要求高的应用中发挥了重要作用。
网络层的 IP 协议通过制定新地址,确保了两台主机能够准确区分彼此,并实现跨网络的数据传输。ARP 协议和路由协议的协同作用,使得数据包能够顺利地找到目标主机并进行转发。
网络接口层的以太网协议对电信号进行分组并形成数据帧,同时 MAC 地址的唯一性和以太网的广播形式确保了数据在局域网内的正确传输。
TCP/IP 协议的实际应用案例进一步展示了其强大的功能和灵活性。文件传输案例中,Java 和 Python 实现的基于 TCP/IP 协议的文件传输,为用户提供了高效的数据传输方式。Python 通信案例中的多用户支持、消息广播、客户端身份验证和持久化连接等扩展功能,丰富了网络通信的应用场景。公共聊天室案例则展示了 TCP/IP 协议在实现多人实时通信方面的能力。
总之,TCP/IP 协议的重要性不仅在于其作为互联网通信的基础,更在于其各层功能的协同作用和实际应用中的广泛适用性。它为我们的数字生活提供了坚实的支撑,推动了互联网技术的不断发展和进步。
相关文章推荐: