linux之TCP

1. 什么是TCP协议

  • TCP(传输控制协议,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流 的传输层通信协议。它旨在提供可靠的端到端通信 ,在发送数据之前,需要在两个通信端点之间建立连接。TCP 通过一系列机制确保数据的可靠传输 ,这些机制包括序列号、确认应答、重传控制、流量控制和拥塞控制

2. TCP协议特征

  • 面向连接
    • TCP 是一种面向连接的协议 ,这意味着在数据交换之前,两个通信端 必须先建立连接。这个连接通过一个三次握手过程(SYN、SYN-ACK、ACK)来建立,确保双方都准备好数据交换。
  • 可靠传输
    • TCP 通过序列号和确认应答机制确保数据的可靠传输。发送方为每个报文段分配一个序列号,接收方通过发送确认应答(ACK)来确认已经收到特定序列号的报文段。如果发送方没有在合理的超时时间内收到确认应答,它将重传该报文段。
  • 流量控制
    • TCP 使用窗口大小调整机制来进行流量控制防止发送方过快地发送数据,导致接收方来不及处理。通过调整窗口大小,TCP 能够动态地管理数据的传输速率,避免网络拥塞和数据丢失。
  • 拥塞控制
    • TCP 实现了拥塞控制算法 (如慢启动、拥塞避免、快重传和快恢复),以避免网络中的过度拥塞。这些算法可以根据网络条件动态调整数据的发送速率,从而提高整个网络的效率和公平性。
  • 数据排序
    • 由于网络延迟和路由变化 ,TCP 报文段可能会乱序到达接收方。TCP 能够根据序列号重新排序乱序到达的报文段,确保数据以正确的顺序交付给应用层。
  • 端到端通信
    • TCP 提供端到端的通信。每个 TCP 连接由四个关键元素唯一确定:源 IP 地址 ( 那个电脑 ) 、源端口号 ( 那个服务 ) 、目标 IP 地址、目标端口号。这种方式确保了数据能够在复杂的网络环境中准确地从一个端点传输到另一个端点。

3. TCP报文格式

  • 如图
  • 补充

    • 序号不是连续,是序号 + 数据长度,假如第一次发送是100个字节,并且首次发送序号为0,第二次发送时 0 + 100,第三次是100 + 100......
    • 源端口号(16位):标识发送方的端口号。
    • 目的端口号(16位):接收方的端口号。
    • 序列号(32位):标识从发送方发送的数据字节流的序列号,用于数据的顺序控制。
    • 确认号(32位):用于确认接收到的数据, 确认号是指 期望收到的下一个字节的序列号。
    • 数据偏移位(4位):表示 TCP 头部的长度。
    • 保留位(6位):必须为0,保留字段,不使用,只有在数据偏移量不够的时候才使用。
    • 标志字段共有六个比特,每个比特对应一个标志:URG、ACK、PSH、RST、SYN、FIN。 这些标志用于控制 TCP 的不同行为,例如建立连接(SYN),终止连接(FIN),指示数据急迫性(URG)等。
      • URG:紧急标志,为 1 表示当前报文段存在被发送端上层实体置为"紧急"的数据接收方应当优先处理这些数 据。紧急指针字段指出了这部分数据的结束位置
      • ACK:确认标志,为 1 指示 确认字段的值是有效的。该报文段包含对已被成功接收报文段的确认。连接建立后,直至释放前传输的所有报文段 ACK 标志均为 1
      • PSH:为 1 指示接收方应立即将数据交给上层。
      • RST:为 1 表示链接出现错误,要求接收方终止连接,并重新建立连接。
      • SYN:该标志位用于建立连接。TCP 连接建立时的前两次握手 SYN 为 1。
      • FIN:为 1 表示发送方已经没有数据发送,想要断开连接。
  • 窗口大小(16位):控制流量控制,指示接收方的缓冲区大小,控制对方发送数据量是多少,避免溢出

  • 校验和(16位):用于错误检测。

  • 紧急指针(16位):指向紧急数据的偏移量,一个 16 位的字段,如果 URG 标志被设置则为正数,表明从当前序列号开始的紧急数据的字节偏移量。

  • 选项(长度可变):可变长的字段,用于指定 TCP 选项,可以添加一下参数上去。

  • 填充(长度可变):用于确保头部长度是 32 位字对齐,如果不是4个字节的话,会补充。

4. 三次握手

  • 如图,这是TCP三次握手的图解

  • 解释

    • 用小写的 seq 表示 TCP 报文头部的序号,用小写的 ack 表示确认号。未提到的标志位均为 0。
    • TCP 服务器准备好 接受连接,进入 LISTEN状态,这一过程称为被动打开。
    • 第一次握手:客户端发送 SYN ( 建立连接 ) 标志为 1,和随机生成的初始化序列号 seq 报文段,请求建立连接 。此时的 seq 记为 ISN(c)(Initial Sequence Number,初始序列号 ),括号中的 c 表示这是客户端的序列号。客户端发送后变为 SYN-SENT 状态。 (请求连接,并告知客户端的初始化序列号)
  • 第二次握手:服务端收到客户端的第一次握手信号。随即确认客户端的 SYN 报文段 ,发送一个 ACK ( 确认 ) 和 SYN 标志均为 1 的报文段。该报文段中ack=ISN(c)+1seq 随机,标记为 ISN(s),此处的 s 表示这是服务端的序列号。服务端变为SYN-RCVD 状态。 (确认客户端的请求,服务器同意连接,并告诉了给客户端知道服务器序列号)

  • 第三次握手:客户端收到服务端的第二次握手信号,随即确认服务端的报文段发送 ACK 标志为 1 的报文段。该报文段中 ack=ISN(s)+1,seq=ISN(c)+1。服务端收到客户端的第三次握手信号之后变为 ESTABLISHED 状态。(客户端确立了服务端响应,连接正式确立)。

  • 部分状态的意思

    • CLOSED
      • 这是初始状态和最终状态,表示没有活动的连接,也没有正在进行的通信
    • LISTEN
      • 在服务器端监听来自客户端的连接请求。服务器在这个状态下等待进入的 SYN 报文
    • SYN_SENT
      • 客户端发送一个连接请求到服务器(SYN 包),然后转入 SYN_SENT 状态等待服务器确认
    • SYN_RECEIVED
      • 服务器接收到客户端的 SYN 报文后,响应一个 SYN+ACK 报文,同时进入SYN_RECEIVED 状态。这个状态表明服务器端已响应连接请求。
    • ESTABLISHED
      • 连接已建立成功,数据可以开始传输。这是在三次握手成功完成后,双方都会进入的状态。

5. 四次挥手

  • 如图
  • 解释
    • TCP 的连接释放过程 又称为四次挥手。
    • 四次挥手可以由客户端发起,也可以由服务端发起。此处假设连接请求的断开操作由客户端发起。连接断开前,双方都处于 ESTABLISHED 状态。需要注意的是,连接建立后,即客户端和服务端处于 ESTABLISHED 时,双方发送的报文段 ACK 标志均为 1。
    • 我们用小写的 seq 表示 TCP 报文头部的序号,用小写的 ack 表示确认号。未提到的标志位均为 0。
    • 第一次挥手:客户端发送FIN ( 取消标志 ) 标志为 1(即 FINISH,表示通信结束)的报文段,请求断开连接 ,执行主动关闭。此时,报文段中包含对于 **服务端数据的确认,ACK 为 1,假设 ack=V(当前确认号)。**连接断开前已经历了一系列的数据传输,seq 取决于之前已发送的报文段,假设 seq=U(当前序列号)。客户端状态变为 FIN-WAIT-1。
    • 第二次挥手:服务端接收到第一次挥手信息,随即发送 ACK 标志为 1,ack=U+1 的报文段,此时 seq=V。客户端接收到服务端的第二次挥手信号,变为 FIN-WAIT-2 状态。第二次挥手后,服务端仍可发送数据,客户端仍可接收。
  • 第三次挥手:服务端完成数据传送后,发送 FIN 标志和 ACK 标志均为 1 的报文段,ack=U+1,seq 大于 V,假设为 W,请求断开连接,这一过程称为被动关闭。服务端发送第三次挥手信号后,变为 LAST-ACK 状态。
  • 第四次挥手:客户端收到第三次挥手信号,随即发送 ACK 标志为 1,seq=U+1,ack=W+1 的报文段,变为 TIME-WAIT 状态。服务端收到第四次挥手信号,客户端从变为 TIME-WAIT 状态开始计时,等待 2MSL(2 倍最大报文时长,约定值)后进入 CLOSED 状态。四次挥手结束。
  • 部分状态的意思
    • FIN_WAIT_1
      • 在一个 TCP 连接中,当一端完成它的数据发送任务后,它会发送一个 FIN 报文,表示它已经完成数据发送,进入 FIN_WAIT_1 状态。
    • FIN_WAIT_2
      • 在发送了 FIN 报文并接收到对方的 ACK 响应后,进入 FIN_WAIT_2 状态。在这个状态下,连接一端等待对方的 FIN 报文。
    • CLOSE_WAIT
      • 在接收到对方的 FIN 报文后,进入 CLOSE_WAIT 状态。在这个状态下,TCP 连接的这一端知道对方已经没有数据发送,但本地可能还有数据需要发送。
    • CLOSING
      • 在同时关闭的情况下,当两端几乎同时发送 FIN 报文时,可能会进入 CLOSING 状态,表示双方都在等待对方的 FIN 报文的确认。
    • LAST_ACK
      • 在发送完自己的所有数据并发送了 FIN 报文后,如果还需要等待来自对方的最后一个ACK,就会进入 LAST_ACK 状态。
    • TIME_WAIT
      • 当 TCP 连接的一端接收到对方的 FIN 报文并发送了 ACK 报文后,它会进入TIME_WAIT 状态。该状态会持续一段时间(2 倍的 MSL,最大报文生存时间),以确保对方接收到最后一个 ACK 报文,这样可以正确地关闭连接,同时删除以前发过的报文确保旧的重复报文不会在网络中造成混乱。

**6.**常用函数

6.1 socket

cpp 复制代码
#include <sys/socket.h> 
#include <sys/types.h>

/** 
* @brief 在通信域中创建一个未绑定的 socket,并返回一个文件描述符,该描述符可以在后续对 socket 进行操作的函数调用中使用 
* 
* @param domain 指定要创建套接字的通信域。
* AF_INET:IPv4 互联网协议。

* @param type 指定要创建的 socket 类型 
* SOCK_STREAM:提供序列化、可靠的、双向的、基于连接的字节流。可以支持带外数据传输机制。 
* SOCK_DGRAM:支持数据报(无连接、不可靠的固定最大长度的消息)。

* @param protocol通常写0,让系统自动选择默认协议 
* @return int 文件描述符,如果失败返回-1 
*/ 
int socket(int domain, int type, int protocol);

6.2 bind :功能:用于绑定 IP 地址和端口号到 socketfd

cpp 复制代码
/**
* @param sockfd 套接字文件描述符 
* @param addr 指定的地址。地址的长度和格式取决于 socket 的地址族 
* @param addrlen addr 指向的地址结构的大小(以字节为单位)。 
* @return int 成功 0 
* 失败 -1 
*/ 
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

• 地址族:指定了套接字(socket)使用的网络协议类型以及地址的格式。

• 如图,结构:

• 一般来说是使用sockaddr_in,最后写回去函数需要强制转换

所以一般来说,sin_family一般使用这个:AF_INET

代表 IPv4 网络协议的地址族,使用 32 位地址。

主要用于互联网上的通信。

地址格式通常为点分十进制,如 192.168.1.1。

注意这个sin_addr比较特别不能直接赋值,需要使用相关函数去转换IP地址。

6.3 listen

cpp 复制代码
/** 
* @brief 将 sockfd 指定的套接字标记为被动套接字,即将用于使用 accept接受传入的连接请求。 
* 
* @param sockfd 套接字文件描述符 
* @param backlog 指定还未 accpet 但是已经完成链接的队列长度(说明最多有多少个客户端连接)。
* @return int 成功 0 
* 
失败 -1 
*/ 
int listen(int sockfd, int backlog);

6.4 accept

cpp 复制代码
/** 
* @brief 从监听套接字 sockfd 的待处理连接队列中提取第一个连接请求,创建一个新的连接套接字,并返回指向该套接字的新文件描述符。新创建的套接字不处于监听状态。 原始套接字 sockfd 不受此调用的影响。 
* 
* @param sockfd 一个使用 socket创建、使用 bind绑定到本地地址,并在listen后监听连接的套接字。 
* @param addr 要么是一个空指针,要么是一个指向 sockaddr 结构的指针,用于返回连接 socket 的地址。 
* @param addrlen 如果 address 是空指针,则为一个空指针;如果 address 不是空指针,则为一个指向 socklen_t 对象的指针,该对象在调用前指定提供的 sockaddr 结构的长度,并在调用后指定存储地址的长度。 
* @return int 返回一个新的套接字文件描述符,用于与客户端通信,如果失败返回-1, 并设置 errno 来表示错误原因 
*/ 
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

6.5 connect

cpp 复制代码
/** 
* @brief 由客户端调用,来与服务端建立连接。 
* 
* @param sockfd 客户端套接字的文件描述符 
* @param addr 指向 sockaddr 结构体的指针,包含目的地地址信息 
* @param addrlen 指定 addr 指向的结构体的大小 
* @return int 成功 0 
* 失败 -1,并设置 errno 以指示错误原因 
*/ 
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

6.6 send

cpp 复制代码
/** 
* @brief 用于向另一个套接字传输消息。 
*
* @param sockfd 发送套接字的文件描述符。 
* @param buf 发送缓冲区,并非操作系统分配为服务端和客户端分配的缓冲区,而是用户为了发送数据,自己维护的字节序列。const 修饰表名它是"只读"的,即 send 函数不会修改这块内存的内容。 
* @param len 要发送的数据的字节长度。它决定了从 buf 指向的缓冲区中将发送多少数据。 
* @param flags flags 参数是以下标志之一或多个的按位或。对于大多数应用,这个参数被设置为 0,表示不使用任何特殊行为。 

* @return ssize_t 成功发送的字节数。如果出现错误,它将返回-1,并设置 errno 以指示错误的具体原因。 
*/ 
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

6.7 recv

cpp 复制代码
/** 
* @brief 从套接字关联的连接中接收数据。 
* 
* @param sockfd 套接字文件描述符。 
* @param buf 接收缓冲区,同样地,此处也并非内核维护的缓冲区。
* @param len 缓冲区长度,即 buf 可以接收的最大字节数。 
* @param flags flags 参数是以下标志之一或多个的按位或。对于大多数应用,这个参数被设置为 0,表示不使用任何特殊行为。
* @return ssize_t 返回接收到的字节数,如果连接已经正常关闭,返回值将是 0。如果出现错误,返回-1,并且 errno 变量将被设置为指示错误的具体原因。 
*/ 
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

6.8 shutdown

cpp 复制代码
/** 
* @brief 关闭套接字的一部分或全部连接 
* 
* @param sockfd 套接字文件描述符 
* @param how 指定关闭的类型。其取值如下: 
* SHUT_RD:关闭读。之后,该套接字不再接收数据。任何当前阻塞在 recv 调用上的操作都将返回 0,表示连接的另一端已经关闭。 
* SHUT_WR:关闭写。之后,试图通过该套接字发送数据将导致错误。如果使用此选项,TCP 连接将发送一个 FIN 包给连接的对端,表明此方向上的数据传输已经完成。此时对端的 recv 调用将接收到 0。 
* SHUT_RDWR:关闭读写。同时关闭套接字的读取和写入部分,等同于分别调用 SHUT_RD 和 SHUT_WR。之后,该套接字既不能接收数据也不能发送数据。 
* @return int 成功 0 
* 失败 -1,并设置 errno 变量以指示具体的错误原因。 
*/ 
int shutdown(int sockfd, int how);

6.9 close

cpp 复制代码
#include <unistd.h> 
/* 
	用于关闭一个之前通过 open()、socket()等函数打开的文件描述符 
	int __fd: 这是一个整数值,表示要关闭的文件描述符 
	return: 成功关闭文件描述符时,close()函数返回 0发送失败,例如试图关闭一个已经关闭的文件描述符或系统资源不足,close()会返回-1
*/ 
int close(int __fd);

6.10 网络字节序和主机字节序转换

6.10.1 两种字节序

  • 在网络编程中,特别是在跨平台和网络通信时,字节序(Byte Order)是非常重要的概念。字节序指的是多字节数据在内存中的存储顺序。主要有两种字节序
  • 大端字节序(Big-Endian):高位字节存储在内存的低地址处低位字节存储在高地址处 。这种字节序遵循自然数字的书写习惯,也被称为网络字节序(Network ByteOrder)或网络标准字节序,因为它在网络通信中被广泛采用,如 IP 协议就要求使用大端字节序。
  • 小端字节序(Little-Endian):低位字节存储在内存的低地址处,高位字节存储在高地址处。称为主机字节序(Host Byte Order)。

6.10.2 函数

cpp 复制代码
#include <arpa/inet.h> 
/** 
* @brief 将无符号整数 hostlong 从主机字节顺序(h)转换为网络字节顺序(n)。 
*/ 
uint32_t htonl(uint32_t hostlong); 

/** 
* @brief 将无符号短整数 hostshort 从主机字节顺序(h)转换为网络字节顺序 (n)。 
*/ 
uint16_t htons(uint16_t hostshort); 
/**
* @brief 将无符号整数 netlong 从网络字节顺序(n)转换为主机字节顺序(h)。 
*/ 
uint32_t ntohl(uint32_t netlong); 
/** 
* @brief 将无符号短整数 netshort 从网络字节顺序(n)转换为主机字节顺序(h)。 
*/ 
uint16_t ntohs(uint16_t netshort);

6.10.3 函数

cpp 复制代码
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
/** 
* @brief 将来自 IPv4 点分十进制表示法的 Internet 主机地址 cp 转换为二进制形式(以网络字节顺序)并将其存储在 inp 指向的结构体中。 
* @return int 成功返回 1;失败 返回 0 
*/ 
int inet_aton(const char *cp, struct in_addr *inp); 

/** 
* @brief 将来自 IPv4 点分十进制表示法的 Internet 主机地址 cp 转换为网络字节顺序的二进制数据。 
* @return 如果输入无效,则返回 INADDR_NONE(通常为 -1)。使用此函数存在问题,因为 -1 是一个有效地址(255.255.255.255)。请优先使用 inet_aton()、inet_pton() 或 getaddrinfo(),它们提供了更清晰的错误返回方式。 
*/ 
in_addr_t inet_addr(const char *cp); 

/**
* @brief 函数将字符串 cp(以 IPv4 点分十进制表示法表示)转换为适合用作Internet 网络地址的主机字节顺序中的数字。 
* @return in_addr_t 成功时,返回转换后的地址。如果输入无效,则返回 -1。 
*/ 
in_addr_t inet_network(const char *cp); 

/* @brief 字符串格式转换为 sockaddr_in 格式 
@param int af: 通常为 AF_INET 用于 IPv4 地址,或 AF_INET6 用于 IPv6 地址 
@param char *src: 包含 IP 地址字符串的字符数组,如果是 IPv4 地址,格式为点 分十进制(如 "192.168.1.1");如果是 IPv6 地址,格式为冒号分隔的十六进制表示 (如 "2001:0db8:85a3:0000:0000:8a2e:0370:7334") 
@param void *dst:指向一个足够大的缓冲区(对于 IPv4 是一个 struct in_addr 结构体,对于 IPv6 是一个 struct in6_addr 结构体),用于存储转换后的二进制 IP 地址 
@return int : 成功转换返回 0; 输入地址错误返回 1;发生错误返回-1 
*/ 
int inet_pton(int af, const char *src, void *dst); 

/** 
* @brief 
* 
* @param in 将以网络字节顺序给出的 Internet 主机地址 in 转换为 IPv4 点分十 进制表示法的字符串。字符串存储在静态分配的缓冲区中,后续调用将覆盖该缓冲区。 
* @return char* 缓冲区指针 
*/ 
char *inet_ntoa(struct in_addr in); 

/** 
* @brief 是 inet_netof() 和 inet_lnaof() 的反函数。它返回一个以网络字节顺 序表示的 Internet 主机地址,由主机字节顺序中的网络号 net 和本地地址 host 组成。 
*/ 
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host); 

