高端大气企业网站源码,dw用ps切片做网站,衡阳seo网站推广,微信运营里面有疑问或者不正确的地方可以给我留言。
对TCP/IP、UDP、Socket编程这些词你不会很陌生吧#xff1f;随着网络技术的发展#xff0c;这些词充斥着我们的耳朵。那么我想问#xff1a;
什么是TCP/IP、UDP#xff1f;Socket在哪里呢#xff1f;Socket是什么呢#xff1…里面有疑问或者不正确的地方可以给我留言。
对TCP/IP、UDP、Socket编程这些词你不会很陌生吧随着网络技术的发展这些词充斥着我们的耳朵。那么我想问
什么是TCP/IP、UDPSocket在哪里呢Socket是什么呢你会使用它们吗
什么是TCP/IP、UDP
TCP/IP传输控制协议/互联网协议Transmission Control Protocol/Internet Protocol是用于网络通信的核心协议集构成了互联网的基础。它定义了计算机和网络设备如何通过网络传输数据并提供了可靠的端到端数据传输机制。
从上述这段话可以看出TCP/IP是一个集合。什么集合协议的集合目的就是是实现 网络互联和数据通信使不同设备能够通过网络可靠且有效地交换信息。它包括以下协议
Ethernet局域网中使用的常见协议定义了数据帧在有线网络上的传输方式。Wi-Fi (Wireless Fidelity)无线局域网协议基于IEEE 802.11标准。PPP (Point-to-Point Protocol)用于在两个直接相连的节点之间传输数据如电话线上。SLIP (Serial Line Internet Protocol)早期用于通过串行连接传输IP数据包的协议。IP (Internet Protocol)负责路由和寻址将数据包从源主机发送到目标主机。包括 IPv4最广泛使用的IP协议使用32位地址。IPv6升级版IP协议使用128位地址提供更多的IP地址。ICMP (Internet Control Message Protocol)用于发送错误报告和网络诊断信息如ping操作。ARP (Address Resolution Protocol)用于将IP地址转换为网络硬件地址如MAC地址。RARP (Reverse ARP)将硬件地址转换为IP地址较少使用。TCP (Transmission Control Protocol)提供可靠的、面向连接的数据传输协议确保数据按顺序无错地到达。UDP (User Datagram Protocol)提供不可靠、无连接的数据传输协议适用于对速度要求较高且能容忍少量数据丢失的应用如视频流和在线游戏。HTTP (Hypertext Transfer Protocol)用于网页浏览传输超文本。HTTPS (HTTP Secure)HTTP的加密版本通过SSL/TLS保护数据 传输。FTP (File Transfer Protocol)用于在计算机之间传输文件。SMTP (Simple Mail Transfer Protocol)用于发送电子邮件。POP3 (Post Office Protocol 3)用于从邮件服务器下载电子邮件。IMAP (Internet Message Access Protocol)用于从服务器获取电子邮件支持在服务器上管理邮件。DNS (Domain Name System)用于将域名转换为IP地址。Telnet提供远程登录服务。SSH (Secure Shell)加密的远程登录协议替代Telnet。
看到这里是不是瞬间头大了这什么东西这么多这么复杂当时的人也觉得这一堆东西往这一堆很磕碜很不讲究太TM影响心情了。
所以就引出了教科书上经典的那句话TCP/IP协议集是一个分层、多协议的通信体系。
一群聪明蛋按照协议的功能分工和网络通信过程中的逻辑顺序把协议划分为四层。这种分层设计使得网络协议更加灵活、可扩展并能够解决不同通信问题。如下所示
数据链路层功能处理硬件接口与底层网络通信负责数据帧的发送与接收。
ARP (Address Resolution Protocol)用于将IP地址解析为物理网络地址如MAC地址适用于局域网。RARP (Reverse ARP)将物理地址如MAC地址映射为IP地址较少使用已被DHCP替代。Ethernet局域网协议定义了数据帧在有线网络中的传输方式。Wi-Fi (Wireless Fidelity)无线局域网协议基于IEEE 802.11标准。PPP (Point-to-Point Protocol)用于在两点间通过串行链路传输数据。SLIP (Serial Line Internet Protocol)早期的串行数据传输协议已被PPP取代。
网络层功能负责路由与寻址确保数据包能够从源地址到达目的地址。
IP (Internet Protocol)负责路由和寻址将数据包从源主机发送到目标主机。包括 IPv4使用32位地址的IP协议常用。IPv6使用128位地址的升级版IP协议解决了IPv4地址耗尽问题。ICMP (Internet Control Message Protocol)用于发送错误报告和网络诊断信息如ping操作。IGMP (Internet Group Management Protocol)用于管理多播组的成员支持多播通信。NAT (Network Address Translation)允许多个设备使用一个公有IP地址进行IP地址转换。OSPF (Open Shortest Path First)一种用于路由选择的内部网关协议基于链路状态的路由。BGP (Border Gateway Protocol)用于不同自治系统之间的路由选择。
传输层功能负责提供端到端的通信服务包括数据的分段、传输、错误检测与修复等。
TCP (Transmission Control Protocol)提供可靠、面向连接的数据传输确保数据按顺序无差错地到达。UDP (User Datagram Protocol)提供不可靠、无连接的数据传输适用于对速度要求高、能容忍少量数据丢失的应用如视频流、在线游戏。
应用层功能提供应用程序使用的网络服务。
HTTP (Hypertext Transfer Protocol)用于网页浏览传输超文本。HTTPS (HTTP Secure)HTTP的加密版本通过SSL/TLS保护数据传输。FTP (File Transfer Protocol)用于在计算机之间传输文件。SMTP (Simple Mail Transfer Protocol)用于发送电子邮件。POP3 (Post Office Protocol 3)用于从邮件服务器下载电子邮件。IMAP (Internet Message Access Protocol)用于从服务器获取电子邮件支持在服务器上管理邮件。DNS (Domain Name System)用于将域名转换为IP地址。Telnet提供远程登录服务但不安全因为数据是明文传输。SSH (Secure Shell)安全的远程登录协议替代Telnet支持加密通信。
看着是不是还是有些头大其实主要结构如下 举例说明分层
当你使用浏览器访问一个网站时浏览器使用应用层的HTTP协议来发送请求这个请求会通过传输层的TCP协议分段并加上校验信息再通过互联网层的IP协议选择路径传输到目标服务器最后通过数据链路层的以太网协议发送数据帧到网络中。各层互不干扰但又紧密配合完成整个通信过程。
那这些和Socket有什么关系呢来个图就一目了然了如下图所示 Socket是应用层和传输层之间的接口它将应用层的协议请求映射到传输层上的具体传输服务。Socket充当了网络通信的桥梁既不属于应用层也不属于传输层而是应用程序用于访问传输层服务的一种API。
所以Socket套接字是计算机网络编程中的一种通信机制允许两个程序在不同的设备上进行数据交换。它提供了在不同主机之间通过网络传输数据的接口是实现网络通信的重要工具。
常见的Socket类型
1TCP Socket面向连接可靠的传输TCP是一种面向连接的协议确保数据包按顺序到达且不丢失。TCP Socket用于建立可靠的、持久的连接如网页浏览器与服务器之间的通信。
2UDP Socket无连接非可靠的传输UDP是一种无连接的协议传输速度快但不保证数据的可靠传输。UDP Socket常用于对实时性要求高但允许丢包的应用如视频流、在线游戏等。
Socket 的工作原理基于“客户端-服务器”模型 服务器端 服务器程序在特定的IP地址和端口上“监听”等待连接请求。当客户端请求连接时服务器会接受连接双方通过Socket进行数据传输。 客户端 客户端程序向服务器发起连接请求通过服务器的IP地址和端口号找到目标服务器。连接建立后客户端与服务器可以相互发送和接收数据。
Socket编程的基本步骤
服务器端
1.创建套接字socket()
在C中socket() 函数用于创建套接字socket它是网络编程的基础。通过创建套接字应用程序可以在网络上进行通信。
socket()函数的语法
#include sys/types.h
#include sys/socket.hint socket(int domain, int type, int protocol);参数说明
(1).domain协议族/地址族 指定使用的通信域决定了套接字通信的地址格式。
常见的选项有
AF_INETIPv4协议的地址族。AF_INET6IPv6协议的地址族。AF_UNIX本地通信也称为域套接字主要用于同一台计算机上的进程通信。
(2).type套接字类型 指定通信类型决定了套接字的特性。
常见的选项有
SOCK_STREAM提供面向连接的可靠数据传输TCP协议。SOCK_DGRAM提供无连接的数据报传输UDP协议。SOCK_RAW提供对底层协议的直接访问通常用于高级网络编程和自定义协议。
(3).protocol协议 通常指定为 0表示使用默认协议。如果有多个协议可供选择可以明确指定协议编号。例如
IPPROTO_TCPTCP协议。IPPROTO_UDPUDP协议。
返回值
成功返回一个文件描述符表示新创建的套接字。失败返回 -1并设置 errno 以指示错误原因。
示例代码:
//
// Created by armstrong on 2024/9/9.
//
#include sys/types.h#include sys/socket.h#include iostream#include unistd.h#include cstring#include cerrnoint main(){int sockfd socket(AF_INET, SOCK_STREAM, 0);if(sockfd -1){std::cerr Failed to create socket. Error: strerror(errno) std::endl;return -1;}std::cout Socket created successfully! std::endl;// 关闭socketclose(sockfd);return 0;}
运行指令
在ubantu系统中(windows系统无法运行)先切换到代码文件所在路径例如文件名为socked.cpp,文件路径为/home/socket_learn/socked.cpp
cd /home/socket_learn然后运行g -o sockfd sockfd.cpp 这条指令使用 GNU C 编译器g来编译 sockfd.cpp 源代码文件并生成一个名为 sockfd 的可执行文件。
g -o sockfd sockfd.cpp最后运行
./sockfd
代码运行结果
Socket created successfully!说明
socket()首先创建一个 IPv4 (AF_INET)、面向连接的 TCP (SOCK_STREAM) 套接字。
close()关闭套接字释放资源。
错误处理
如果 socket() 返回 -1则表示套接字创建失败可以通过 errno 获取错误码使用 strerror(errno) 打印错误信息。常见错误 EACCES权限问题无法创建套接字。ENFILE系统中打开的文件套接字已达到上限。EMFILE进程中打开的文件套接字已达到上限。
总结
socket() 是 C 网络编程的基础用于创建套接字。套接字可用于 TCP 和 UDP 等协议创建后需要结合 connect()、bind()、send()、recv() 等函数来实现通信。
2.绑定IP地址和端口bind()
在网络编程中bind() 函数用于将创建的套接字绑定到一个特定的IP地址和端口号。它通常用于服务器端程序允许服务器在特定的地址和端口上监听客户端的连接请求。
bind()函数的语法
#include sys/types.h
#include sys/socket.hint bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数说明
sockfd由 socket() 函数返回的套接字文件描述符。addr指向 sockaddr 结构体的指针包含IP地址和端口号信息。addrlenaddr 结构体的大小通常使用 sizeof(struct sockaddr_in)。
sockaddr_in结构体
在绑定 IPv4 地址时sockaddr_in 结构体通常用于表示IP地址和端口号。该结构体定义在 netinet/in.h 中格式如下
struct sockaddr_in {sa_family_t sin_family; // 地址族AF_INET 表示 IPv4in_port_t sin_port; // 端口号需要使用 htons() 转换成网络字节序struct in_addr sin_addr; // IP 地址使用 inet_addr() 或 INADDR_ANY
};sin_family地址族通常设置为 AF_INET表示 IPv4。sin_port端口号必须使用 htons() 函数将端口号从主机字节序转换为网络字节序。sin_addrIP地址使用 inet_addr() 函数将字符串形式的IP地址转换为 in_addr 类型或者使用 INADDR_ANY 表示绑定到本地所有可用的网络接口。
返回值
成功返回 0。失败返回 -1并设置 errno 以指示错误原因。
常见错误
EADDRINUSE指定的IP地址或端口已经被占用。EINVAL套接字已经绑定过一次不能重复绑定。EBADF提供的文件描述符无效。
示例代码
以下示例演示如何在服务器端创建一个套接字并将其绑定到本地IP地址和端口号 8080。
#include iostream
#include sys/types.h
#include sys/socket.h
#include netinet/in.h // 包含 sockaddr_in 结构体定义
#include arpa/inet.h // 包含 htons、inet_addr 等函数
#include unistd.h // 包含 close 函数
#include cstring // 包含 memset 函数int main() {// 1. 创建套接字 (IPv4, TCP)int sockfd socket(AF_INET, SOCK_STREAM, 0);if (sockfd -1) {std::cerr Failed to create socket. Error: strerror(errno) std::endl;return -1;}// 2. 定义服务器地址结构体struct sockaddr_in server_addr;memset(server_addr, 0, sizeof(server_addr)); // 将结构体清零server_addr.sin_family AF_INET; // IPv4server_addr.sin_port htons(8080); // 设置端口号使用 htons 转换为网络字节序server_addr.sin_addr.s_addr INADDR_ANY; // 绑定到本地所有可用地址// 3. 绑定套接字到IP地址和端口if (bind(sockfd, (struct sockaddr*)server_addr, sizeof(server_addr)) -1) {std::cerr Failed to bind. Error: strerror(errno) std::endl;close(sockfd); // 关闭套接字return -1;}std::cout Bind successful. Socket is now bound to port 8080. std::endl;// 4. 关闭套接字close(sockfd);return 0;
}运行结果
Bind successful. Socket is now bound to port 8080.
代码说明
socket()创建了一个 IPv4、TCP 套接字。
sockaddr_in 结构体用于存储服务器的IP地址和端口信息。INADDR_ANY 表示服务器可以监听来自所有本地网络接口的连接。
bind()将套接字与指定的IP地址和端口绑定。如果绑定成功套接字就可以在指定的端口上接收客户端的连接请求。
htons()将端口号从主机字节序转换为网络字节序这是必要的步骤因为不同的系统可能使用不同的字节序。
close()关闭套接字释放资源。
如何处理多网络接口
如果服务器主机有多个网络接口多个IP地址可以使用不同的IP地址进行绑定
绑定到特定IP地址将 server_addr.sin_addr.s_addr 设置为特定的IP地址如 inet_addr(192.168.1.100)。绑定到所有接口使用 INADDR_ANY这意味着套接字将绑定到主机的所有可用网络接口可以接受来自任意接口的连接。例如以太网接口Ethernet物理网络接口通过网线连接到局域网。Wi-Fi 接口无线网络接口通过 Wi-Fi 连接到网络。环回接口Loopback通常是 127.0.0.1用于本地程序之间的通信即不经过网络只在本机内部进行通信。
绑定后的后续操作
通常服务器端程序在成功绑定套接字后会执行以下步骤
监听连接请求使用 listen() 函数开始监听来自客户端的连接请求。接受连接使用 accept() 函数接受客户端的连接并生成一个新的套接字用于与客户端通信。
小结
bind() 函数将套接字绑定到一个IP地址和端口号用于服务器端程序监听来自客户端的连接。sockaddr_in 结构体用于指定绑定的IP地址和端口号。绑定后服务器可以使用 listen() 和 accept() 函数与客户端进行通信。
3.监听连接请求listen()
在网络编程中服务器端需要监听客户端的连接请求以便处理它们。listen() 函数就是用来让服务器开始监听连接请求的。
listen() 函数的作用
listen() 函数的作用是将套接字设为被动模式从而告诉操作系统这个套接字将用于接受连接。这个函数的主要功能是
让服务器开始监听来自客户端的连接请求。设置连接队列的最大长度即客户端连接请求的等待队列。
listen() 的函数原型以 C/C 为例
int listen(int sockfd, int backlog);sockfd由 socket() 函数返回的套接字描述符这个套接字已经通过 bind() 函数绑定到一个本地 IP 地址和端口号。backlog指定在处理客户端连接之前内核允许的最大等待连接数。它表示在服务器开始处理请求前客户端连接可以在队列中等待的数量上限。
使用 listen() 的步骤
1)创建套接字通过 socket() 函数创建套接字。 (2)绑定 IP 地址和端口通过 bind() 函数将套接字绑定到一个本地 IP 地址和端口号。 (3)开始监听调用 listen() 函数开始监听客户端的连接请求。
代码示例C/C
#include iostream
#include sys/socket.h
#include netinet/in.h
#include unistd.hint main() {// 1. 创建套接字int server_fd socket(AF_INET, SOCK_STREAM, 0);if (server_fd -1) {std::cerr Failed to create socket\n;return -1;}// 2. 绑定 IP 地址和端口号struct sockaddr_in server_addr;server_addr.sin_family AF_INET; // 使用 IPv4server_addr.sin_addr.s_addr INADDR_ANY; // 绑定到所有可用的网络接口server_addr.sin_port htons(8080); // 绑定端口 8080if (bind(server_fd, (struct sockaddr*)server_addr, sizeof(server_addr)) -1) {std::cerr Bind failed\n;close(server_fd);return -1;}// 3. 开始监听允许最多 10 个待处理连接if (listen(server_fd, 10) -1) {std::cerr Listen failed\n;close(server_fd);return -1;}std::cout Server is listening on port 8080\n;// 4. 等待客户端连接这里只是展示未做实际 accept 操作// ...// 关闭套接字close(server_fd);return 0;
}参数说明
server_fd服务器套接字描述符通过 socket() 函数创建。10backlog最大连接等待队列长度。当多个客户端几乎同时尝试连接服务器时服务器会把这些请求放在一个队列中这个参数决定队列的大小。超过这个数量的连接请求将被拒绝返回错误。
监听的实际含义
(1)被动模式listen() 函数将套接字转变为被动模式被动模式意味着这个套接字将用于接受传入的连接而不会主动向其他服务器发出连接请求。服务器将处于等待状态直到有客户端请求连接。
(2)连接队列当有多个客户端请求连接时操作系统会把这些请求放在一个队列中。backlog 参数决定队列中最多可以有多少个未处理的连接请求。在队列满时如果有新的连接请求它们将被拒绝客户端可能会收到错误信息。
队列的作用
服务器处理每个客户端连接的速度可能不同而客户端请求连接的速度可能较快。backlog 队列允许服务器有缓冲时间来处理请求。如果服务器忙于处理现有的连接其他客户端的连接请求可以暂时存放在队列中等待处理。如果队列已满额外的连接请求将被拒绝。
4.接受客户端连接accept()
accept() 是服务器端网络编程中的一个关键函数用于接受来自客户端的连接请求。服务器在调用 listen() 开始监听客户端的连接后当有客户端尝试连接时accept() 函数负责接受该连接并为此生成一个新的套接字以进行数据传输。
accept() 函数的作用
accept() 函数从服务器的等待队列中取出一个连接请求并与客户端建立连接。它为该连接分配一个新的套接字该套接字将用于与客户端之间的实际通信。原来的监听套接字继续监听其他连接请求。
accept() 的函数原型以 C/C 为例
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockfd服务器的监听套接字文件描述符由 socket() 和 bind() 创建并监听。addr指向一个 sockaddr 结构体的指针用于存储客户端的地址信息客户端的 IP 和端口。addrlenaddr 结构体的大小传入时是该结构体的大小返回时表示客户端地址的实际大小。
返回值
成功时accept() 返回一个新的套接字文件描述符这个描述符用于与客户端进行后续的通信。如果出错返回值为 -1同时设置 errno 以表示错误类型。
使用场景
accept() 通常和 socket()、bind()、listen() 函数一起使用。以下是服务器接受客户端连接的典型步骤
创建套接字使用 socket() 创建服务器端的套接字。绑定 IP 和端口使用 bind() 将套接字绑定到特定的 IP 地址和端口。监听连接请求使用 listen() 函数监听客户端连接请求。接受连接使用 accept() 接受客户端连接并生成一个新的套接字进行通信。
代码示例C/C
#include iostream
#include sys/socket.h
#include netinet/in.h
#include unistd.h
#include arpa/inet.hint main() {// 1. 创建套接字int server_fd socket(AF_INET, SOCK_STREAM, 0);if (server_fd -1) {std::cerr Failed to create socket\n;return -1;}// 2. 绑定 IP 地址和端口号struct sockaddr_in server_addr;server_addr.sin_family AF_INET; // 使用 IPv4server_addr.sin_addr.s_addr INADDR_ANY; // 绑定到所有可用的网络接口server_addr.sin_port htons(8080); // 绑定端口 8080if (bind(server_fd, (struct sockaddr*)server_addr, sizeof(server_addr)) -1) {std::cerr Bind failed\n;close(server_fd);return -1;}// 3. 开始监听if (listen(server_fd, 10) -1) {std::cerr Listen failed\n;close(server_fd);return -1;}std::cout Server is listening on port 8080\n;// 4. 接受客户端连接struct sockaddr_in client_addr;socklen_t client_addr_len sizeof(client_addr);int client_fd accept(server_fd, (struct sockaddr*)client_addr, client_addr_len);if (client_fd -1) {std::cerr Accept failed\n;close(server_fd);return -1;}std::cout Connection accepted from inet_ntoa(client_addr.sin_addr) : ntohs(client_addr.sin_port) \n;// 与客户端通信代码略// 关闭客户端和服务器套接字close(client_fd);close(server_fd);return 0;
}参数解释
server_fd由 socket() 函数创建的服务器监听套接字。client_addr这是一个 sockaddr_in 结构体存储客户端的 IP 地址和端口号。client_addr_len表示 client_addr 结构体的大小。
accept() 的流程
(1)阻塞行为accept() 是一个阻塞函数意味着它会一直等待直到有客户端连接请求到达。如果没有连接请求服务器会在此函数上阻塞。
(2)返回客户端套接字一旦连接请求到达accept() 从连接队列中取出请求返回一个新的套接字用于与客户端进行通信。
(3)服务器继续监听原来的监听套接字 (server_fd) 仍然处于监听状态可以接受其他客户端连接请求而当前客户端的通信使用新的套接字 (client_fd) 进行。
常见用法
服务器通过 accept() 获取与客户端通信的专用套接字。后续的数据接收和发送可以使用 read() 和 write() 或 recv() 和 send()。通信结束后服务器需要调用 close() 关闭新的套接字以释放资源。
总结
accept() 用于从连接队列中取出客户端的连接请求建立连接。返回一个新的套接字用于服务器与客户端的通信。原监听套接字继续等待其他连接请求。
通过 accept()服务器能够与多个客户端进行通信每个客户端都有自己独立的套接字而服务器的监听套接字则持续接收新的连接请求。
5.发送和接收数据send()/recv()
在网络编程中服务器和客户端建立连接后可以通过 send() 和 recv() 函数来发送和接收数据。这两个函数分别用于通过套接字向远程端发送数据和从远程端接收数据。
send() 函数
send() 函数用于通过套接字发送数据到连接的另一端。
send() 的函数原型
ssize_t send(int sockfd, const void *buf, size_t len, int flags);sockfd套接字描述符用于标识连接。buf指向存储待发送数据的缓冲区的指针。len要发送的数据长度字节数。flags发送选项通常设置为 0。可以设置不同的标志来控制发送行为例如 MSG_DONTWAIT非阻塞发送。
返回值
返回实际发送的字节数。如果返回值小于 len表示数据未完全发送。如果返回 -1表示发送失败并设置 errno。
recv() 函数
recv() 函数用于通过套接字从连接的另一端接收数据。
recv() 的函数原型
ssize_t recv(int sockfd, void *buf, size_t len, int flags);sockfd套接字描述符标识连接。buf指向接收数据的缓冲区的指针。len缓冲区的大小即最多接收多少字节。flags接收选项通常设置为 0。可以设置不同的标志来控制接收行为例如 MSG_WAITALL等待所有数据。
返回值
返回实际接收到的字节数。如果返回 0表示连接已关闭。如果返回 -1表示接收失败并设置 errno。
发送和接收数据的流程
服务器和客户端建立连接。服务器使用 recv() 函数接收来自客户端的数据。客户端使用 send() 函数向服务器发送数据或反之。在发送或接收完数据后双方可以通过 close() 关闭套接字。
代码示例
以下是使用 send() 和 recv() 进行数据发送和接收的代码示例
#include iostream
#include sys/socket.h
#include netinet/in.h
#include unistd.h
#include arpa/inet.hint main() {// 1. 创建套接字int server_fd socket(AF_INET, SOCK_STREAM, 0);if (server_fd -1) {std::cerr Failed to create socket\n;return -1;}// 2. 绑定 IP 地址和端口号struct sockaddr_in server_addr;server_addr.sin_family AF_INET;server_addr.sin_addr.s_addr INADDR_ANY;server_addr.sin_port htons(8080);if (bind(server_fd, (struct sockaddr*)server_addr, sizeof(server_addr)) -1) {std::cerr Bind failed\n;close(server_fd);return -1;}// 3. 开始监听if (listen(server_fd, 10) -1) {std::cerr Listen failed\n;close(server_fd);return -1;}std::cout Server is listening on port 8080\n;// 4. 接受客户端连接struct sockaddr_in client_addr;socklen_t client_addr_len sizeof(client_addr);int client_fd accept(server_fd, (struct sockaddr*)client_addr, client_addr_len);if (client_fd -1) {std::cerr Accept failed\n;close(server_fd);return -1;}// 5. 接收数据char buffer[1024] {0};ssize_t recv_len recv(client_fd, buffer, sizeof(buffer), 0);if (recv_len -1) {std::cerr Receive failed\n;close(client_fd);close(server_fd);return -1;}std::cout Received from client: buffer std::endl;// 6. 发送数据const char *response Hello from server;if (send(client_fd, response, strlen(response), 0) -1) {std::cerr Send failed\n;}// 7. 关闭套接字close(client_fd);close(server_fd);return 0;
}服务端代码
#include iostream
#include sys/socket.h
#include netinet/in.h
#include unistd.h
#include arpa/inet.hint main() {// 1. 创建套接字int client_fd socket(AF_INET, SOCK_STREAM, 0);if (client_fd -1) {std::cerr Failed to create socket\n;return -1;}// 2. 连接到服务器struct sockaddr_in server_addr;server_addr.sin_family AF_INET;server_addr.sin_port htons(8080);server_addr.sin_addr.s_addr inet_addr(127.0.0.1);if (connect(client_fd, (struct sockaddr*)server_addr, sizeof(server_addr)) -1) {std::cerr Connection failed\n;close(client_fd);return -1;}// 3. 发送数据const char *message Hello from client;if (send(client_fd, message, strlen(message), 0) -1) {std::cerr Send failed\n;}// 4. 接收数据char buffer[1024] {0};ssize_t recv_len recv(client_fd, buffer, sizeof(buffer), 0);if (recv_len -1) {std::cerr Receive failed\n;close(client_fd);return -1;}std::cout Received from server: buffer std::endl;// 5. 关闭套接字close(client_fd);return 0;
}关键点
阻塞行为send() 和 recv() 默认是阻塞的意味着如果网络缓慢或没有数据到达程序会等待。可以通过设置非阻塞模式改变这种行为。返回值检查检查 send() 和 recv() 的返回值很重要以确保数据正确发送和接收并处理错误情况。数据传输的长度send() 可能不会一次发送所有数据程序需要处理这种情况确保所有数据都被发送。连接关闭如果 recv() 返回 0表示对方关闭了连接。
总结
send() 用于通过套接字发送数据。recv() 用于接收来自远程端的数据。它们是实现服务器与客户端通信的基础工具通常与 socket()、bind()、connect() 等函数配合使用。
6.关闭连接close()
在网络编程中close() 函数用于关闭套接字并释放与该套接字相关的资源。当服务器或客户端不再需要与对方通信时调用 close() 可以终止连接。
close() 函数的作用
close() 函数不仅仅是关闭文件描述符在这种情况下是套接字还会终止与该套接字关联的 TCP 连接释放所有资源。如果套接字是连接的一部分如 TCP 连接则会通知对端连接已关闭后续通信不再可能。
close() 函数的原型
int close(int sockfd);sockfd套接字描述符标识要关闭的连接。
返回值
返回 0 表示成功。返回 -1 表示失败并设置 errno以提供错误的具体信息。
关闭连接的流程
当服务器或客户端调用 close() 函数时系统会开始执行 TCP 的四次挥手Four-Way Handshake协议以优雅地关闭连接。在这个过程中系统会将套接字的状态从 ESTABLISHED已建立连接变为 FIN_WAIT 等不同状态直到连接完全关闭。调用 close() 后系统会释放与该套接字关联的所有内存资源包括文件描述符、缓冲区等。
客户端
1.创建套接字socket()
2.连接到服务器connect()
3.发送和接收数据send()/recv()
4.关闭连接close()
Socket广泛应用于各种网络通信场景如HTTP、FTP、电子邮件等协议的底层实现。
示例
服务端
#include iostream
#include cstring
#include sys/socket.h //包含socket函数和数据结构
#include netinet/in.h //包含Internet地址簇
#include unistd.h //包含标准符号常数和类型using namespace std;
const int PORT9006;int main(){int server_fd,new_socket;//定义套接字文件描述符/*server_fd表示服务器的文件描述符在网络编程中套接字socket通过文件描述符来操作就像操作文件一样。new_socket表示新的连接套接字当服务器接受客户端的连接请求时它会创建一个新的套接字来处理这个连接这个新的套接字通过 new_socket 变量来表示。*/struct sockaddr_in address;//定义地址结构体/*sockaddr_in 结构体是一个非常重要的数据结构它用于存储网络地址信息。这个结构体是 sockaddr 结构体的一个特化版本专门用于IPv4地址。在 netinet/in.h 头文件中定义*/int addrlensizeof(address);//地址长度char buffer[1024]{0};//定义缓冲区/*定义了一个大小为1024字节的字符数组用于存储接收到的数据或将要发送的数据。{0} 初始化数组中的所有元素为0这是一个常见的做法用于确保缓冲区不包含任何随机数据。当然了它还有保证数据完整性提高效率数据格式化防止数据丢失等功能这里是简单示例主要就是用于存储接收到的数据或将要发送的数据就不做解释了*/const char *message你说得对;//定义服务器发送的消息使用 const 可以防止函数意外修改字符串内容。安全//创建套接字用于在C网络编程中创建一个TCP套接字的。/* if((server_fdsocket(AF_INET,SOCK_STREAM,0))0){perror(socket failed);exit(EXIT_FAILURE);}*/if ((server_fd socket(AF_INET, SOCK_STREAM, 0)) -1) {perror(socket failed);exit(EXIT_FAILURE);}/*socket() 是一个系统调用用于创建一个新的套接字。它返回一个文件描述符该文件描述符是用于后续套接字操作如绑定、监听、连接等的索引。AF_INET表示IPv4地址族用于创建一个基于IPv4的套接字。SOCK_STREAM表示创建一个提供序列化、可靠、双向连接的字节流套接字这通常用于TCP连接。传递0表示使用默认的协议对于AF_INET和SOCK_STREAM这意味着使用TCP协议。 0检查server_fd是否为0即检查套接字是否创建失败。perror()函数将打印出错误信息到标准错误流exit()函数用于终止当前程序并返回一个状态码给操作系统。EXIT_FAILURE是一个宏通常定义为非零值表示程序异常终止
*///设置套接字选项允许地址重用int opt1;if(setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,opt,sizeof(opt))){perror(setsockopt);exit(EXIT_FAILURE);}/*opt这个变量用于表示套接字选项的状态1 表示启用该选项。setsockopt() 是一个系统调用用于设置套接字选项。它可以改变套接字的行为例如如何接收数据或者如何处理特殊的网络条件。SOL_SOCKET是一个指定套接字选项级别的常量表示这是针对套接字本身的选项而不是针对某个特定协议的。SO_REUSEADDR 是一个套接字选项允许套接字绑定到一个已经被使用在TIME_WAIT状态的本地地址和端口。SO_REUSEPORT 是一个扩展选项允许多个套接字绑定到同一个端口只要它们的地址不同。如果 setsockopt() 返回非零值表示设置选项失败。*///绑定套接字到端口address.sin_familyAF_INET;//地址簇,address.sin_addr.s_addrINADDR_ANY;//地址。INADDR_ANY表示接受任意IP的连接address.sin_porthtons(PORT);//端口号htons函数将主机字节序转换为网络字节序/*这三段代码是设置服务器端套接字地址信息的一部分它们配置了服务器将监听的协议族、IP地址和端口号sin_family 是 sockaddr_in 结构体的一个成员它指定了地址族。设置 sin_family 为 AF_INET 表示服务器将使用 IPv4 协议。sin_addr 是 sockaddr_in 结构体的一个成员它是一个 in_addr 结构体用于存储 IPv4 地址。INADDR_ANY 是一个特殊的常量当绑定套接字时使用。它告诉操作系统自动绑定到所有可用的网络接口的 IPv4 地址。sin_port 是 sockaddr_in 结构体的一个成员用于指定端口号。htons() 函数是 host to network short 的缩写它将一个短整型16位从主机字节序转换为网络字节序。*///绑定套接字if(bind(server_fd,(struct sockaddr *)address,sizeof(address))0){perror(bind failed);exit(EXIT_FAILURE);}/*bind() 是一个系统调用用于将一个套接字server_fd绑定到指定的地址和端口上。(struct sockaddr *)address将address变量的地址传递给bind()函数其他基本的东西都和上面提到过的大相径庭*///监听套接字if(listen(server_fd,3)0){//监听参数3表示最大连接数perror(listen);exit(EXIT_FAILURE);}/*listen() 是一个系统调用用于告诉内核准备接受连接请求。这个函数将一个被动套接字也称为监听套接字转变为监听状态。参数 3 表示服务器将允许最多3个连接请求在队列中等待接受。*/coutListening on portPORTendl;//接受客户端连接if((new_socketaccept(server_fd,(struct sockaddr*)address,(socklen_t*)addrlen))0){perror(accept);exit(EXIT_FAILURE);}/*accept() 是一个系统调用用于从监听队列中取出第一个连接请求并为该请求创建一个新的套接字。*///读取客户端发送的数据read(new_socket,buffer,1024);//读取数据到缓冲区cout消息来自丽宝:bufferendl;/*read() 是一个系统调用用于从文件描述符在这里是套接字读取数据。new_socket是通过 accept() 函数创建的新套接字的文件描述符用于与客户端进行通信*///发送数据回客户端send(new_socket,message,strlen(message),0);//发送消息cout消息发送成功\n;//关闭套接字close(new_socket);//关闭客户端套接字close(server_fd);//关闭服务器套接字return 0;
}客户端
#include iostream
#include sys/socket.h // 包含socket函数和数据结构
#include arpa/inet.h // 包含inet函数用于IP地址转换
#include unistd.h // 包含标准符号常数和类型
#include string.h using namespace std;const int PORT 9006; // 定义服务器的端口号int main() {int sock 0; // 定义套接字文件描述符struct sockaddr_in serv_addr; // 定义服务器地址结构体const char *hello 302女的美男的帅; // 定义客户端发送的消息char buffer[1024] {0}; // 定义接收数据的缓冲区/*sock 用于存储客户端套接字的文件描述符它是网络通信中的一个重要概念代表了网络通信的一端。*/// 创建套接字if ((sock socket(AF_INET, SOCK_STREAM, 0)) 0) {cout \n Socket creation error \n;return -1;}/*如果创建成功sock 将包含一个非负值如果创建失败则为 -1。*/serv_addr.sin_family AF_INET; // 服务器地址族serv_addr.sin_port htons(PORT); // 服务器端口号// 将文本形式的IP地址转换为二进制形式if (inet_pton(AF_INET, 127.0.0.1, serv_addr.sin_addr) 0) {cout \nInvalid address / Address not supported \n;return -1;}/*inet_pton() 用于将表示网络地址的字符串转换为网络字节序的二进制形式。*/// 连接到服务器if (connect(sock, (struct sockaddr *)serv_addr, sizeof(serv_addr)) 0) {cout \nConnection Failed \n;return -1;}/*connect() 用于客户端套接字与服务器套接字建立连接。*/// 发送数据send(sock, hello, strlen(hello), 0); // 发送消息cout 消息发送成功\n;// 接收服务器发送的数据read(sock, buffer, 1024); // 从服务器接收数据cout 消息来自赟赟: buffer std::endl;// 关闭套接字close(sock); // 关闭套接字return 0;
}
运行指令
g -o server server.cpp
g -o client client.cpp
./server
./client