跳到主要内容 Linux TCP 协议详解与 Socket 编程实战 | 极客日志
C
Linux TCP 协议详解与 Socket 编程实战 TCP 协议的基本概念、特征及报文格式,详细解析了三次握手与四次挥手过程。内容涵盖 TCP 状态机、常用 Socket 函数(socket、bind、listen、accept 等)、字节序转换以及缓冲区机制。最后提供了基于多线程和多进程的多连接服务端 C 语言示例代码,帮助读者理解 Linux 网络编程的核心实现。
星星泡饭 发布于 2026/3/24 更新于 2026/4/18 18K 浏览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. 三次握手
解释
用小写的 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)+1 ,seq 随机,标记为 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 #include <sys/socket.h>
#include <sys/types.h>
int socket (int domain, int type, int protocol) ;
6.2 bind:功能:用于绑定 IP 地址和端口号到 socketfd
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
int listen (int sockfd, int backlog) ;
6.4 accept
int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;
6.5 connect
int connect (int sockfd, const struct sockaddr *addr, socklen_t addrlen) ;
6.6 send
ssize_t send (int sockfd, const void *buf, size_t len, int flags) ;
6.7 recv
ssize_t recv (int sockfd, void *buf, size_t len, int flags) ;
6.8 shutdown
int shutdown (int sockfd, int how) ;
6.9 close #include <unistd.h>
int close (int __fd) ;
6.10 网络字节序和主机字节序转换
6.10.1 两种字节序
在网络编程中,特别是在跨平台和网络通信时,字节序(Byte Order)是非常重要的概念。字节序指的是多字节数据在内存中的存储顺序。主要有两种字节序 :
大端字节序(Big-Endian) :高位字节存储在内存的低地址处 ,低位字节存储在高地址处 。这种字节序遵循自然数字的书写习惯,也被称为网络字节序 (Network ByteOrder)或网络标准字节序,因为它在网络通信中被广泛采用,如 IP 协议就要求使用大端字节序。
小端字节序(Little-Endian) :低位字节存储在内存的低地址处,高位字节存储在高地址处。称为主机字节序(Host Byte Order)。
6.10.2 函数 #include <arpa/inet.h>
uint32_t htonl (uint32_t hostlong) ;
uint16_t htons (uint16_t hostshort) ;
uint32_t ntohl (uint32_t netlong) ;
uint16_t ntohs (uint16_t netshort) ;
6.10.3 函数 #include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton (const char *cp, struct in_addr *inp) ;
in_addr_t inet_addr (const char *cp) ;
in_addr_t inet_network (const char *cp) ;
int inet_pton (int af, const char *src, void *dst) ;
char *inet_ntoa (struct in_addr in) ;
struct in_addr inet_makeaddr (in_addr_t net, in_addr_t host) ;
in_addr_t inet_lnaof (struct in_addr in) ;
in_addr_t inet_netof (struct in_addr in) ;
6.10.4 例子 #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 例子 #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("192.168.6.101" ,&server_addr_in);
printf ("经过 inet_aton 转化之后的 IP 地址为 0x%x\n" ,server_addr_in.s_addr);
server_in_addr_t = inet_addr("192.168.6.101" );
printf ("经过 inet_addr 转化之后的 IP 地址为 0x%x\n" ,server_in_addr_t );
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);
printf ("经过 inet_pton 转化之后的 IP 地址为%s\n" ,inet_ntoa(server_addr_in));
printf ("inet_lnaof:0x%X\n" ,inet_lnaof(server_addr_in));
printf ("inet_netof:0x%X\n" ,inet_netof(server_addr_in));
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 客户端 #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));
client_in.sin_family = AF_INET;
client_in.sin_port = htons(8888 );
inet_pton(AF_INET,"192.168.136.101" ,&(client_in.sin_addr));
server_in.sin_family = AF_INET;
server_in.sin_port = htons(6666 );
inet_pton(AF_INET,"0.0.0.0" ,&(server_in.sin_addr));
socketfd = socket(AF_INET,SOCK_STREAM,0 );
handle_error("socket" ,socketfd);
tmp_result = bind(socketfd,(struct sockaddr *)&client_in,sizeof (client_in));
handle_error("bind" ,tmp_result);
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 );
close(socketfd);
return 0 ;
}
7.2 服务端 #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 (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));
server_in.sin_family = AF_INET;
server_in.sin_port = htons(6666 );
inet_pton(AF_INET,"0.0.0.0" ,&(server_in.sin_addr));
socketfd = socket(AF_INET,SOCK_STREAM,0 );
handle_error("socket" ,socketfd);
tmp_result = bind(socketfd,(struct sockaddr *)&server_in,sizeof (server_in));
handle_error("bind" ,tmp_result);
tmp_result = listen(socketfd,128 );
handle_error("listen" ,tmp_result);
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() 函数来设置文件流的缓冲模式。
#include <stdio.h>
int setvbuf (FILE *stream, char *buf, int mode, size_t size) ;
#include <stdio.h>
int fflush (FILE *stream) ;
8.1 例子 #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 ; }
setvbuf(fp,NULL ,_IONBF,0 );
fprintf (fp,"hello" );
char * args[] = {"/usr/bin/ls" ,"-l" ,NULL };
char * envs[] = {NULL };
execve(args[0 ],args,envs);
perror("execve" );
return 0 ;
}
9. 服务端基于多线程的支持多个连接的范例程序
9.1 服务端 #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" ); }
}
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));
server_in.sin_family = AF_INET;
server_in.sin_port = htons(6666 );
inet_pton(AF_INET,"0.0.0.0" ,&(server_in.sin_addr));
socketfd = socket(AF_INET,SOCK_STREAM,0 );
handle_error("socket" ,socketfd);
tmp_result = bind(socketfd,(struct sockaddr *)&server_in,sizeof (server_in));
handle_error("bind" ,tmp_result);
tmp_result = listen(socketfd,128 );
handle_error("listen" ,tmp_result);
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 客户端 #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));
client_in.sin_family = AF_INET;
client_in.sin_port = htons(8888 );
inet_pton(AF_INET,"192.168.136.101" ,&(client_in.sin_addr));
server_in.sin_family = AF_INET;
server_in.sin_port = htons(6666 );
inet_pton(AF_INET,"0.0.0.0" ,&(server_in.sin_addr));
socketfd = socket(AF_INET,SOCK_STREAM,0 );
handle_error("socket" ,socketfd);
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 );
close(socketfd);
return 0 ;
}
10. 服务端基于多进程的支持多个连接的范例程序
10.1 服务端 #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" ); }
}
free (read_buf);
free (write_buf);
close(client_fd);
}
void zombie_dealer (int sig) {
pid_t pid;
int status;
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));
server_in.sin_family = AF_INET;
server_in.sin_port = htons(6666 );
inet_pton(AF_INET,"0.0.0.0" ,&(server_in.sin_addr));
socketfd = socket(AF_INET,SOCK_STREAM,0 );
handle_error("socket" ,socketfd);
tmp_result = bind(socketfd,(struct sockaddr *)&server_in,sizeof (server_in));
handle_error("bind" ,tmp_result);
tmp_result = listen(socketfd,128 );
handle_error("listen" ,tmp_result);
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 客户端 #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));
client_in.sin_family = AF_INET;
client_in.sin_port = htons(8888 );
inet_pton(AF_INET,"192.168.136.101" ,&(client_in.sin_addr));
server_in.sin_family = AF_INET;
server_in.sin_port = htons(6666 );
inet_pton(AF_INET,"0.0.0.0" ,&(server_in.sin_addr));
socketfd = socket(AF_INET,SOCK_STREAM,0 );
handle_error("socket" ,socketfd);
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 );
close(socketfd);
return 0 ;
}
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online