/** 
* @brief 返回 Internet 地址 in 的本地网络地址部分。返回的值以主机字节顺序表示。 
*/ 
in_addr_t inet_lnaof(struct in_addr in); 

/** 
* @brief 返回 Internet 地址 in 的网络号部分。返回的值以主机字节顺序表示。
*/ 
in_addr_t inet_netof(struct in_addr in);

6.10.4 例子

cpp 复制代码
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>


int main(int argc, char const *argv[])
{
    unsigned short local_num = 0x1f,net_num;
    net_num = htons(local_num);//本地转换网络
    printf("本地数据0x%x转换为网络数据0x%x\n",local_num,net_num);


    local_num = ntohs(net_num);//网络转换为本地
    printf("网络数据0x%x转换为本地数据0x%x\n",net_num,local_num);

    return 0;
}

6.10.5 例子

cpp 复制代码
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>


int main(int argc, char const *argv[])
{
    printf("192.168.6.101的十六进制为0x%X 0x%X 0x%X 0x%X\n",192,168,6,101);

    // 网络地址赋值,保存
    struct sockaddr_in server_in;
    struct in_addr server_addr_in;
    in_addr_t server_in_addr_t;

    //inet_aton
    inet_aton("192.168.6.101",&server_addr_in);
    //转化为十六进制是为了方便理解
    printf("经过inet_aton转化之后的IP地址为0x%x\n",server_addr_in.s_addr);

    //inet_addr,不推荐使用,因为输入-1 返回的地址是一个有效地址(255.255.255.255)
    server_in_addr_t = inet_addr("192.168.6.101");
    printf("经过inet_addr转化之后的IP地址为0x%x\n",server_in_addr_t);

    //inet_pton,这个可是指定协议
    inet_pton(AF_INET,"192.168.6.101",&(server_in.sin_addr));
    printf("经过inet_pton转化之后的IP地址为0x%x\n",server_in.sin_addr.s_addr);

    //inet_ntoa
    printf("经过inet_pton转化之后的IP地址为%s\n",inet_ntoa(server_addr_in));

    //inet_lnaof 本地号

    printf("inet_lnaof:0x%X\n",inet_lnaof(server_addr_in));

    //inet_netof 网络号
    printf("inet_netof:0x%X\n",inet_netof(server_addr_in));


    //inet_makeaddr
    struct in_addr tmp = inet_makeaddr(inet_netof(server_addr_in),inet_lnaof(server_addr_in));
    printf("通过inet_makeaddr拼接之后为0x%X\n",tmp.s_addr);
    printf("通过inet_makeaddr拼接之后为%s\n",inet_ntoa(tmp));
    return 0;
}

7. 例子,只接受一个连接的范例程序

7.1 客户端

cpp 复制代码
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>

#define handle_error(cmd, result)   \
        if (result < 0) {           \
            perror(cmd);            \
            return -1;              \
        }


void * read_from_server(void * arg){
    int client_fd = *(int *)arg;
    char * read_buf = NULL;
    read_buf = malloc(sizeof(char) * 1024);
    ssize_t count;
    if(!read_buf){
        perror("malloc");
        shutdown(client_fd,SHUT_RD);
        free(read_buf);
        return NULL;
    }

    while((count = recv(client_fd,read_buf,1024,0)) > 0){
        fputs(read_buf,stdout);
        memset(read_buf,0,sizeof(read_buf));
    }

    free(read_buf);
    shutdown(client_fd,SHUT_RD);
}

void * write_to_server(void * arg){
    int client_fd = *(int *)arg;
    char * write_buf = NULL;
    write_buf = malloc(sizeof(char) * 1024);
    ssize_t count;
    if(!write_buf){
        perror("malloc");
        free(write_buf);
        shutdown(client_fd,SHUT_WR);
        return NULL;
    }

    while(fgets(write_buf,1024,stdin) != NULL){
        count = send(client_fd,write_buf,1024,0);
        if(count < 0){
            perror("send");
            break;
        }
    }

    free(write_buf);
    shutdown(client_fd,SHUT_WR);
}

int main(int argc, char const *argv[])
{
    int socketfd,tmp_result;
    //定义两个结构体,一个表示服务端,一个是客户端的
    struct sockaddr_in server_in,client_in;
    //清空
    memset(&server_in,0,sizeof(server_in));
    memset(&client_in,0,sizeof(client_in));
    //设置客户端的IP
    client_in.sin_family = AF_INET;
    client_in.sin_port = htons(8888);
    //server_in.sin_addr.s_addr = htonl(0);
    inet_pton(AF_INET,"192.168.136.101",&(client_in.sin_addr));

    //设置IP和端口
    server_in.sin_family = AF_INET;
    server_in.sin_port = htons(6666);
    //server_in.sin_addr.s_addr = htonl(0);
    inet_pton(AF_INET,"0.0.0.0",&(server_in.sin_addr));

    //socket
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",socketfd);

    //bind 绑定
    tmp_result = bind(socketfd,(struct sockaddr *)&client_in,sizeof(client_in));
    handle_error("bind",tmp_result);

    //connect客户端主动连接
    tmp_result = connect(socketfd,(struct sockaddr *)&server_in,sizeof(server_in));
    handle_error("connect",tmp_result);

    printf("连接成功,服务端的IP为%s,端口号为%d\n",inet_ntoa(server_in.sin_addr),ntohs(server_in.sin_port));

    pthread_t read_pid,write_pid;
    pthread_create(&read_pid,NULL,read_from_server,(void *)&socketfd);
    pthread_create(&write_pid,NULL,write_to_server,(void *)&socketfd);

    pthread_join(read_pid,NULL);
    pthread_join(write_pid,NULL);

    //客户端连接上了
    //printf("客户端的地址为%s,端口号为%d\n",inet_ntoa(server_in.sin_addr),ntohs(server_in.sin_port));
    close(socketfd);
    return 0;
}

7.2 服务端

cpp 复制代码
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>

#define handle_error(cmd, result)   \
        if (result < 0) {           \
            perror(cmd);            \
            return -1;              \
        }


// 使用recv接收客户端发送的数据  打印到控制台
void * read_from_client(void * arg){
    int client_fd = *(int *)arg;
    char * read_buf = NULL;
    read_buf = malloc(sizeof(char) * 1024);
    ssize_t count;
    if(!read_buf){
        perror("malloc");
        shutdown(client_fd,SHUT_RD);
        free(read_buf);
        return NULL;
    }

    while((count = recv(client_fd,read_buf,1024,0)) > 0){
        fputs(read_buf,stdout);
        memset(read_buf,0,sizeof(read_buf));
    }

    free(read_buf);
    shutdown(client_fd,SHUT_RD);
}
// 接收控制台输入的信息  写出去
void * write_to_client(void * arg){
    int client_fd = *(int *)arg;
    char * write_buf = NULL;
    write_buf = malloc(sizeof(char) * 1024);
    ssize_t count;
    if(!write_buf){
        perror("malloc");
        free(write_buf);
        shutdown(client_fd,SHUT_WR);
        return NULL;
    }

    while(fgets(write_buf,1024,stdin) != NULL){
        count = send(client_fd,write_buf,1024,0);
        if(count < 0){
            perror("send");
            break;
        }
    }

    free(write_buf);
    shutdown(client_fd,SHUT_WR);
}

int main(int argc, char const *argv[])
{
    int socketfd,tmp_result,client_fd;
    //定义两个结构体,一个表示服务端,一个是客户端的
    struct sockaddr_in server_in,client_in;
    //清空
    memset(&server_in,0,sizeof(server_in));
    memset(&client_in,0,sizeof(client_in));
    //设置IP和端口
    server_in.sin_family = AF_INET;
    server_in.sin_port = htons(6666);
    //server_in.sin_addr.s_addr = htonl(0);
    inet_pton(AF_INET,"0.0.0.0",&(server_in.sin_addr));
    //socket
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",socketfd);

    //bind 绑定
    tmp_result = bind(socketfd,(struct sockaddr *)&server_in,sizeof(server_in));
    handle_error("bind",tmp_result);

    //listen 监听
    tmp_result = listen(socketfd,128);
    handle_error("listen",tmp_result);

    //accept
    int client_len = sizeof(client_in);
    client_fd = accept(socketfd,(struct sockaddr *)&client_in,&client_len);
    handle_error("client",client_fd);
    //连接成功的时候
    printf("客户端的地址为%s,端口号为%d\n",inet_ntoa(client_in.sin_addr),ntohs(client_in.sin_port));
    pthread_t read_pid,write_pid;
    pthread_create(&read_pid,NULL,read_from_client,(void *)&client_fd);
    pthread_create(&write_pid,NULL,write_to_client,(void *)&client_fd);

    pthread_join(read_pid,NULL);
    pthread_join(write_pid,NULL);


    //客户端连接上了
    close(client_fd);
    close(socketfd);

    return 0;
}

8. 关于缓冲的补充说明

  • 进程IO的缓冲区
    • 进程的缓冲区机制是一种中间存储技术 ,用于在数据处理和在传输中暂存数据
    • 作用:减少直接读写磁盘的次数,提高性能。
    • 进程的 IO 缓冲区用于文件读写或通过标准输入输出与终端交互。分为输入缓冲区和输出缓冲区。
      • 输入缓冲区 存储 从文件或其他外部源接收的数据,直到进程准备好处理它
      • 输出缓冲区 暂存 要发送到外部设备或系统的数据,直到能够执行写操作。
  • 网络中的缓冲区
    • 网络编程中,尤其是使用 socket 通信时,内核会在内核空间维护网络缓冲区,它也分为输入输出缓冲区。与 IO 缓冲区不同,网络缓冲区的管理是由内核来完成的,用户无法干预。当然,用户可以在应用程序中通过在堆空间动态分配内存的方式构建额外的缓冲区。
    • 数据接收
      • 当网络数据到达网络接口时,数据首先被操作系统的网络驱动接收。操作系统会将这些数据存储在内核空间的接收缓冲区中。应用程序之后通过读取系统调用(如 recv 或 read),从内核空间的缓冲区中获取这些数据。
    • 数据发送
      • 当应用程序想要发送数据时,它通过写入系统调用(如 send 或 write)将数据传递给内核。内核将这些数据存放在内核空间的发送缓冲区中,然后根据网络协议的需要适时将数据发送到网络上。
  • 缓冲模式
    • 进程在执行 IO 操作时,可以有以下三种类型的缓冲模式。
    • 行缓冲(Line Buffering)
      • 在这种模式下,碰到换行符或缓冲区已满时刷写到目标文件。行缓冲通常用于标准输出(stdout),尤其是当标准输出关联到终端(控制台)时。这样做可以确保用户每输入一行命令就能看到响应的输出,而不必等到缓冲区完全填满。
    • 全缓冲(Fully Buffering)
      • 在全缓冲模式下,数据只会在缓冲区满时刷新,这意味着数据被存储在缓冲区直到缓冲区被填满,然后整个缓冲区的内容一次性刷写。通常向文件写数据的默认模式就是全缓冲,这样可以减少对底层系统资源的调用次数,提高数据处理效率。
    • 无缓冲(No Buffering)
      • 无缓冲模式意味着数据直接刷写,不经过缓冲区。标准错误(stderr)通常是无缓冲的,以确保错误信息能够立即输出。
    • 设置缓冲模式
      • 在 C 语言中,可以通过调用 setvbuf()函数来设置文件流的缓冲模式。
cpp 复制代码
#include <stdio.h> 
/** 
* @brief 设置文件流的缓冲模式 
* 
* @param stream 要设置的文件流,可以是输入缓冲流也可以是输出缓冲流 
* @param buf 指向缓冲区的指针,这个参数为 NULL 则自动分配缓冲区。 
* @param mode 缓冲区模式 
* _IOFBF:全缓冲,数据会存储在缓冲区中直到缓冲区满。 
* _IOLBF:行缓冲,数据会存储在缓冲区中直到碰到换行符或缓冲区满。 
* _IONBF:无缓冲,输出操作将直接从调用进程到目标设备,不经过缓冲区。 
* @param size 缓冲区大小,以字节为单位。size 为 0 时, 
* 如果 buf 为 NULL,则标准 C 库将为该文件流自动分配一个默认大小的缓冲区 
* 如果 buf 不为 NULL,将 size 设置为 0 不合逻辑,行为是未定义的 
* @return int 成功返回 0,失败返回非零值 
*/ 
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

#include <stdio.h> 
/** 
* fflush() 强制刷新缓冲区
* @param stream 待刷新的数据流,如果为 NULL,则刷新所有打开的输出流。 
* @return int 成功返回 0.失败返回 EOF,并设置 errno 
*/ 
int fflush(FILE *stream);

8.1 例子

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char const *argv[])
{
    FILE * fp = fopen("./testfile.txt","w");

    if(fp == NULL){
        perror("fopen");
        return 1;

    }
    //默认是全缓冲,正常退出会写入到文件
    //方法1,在摧毁前,改变缓冲模式,变为没有缓冲
    setvbuf(fp,NULL,_IONBF,0);
    fprintf(fp,"hello");
    
    
    // //方法二,手动刷新
    // fflush(fp);
    //方法三,在fprintf加上\n,并且需要改为行模式
    // setvbuf(fp,NULL,_IOLBF,0);
    // fprintf(fp,"hello\n");
    
    //但是摧毁前面的代码,就不会写入的

    char * args[] = {"/usr/bin/ls","-l",NULL};
    char * envs[] = {NULL};

    execve(args[0],args,envs);

    perror("execve");


    return 0;
}

9. 服务端基于多线程的支持多个连接的范例程序

9.1 服务端

cpp 复制代码
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>

#define handle_error(cmd, result)   \
        if (result < 0) {           \
            perror(cmd);            \
            return -1;              \
        }


// 读取客户端发送过来的数据  回复收到
void * read_from_client_then_write_OK(void * arg){
    int client_fd = *(int *)arg;
    char * read_buf = NULL;
    char * write_buf = NULL;

    read_buf = malloc(sizeof(char) * 1024);
    write_buf = malloc(sizeof(char) * 1024);

    ssize_t read_count,write_count;

    if(read_buf == NULL){
        perror("read_buf malloc");
        close(client_fd);
        return NULL;
    }

    if(write_buf == NULL){
        perror("write_buf malloc");
        close(client_fd);
        return NULL;
    }

    while(read_count = recv(client_fd,read_buf,1024,0)){
        if(read_count < 0){
            perror("recv:");
            return NULL;
        }

        if(read_count == 0){
            close(client_fd);
            free(read_buf);
            free(write_buf);
            return NULL;
        }
        printf("从客户端%d读取到的数据为%s\n",client_fd,read_buf);

        strcpy(write_buf,"收到");

        write_count = send(client_fd,write_buf,1024,0);
        if(write_count < 0){
            perror("send");
        }
    }
    // 当客户端输入ctrl + d时会退出循环
    free(read_buf);
    free(write_buf);
    close(client_fd);
}


int main(int argc, char const *argv[])
{
    int socketfd,tmp_result;
    //定义两个结构体,一个表示服务端,一个是客户端的
    struct sockaddr_in server_in,client_in;
    //清空
    memset(&server_in,0,sizeof(server_in));
    memset(&client_in,0,sizeof(client_in));
    //设置IP和端口
    server_in.sin_family = AF_INET;
    server_in.sin_port = htons(6666);
    //server_in.sin_addr.s_addr = htonl(0);
    inet_pton(AF_INET,"0.0.0.0",&(server_in.sin_addr));
    //socket
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",socketfd);

    //bind 绑定
    tmp_result = bind(socketfd,(struct sockaddr *)&server_in,sizeof(server_in));
    handle_error("bind",tmp_result);

    //listen 监听
    tmp_result = listen(socketfd,128);
    handle_error("listen",tmp_result);
    //accept
    int client_len = sizeof(client_in);
    while (1){
        int client_fd = accept(socketfd,(struct sockaddr *)&client_in,&client_len);
        handle_error("client",client_fd);
        //连接成功的时候
        // 和每一个客户端使用一个线程交互  把客户端发送的信息打印到控制台 回复收到
        printf("客户端的地址为%s,端口号为%d\n",inet_ntoa(client_in.sin_addr),ntohs(client_in.sin_port));
        pthread_t read_write_pid;
        if(pthread_create(&read_write_pid,NULL,read_from_client_then_write_OK,(void *)&client_fd)){
            perror("pthread_create");
            continue;
        }
        // 需要等待线程结束  但是不能挂起等待
        pthread_detach(read_write_pid);
    }
    
    close(socketfd);

    return 0;
}

9.2 客户端

cpp 复制代码
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>

#define handle_error(cmd, result)   \
        if (result < 0) {           \
            perror(cmd);            \
            return -1;              \
        }



void * read_from_server(void * arg){
    int client_fd = *(int *)arg;
    char * read_buf = NULL;
    read_buf = malloc(sizeof(char) * 1024);
    ssize_t count;
    if(!read_buf){
        perror("malloc");
        shutdown(client_fd,SHUT_RD);
        free(read_buf);
        return NULL;
    }

    while((count = recv(client_fd,read_buf,1024,0)) > 0){
        fputs(read_buf,stdout);
        fputs("\n",stdout);
        memset(read_buf,0,sizeof(read_buf));
    }

    free(read_buf);
    shutdown(client_fd,SHUT_RD);
}

void * write_to_server(void * arg){
    int client_fd = *(int *)arg;
    char * write_buf = NULL;
    write_buf = malloc(sizeof(char) * 1024);
    ssize_t count;
    if(!write_buf){
        perror("malloc");
        free(write_buf);
        shutdown(client_fd,SHUT_WR);
        return NULL;
    }

    while(fgets(write_buf,1024,stdin) != NULL){
        count = send(client_fd,write_buf,1024,0);
        if(count < 0){
            perror("send");
            break;
        }
    }

    free(write_buf);
    shutdown(client_fd,SHUT_WR);
}

int main(int argc, char const *argv[])
{
    int socketfd,tmp_result;
    //定义两个结构体,一个表示服务端,一个是客户端的
    struct sockaddr_in server_in,client_in;
    //清空
    memset(&server_in,0,sizeof(server_in));
    memset(&client_in,0,sizeof(client_in));
    //设置客户端的IP
    client_in.sin_family = AF_INET;
    client_in.sin_port = htons(8888);
    //server_in.sin_addr.s_addr = htonl(0);
    inet_pton(AF_INET,"192.168.136.101",&(client_in.sin_addr));

    //设置IP和端口
    server_in.sin_family = AF_INET;
    server_in.sin_port = htons(6666);
    //server_in.sin_addr.s_addr = htonl(0);
    inet_pton(AF_INET,"0.0.0.0",&(server_in.sin_addr));

    //socket
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",socketfd);

    //bind 绑定,自动分配
    // 客户端可以不绑定  系统会自动分配一个空闲的端口号给客户端
    // tmp_result = bind(socketfd,(struct sockaddr *)&client_in,sizeof(client_in));
    // handle_error("bind",tmp_result);

    //connect客户端主动连接
    tmp_result = connect(socketfd,(struct sockaddr *)&server_in,sizeof(server_in));
    handle_error("connect",tmp_result);

    printf("连接成功,服务端的IP为%s,端口号为%d\n",inet_ntoa(server_in.sin_addr),ntohs(server_in.sin_port));

    pthread_t read_pid,write_pid;
    pthread_create(&read_pid,NULL,read_from_server,(void *)&socketfd);
    pthread_create(&write_pid,NULL,write_to_server,(void *)&socketfd);

    pthread_join(read_pid,NULL);
    pthread_join(write_pid,NULL);

    //客户端连接上了
    //printf("客户端的地址为%s,端口号为%d\n",inet_ntoa(server_in.sin_addr),ntohs(server_in.sin_port));
    close(socketfd);
    return 0;

}

10. 服务端基于多进程的支持多个连接的范例程序

10.1 服务端

cpp 复制代码
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>

#define handle_error(cmd, result)   \
        if (result < 0) {           \
            perror(cmd);            \
            return -1;              \
        }


// 读取客户端发送过来的数据  回复收到
void * read_from_client_then_write_OK(void * arg){
    int client_fd = *(int *)arg;
    char * read_buf = NULL;
    char * write_buf = NULL;

    read_buf = malloc(sizeof(char) * 1024);
    write_buf = malloc(sizeof(char) * 1024);

    ssize_t read_count,write_count;

    if(read_buf == NULL){
        perror("read_buf malloc");
        close(client_fd);
        return NULL;
    }

    if(write_buf == NULL){
        perror("write_buf malloc");
        close(client_fd);
        return NULL;
    }

    while(read_count = recv(client_fd,read_buf,1024,0)){
        if(read_count < 0){
            perror("recv:");
            return NULL;
        }

        if(read_count == 0){
            close(client_fd);
            free(read_buf);
            free(write_buf);
            return NULL;
        }
        printf("从客户端%d读取到的数据为%s\n",client_fd,read_buf);

        strcpy(write_buf,"收到");

        write_count = send(client_fd,write_buf,1024,0);
        if(write_count < 0){
            perror("send");
        }
    }
    // 当客户端输入ctrl + d时会退出循环
    free(read_buf);
    free(write_buf);
    close(client_fd);
}


void zombie_dealer(int sig) {
    pid_t pid;
    int status;
    // 一个 SIGCHLD 可能对应多个子进程的退出
    // 使用 while 循环回收所有退出的子进程,避免僵尸进程的出现
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        if (WIFEXITED(status)) {
            printf("子进程: %d 以 %d 状态正常退出,已被回收\n", pid,WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("子进程: %d 被 %d 信号杀死,已被回收\n", pid,WTERMSIG(status));
        } else {
            printf("子进程: %d 因其它原因退出,已被回收\n", pid);
        }
    }
}

int main(int argc, char const *argv[])
{
    //注册信号
    signal(SIGCHLD,zombie_dealer);
    int socketfd,tmp_result;
    //定义两个结构体,一个表示服务端,一个是客户端的
    struct sockaddr_in server_in,client_in;
    //清空
    memset(&server_in,0,sizeof(server_in));
    memset(&client_in,0,sizeof(client_in));
    //设置IP和端口
    server_in.sin_family = AF_INET;
    server_in.sin_port = htons(6666);
    //server_in.sin_addr.s_addr = htonl(0);
    inet_pton(AF_INET,"0.0.0.0",&(server_in.sin_addr));
    //socket
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",socketfd);

    //bind 绑定
    tmp_result = bind(socketfd,(struct sockaddr *)&server_in,sizeof(server_in));
    handle_error("bind",tmp_result);

    //listen 监听
    tmp_result = listen(socketfd,128);
    handle_error("listen",tmp_result);
    //accept
    int client_len = sizeof(client_in);
    while (1){
        int client_fd = accept(socketfd,(struct sockaddr *)&client_in,&client_len);
        handle_error("client",client_fd);
        //连接成功的时候
        int pid = fork();
        if(pid < 0){
            perror("fork");
        }else if(pid == 0){
            close(socketfd);
            printf("客户端的地址为%s,端口号为%d\n",inet_ntoa(client_in.sin_addr),ntohs(client_in.sin_port));
            read_from_client_then_write_OK((void *)&client_fd);
            close(client_fd);
            exit(EXIT_SUCCESS);
        }else {
            close(client_fd);
        }
        
    }


    close(socketfd);

    return 0;

}

10.2 客户端

cpp 复制代码
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>

#define handle_error(cmd, result)   \
        if (result < 0) {           \
            perror(cmd);            \
            return -1;              \
        }



void * read_from_server(void * arg){
    int client_fd = *(int *)arg;
    char * read_buf = NULL;
    read_buf = malloc(sizeof(char) * 1024);
    ssize_t count;
    if(!read_buf){
        perror("malloc");
        shutdown(client_fd,SHUT_RD);
        free(read_buf);
        return NULL;
    }

    while((count = recv(client_fd,read_buf,1024,0)) > 0){
        fputs(read_buf,stdout);
        fputs("\n",stdout);
        memset(read_buf,0,sizeof(read_buf));
    }

    free(read_buf);
    shutdown(client_fd,SHUT_RD);
}

void * write_to_server(void * arg){
    int client_fd = *(int *)arg;
    char * write_buf = NULL;
    write_buf = malloc(sizeof(char) * 1024);
    ssize_t count;
    if(!write_buf){
        perror("malloc");
        free(write_buf);
        shutdown(client_fd,SHUT_WR);
        return NULL;
    }

    while(fgets(write_buf,1024,stdin) != NULL){
        count = send(client_fd,write_buf,1024,0);
        if(count < 0){
            perror("send");
            break;
        }
    }

    free(write_buf);
    shutdown(client_fd,SHUT_WR);
}

int main(int argc, char const *argv[])
{
    int socketfd,tmp_result;
    //定义两个结构体,一个表示服务端,一个是客户端的
    struct sockaddr_in server_in,client_in;
    //清空
    memset(&server_in,0,sizeof(server_in));
    memset(&client_in,0,sizeof(client_in));
    //设置客户端的IP
    client_in.sin_family = AF_INET;
    client_in.sin_port = htons(8888);
    //server_in.sin_addr.s_addr = htonl(0);
    inet_pton(AF_INET,"192.168.136.101",&(client_in.sin_addr));

    //设置IP和端口
    server_in.sin_family = AF_INET;
    server_in.sin_port = htons(6666);
    //server_in.sin_addr.s_addr = htonl(0);
    inet_pton(AF_INET,"0.0.0.0",&(server_in.sin_addr));

    //socket
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",socketfd);

    //bind 绑定,自动分配
    // 客户端可以不绑定  系统会自动分配一个空闲的端口号给客户端
    // tmp_result = bind(socketfd,(struct sockaddr *)&client_in,sizeof(client_in));
    // handle_error("bind",tmp_result);

    //connect客户端主动连接
    tmp_result = connect(socketfd,(struct sockaddr *)&server_in,sizeof(server_in));
    handle_error("connect",tmp_result);

    printf("连接成功,服务端的IP为%s,端口号为%d\n",inet_ntoa(server_in.sin_addr),ntohs(server_in.sin_port));

    pthread_t read_pid,write_pid;
    pthread_create(&read_pid,NULL,read_from_server,(void *)&socketfd);
    pthread_create(&write_pid,NULL,write_to_server,(void *)&socketfd);

    pthread_join(read_pid,NULL);
    pthread_join(write_pid,NULL);

    //客户端连接上了
    //printf("客户端的地址为%s,端口号为%d\n",inet_ntoa(server_in.sin_addr),ntohs(server_in.sin_port));
    close(socketfd);
    return 0;

}
相关推荐
苏宸啊2 小时前
OS环境变量
linux·c++
浩瀚之水_csdn2 小时前
avcodec_parameters_copy详解
linux·人工智能·ffmpeg
头发那是一根不剩了2 小时前
Linux 常用服务器命令
linux·运维·服务器
锅包一切2 小时前
二、几种安装类型
linux·运维·后端·操作系统
vortex52 小时前
详解Linux磁盘相关命令:从基础查看到底层运维
linux·运维
敲代码的哈吉蜂2 小时前
Haproxy
linux·运维·服务器
敲代码的哈吉蜂2 小时前
haproxy的算法——混合算法
linux·运维·服务器·算法
市安2 小时前
构建HTTPS服务镜像
linux·运维·服务器
8125035332 小时前
第2篇:为什么要有分层?从工程实践到架构设计
linux·网络·网络协议·计算机网络