目录
4.4 简单的Sever和Client 的实现 基于TCP协议
4.6 基于select 实现了一个可以接受客户端断线重连的TCP服务器
4.7 基于select IO 多路复用实现的一个TCP简单的聊天室
4.9 简单的Sever和Client 的实现 基于UDP协议
4.12 基于epoll实现了一个可以接受客户端断线重连的TCP服务器
4.13 基于epoll IO 多路复用实现的一个TCP简单的聊天室
一、网络编程基础
1.1 TCP/IP 协议栈
应用层:HTTP、FTP、SMTP 等协议
传输层:TCP(可靠连接)、UDP(无连接)
网络层:IP 协议,负责路由与寻址
链路层:物理网卡与数据帧传输
1.2 Socket 编程流程
// 服务端典型流程
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
bind(sockfd, &addr, sizeof(addr)); // 绑定地址
listen(sockfd, 5); // 监听连接
int netfd = accept(sockfd, &client_addr, ...); // 接受连接
read(connfd, buffer, sizeof(buffer)); // 读取数据
write(connfd, response, ...); // 发送响应
close(connfd); // 关闭连接
二、高性能 I/O 多路复用
2.1 select/poll/epoll 对比
2.2 epoll 核心机制
// 创建epoll实例
int epfd = epoll_create(1024);
// 添加事件
struct epoll_event event;
event.events = EPOLLIN | EPOLLET; // 边缘触发模式
event.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
// 等待事件
struct epoll_event events[1024];
int nfds = epoll_wait(epfd, events, 1024, -1);
三、边缘触发(ET)与水平触发(LT)实战
3.1 核心区别
3.2 ET 模式代码示例
// 非阻塞读取所有数据
ssize_t read_all(int fd, char *buf, size_t len) {
ssize_t total = 0;
while (total < len) {
ssize_t ret = recv(fd, buf + total, len - total, 0);
if (ret == -1) {
if (errno == EAGAIN) break; // 数据读完
return -1;
} else if (ret == 0) {
return total; // 连接关闭
}
total += ret;
}
return total;
}
// 事件处理
if (events[i].events & EPOLLIN) {
int n = read_all(fd, buffer, sizeof(buffer));
if (n > 0) {
// 处理业务逻辑
} else {
// 关闭连接
}
}
四、实战案例和函数介绍补充
4.1 htons函数
#if 1
#include "func.h"
//大小端转换
int main(int argc, char *argv[]) {
short port = 0x1234;//在内存地址是 小端头
short big_endian_port= htons(port);
printf("big_endian_port=%x\n",big_endian_port);
return 0;
}
#endif
普通短整型在内存地址是 属于小端头 我们如果要把他设置在服务端中默认要设置成大端头
htons函数可以使用于短整型中,它的作用是将一个 16 位的无符号整数从主机字节序转换为网络字节序
4.2 inet_addr函数
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port= htons(1234);
char ip[]="192.168.1.104";
// inet_aton(ip,&addr.sin_addr);
addr.sin_addr.s_addr= inet_addr(ip);
printf("ip=%x\n",addr.sin_addr.s_addr);
return 0;
}
#endif
inet_addr
函数是在网络编程中用于将点分十进制的 IPv4 地址转换为 32 位的网络字节序整数的函数。在网络编程里,IP 地址通常以点分十进制的字符串形式呈现,例如 192.168.1.1
,但在计算机内部处理和网络传输时,需要将其转换为 32 位的二进制整数。inet_addr
函数的作用就是完成这种转换。
4.3 gethostbyname函数
#if 1
#include "func.h"
//根据域名获取ip地址
int main(int argc, char *argv[]) {
if (argc<2) return -1;
struct hostent * entry = gethostbyname(argv[1]);
if (entry==NULL){
herror("gethostbyname");
return -2;
}
printf(" office name:%s\n",entry->h_name);
for (int i = 0; entry->h_aliases[i]!=NULL; ++i) {
printf("\talias:%s\n",entry->h_aliases[i]);
}
for (int i = 0; entry->h_addr_list[i]!=NULL; ++i) {
char ip[1024]={};
inet_ntop(entry->h_addrtype,entry->h_addr_list[i],ip,1024);
printf("\tip:%s\n",ip);
}
return 0;
}
#endif
gethostbyname
函数是 C 语言中用于进行域名解析的一个重要函数,它可以将域名转换为对应的 IP 地址。在网络通信中,人们通常使用域名(如 www.example.com
)来访问网络资源,但计算机网络通信实际上是基于 IP 地址进行的。gethostbyname
函数的作用就是将人类可读的域名转换为计算机可以理解和使用的 IP 地址,从而实现网络通信
4.4 简单的Sever和Client 的实现 基于TCP协议
Sever
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
//192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2]));
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int ret = bind(sockfd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr));
if (ret==-1) perror("bind");
listen(sockfd,50);
//sockfd是监听socket文件描述符
struct sockaddr_in ClientAddr;
socklen_t socklen =sizeof (ClientAddr);
//socklen 必须赋初值
int netfd= accept(sockfd,(struct sockaddr*)&ClientAddr,&socklen);
//netfd是通信socket的文件描述符
printf("Client ip =%s,Client port =%d\n",
inet_ntoa(ClientAddr.sin_addr),
ntohs(ClientAddr.sin_port));
char buffer[4096]={};
ssize_t sret= recv(netfd,buffer,sizeof (buffer),0);
printf("buffer=%s\n",buffer);
send(netfd,"fuckyou",7,0);
while (1){
sleep(1);
}
return 0;
}
#endif
第一步:先调用socket指定地址族为 IPv4,表明套接字使用 IPv4 地址通信,定义套接字类型为流式套接字,对应 TCP 协议。TCP 具备面向连接、可靠传输(保证数据顺序、错误重传)的特性,适用于网页访问、邮件发送等场景。
第二步:定义 IPv4 地址结构体 ServerAddr
,用于存储服务端地址信息,指定地址族为 IPv4,设置该服务器的端口号和IP地址
第三步:调用listen函数让套接字 sockfd
进入监听状态,最多允许 50 个客户端连接排队等待,
此处的sockfd只能用于监听
第四步:通过bind函数把服务端的端口和IP地址和套接字绑定在一起
第五步:通过 accpet函数把 生成拥有发送缓冲区和写缓冲区的netfd文件描述符
Client
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
//192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2]));
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int ret = connect(sockfd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr));
if (ret==-1) perror("bind");
send(sockfd,"hello",5,0);
char buffer[4096]={};
ssize_t sret=recv(sockfd,buffer,sizeof (buffer),0);
printf("buffer=%s\n",buffer);
while (1){
sleep(1);
}
return 0;
}
#endif
第一步:先调用socket指定地址族为 IPv4,表明套接字使用 IPv4 地址通信,定义套接字类型为流式套接字,对应 TCP 协议。TCP 具备面向连接、可靠传输(保证数据顺序、错误重传)的特性,适用于网页访问、邮件发送等场景。
第二步:定义 IPv4 地址结构体 ServerAddr
,用于存储服务端地址信息,指定地址族为 IPv4,设置该服务器的端口号和IP地址
第三步:调用listen函数让套接字 sockfd
进入监听状态,最多允许 50 个客户端连接排队等待。
第四步:通过connect函数把服务端的端口和IP地址和套接字绑定在一起
经过TCP的三次握手成功建立连接 开始服务端和客户端开始通信
4.5 基于select实现的TCP服务器通信
Server
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
//服务端
//192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2]));
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int reuse =1; //允许重用
int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
if (ret==-1) perror("setsockopt");
ret = bind(sockfd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr));
if (ret==-1) perror("bind");
listen(sockfd,50);
//sockfd是监听socket文件描述符
struct sockaddr_in ClientAddr;
socklen_t socklen =sizeof (ClientAddr);
//socklen 必须赋初值
int netfd= accept(sockfd,NULL,NULL);
fd_set rdset;
char buffer[4096]={};
while (1){
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO,&rdset);
FD_SET(netfd,&rdset);
select(netfd+1,&rdset,NULL,NULL,NULL);
if (FD_ISSET(STDIN_FILENO,&rdset)){
bzero(buffer,sizeof (buffer));
read(STDIN_FILENO,buffer,sizeof (buffer));
send(netfd,buffer,sizeof (buffer),0);
}
if (FD_ISSET(netfd,&rdset)){
bzero(buffer,sizeof (buffer));
ssize_t sret =recv(netfd,buffer,sizeof (buffer),0);
if (sret==0) break;
printf("buffer=%s\n",buffer);
}
}
return 0;
}
#endif
Client
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
//客户端
//192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2]));
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int ret = connect(sockfd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr));
if (ret==-1) perror("bind");
fd_set rdset;
char buffer[4096]={};
while (1){
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO,&rdset);
FD_SET(sockfd,&rdset);
select(sockfd+1,&rdset,NULL,NULL,NULL);
if (FD_ISSET(STDIN_FILENO,&rdset)){
bzero(buffer,sizeof (buffer));
read(STDIN_FILENO,buffer,sizeof (buffer));
send(sockfd,buffer,sizeof (buffer),0);
}
if (FD_ISSET(sockfd,&rdset)){
bzero(buffer,sizeof (buffer));
ssize_t sret =recv(sockfd,buffer,sizeof (buffer),0);
if (sret==0) break;
printf("buffer=%s\n",buffer);
}
}
return 0;
}
#endif
4.6 基于select 实现了一个可以接受客户端断线重连的TCP服务器
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
//服务端
//192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2]));
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int reuse =1; //允许重用
int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
if (ret==-1) perror("setsockopt");
ret = bind(sockfd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr));
if (ret==-1) perror("bind");
listen(sockfd,50);
//sockfd是监听socket文件描述符
fd_set monitorSet;//监听集合
fd_set readySet;//就绪集合
FD_ZERO(&monitorSet);
FD_SET(sockfd,&monitorSet);
char buffer[4096]={};
int netfd=-1;//netfd=-1 代表阿强不存在
while (1){
memcpy(&readySet,&monitorSet,sizeof(monitorSet));
select(20,&readySet,NULL,NULL,NULL);
if (FD_ISSET(sockfd,&readySet)){
netfd = accept(sockfd,NULL,NULL);
printf("aqiang is connected\n");
FD_CLR(sockfd,&monitorSet);
FD_SET(netfd,&monitorSet);
FD_SET(STDIN_FILENO,&monitorSet);
}
if (FD_ISSET(netfd,&readySet)){
bzero(buffer,sizeof (buffer));
ssize_t sret = recv(netfd,buffer,sizeof (buffer),0);
if (sret==0){
FD_CLR(netfd,&monitorSet);
FD_CLR(STDIN_FILENO,&monitorSet);
FD_SET(sockfd,&monitorSet);
close(netfd);
netfd=-1;
printf("wo hui hao hao de\n");
continue;
}
printf("buffer=%s\n",buffer);
}
if (FD_ISSET(STDIN_FILENO,&readySet)){
bzero(buffer,sizeof (buffer));
ssize_t sret = read(STDIN_FILENO,buffer,sizeof (buffer));
if (sret==0){
FD_CLR(netfd,&monitorSet);
FD_CLR(STDIN_FILENO,&monitorSet);
FD_SET(sockfd,&monitorSet);
close(netfd);
netfd=-1;
printf("ni shi yi ge hao ren\n");
continue;
}
send(netfd,buffer,sizeof (buffer),0);
}
}
}
#endif
4.7 基于select IO 多路复用实现的一个TCP简单的聊天室
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAdd ;
ServerAdd.sin_family=AF_INET;
ServerAdd.sin_addr.s_addr= inet_addr(argv[1]);
ServerAdd.sin_port= htons(atoi(argv[2]));
int reuse =1; //允许重用
int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
if (ret==-1) perror("setsockopt");
ret=bind(sockfd,(struct sockaddr*)&ServerAdd,sizeof(ServerAdd));
if (ret==-1) perror("bind");
listen(sockfd,50);
fd_set monitorSet;
fd_set readySet;
FD_ZERO(&monitorSet);
FD_SET(sockfd,&monitorSet);
char buffer[4096]={};
//设计一个数据结构 存储所有客户端的netfd
int netfd[1024];
for (int i = 0; i < 1024; ++i) {
netfd[i]=-1;
}
int curidx = 0;//下一次加入的netfd的下标
//用于查找的哈希表 netfd-->idx
int fdToidx[1024];
for (int i = 0; i < 1024; ++i) {
fdToidx[i]=-1;
}
while (1){
memcpy(&readySet,&monitorSet, sizeof (fd_set));
select(1024,&readySet,NULL,NULL,NULL);
if (FD_ISSET(sockfd,&readySet)){
netfd[curidx]= accept(sockfd,NULL,NULL);
printf("i = %d,netfd = %d\n",curidx,netfd[curidx]);
fdToidx[netfd[curidx]]=curidx;
FD_SET(netfd[curidx],&monitorSet);
++curidx;
}
for (int i = 0; i < curidx; ++i) {
if (netfd[i]!=-1 && FD_ISSET(netfd[i],&readySet)){
bzero(buffer,4096);
int sret = recv(netfd[i],buffer,sizeof(buffer),0);
if (sret==0){
fdToidx[netfd[i]]=-1;
FD_CLR(netfd[i],&monitorSet);
close(netfd[i]);
netfd[i]=-1;
continue;
}
for (int j = 0; j < curidx; ++j) {
if (netfd[j]!=-1 && i!=j){
send(netfd[j],buffer,strlen(buffer),0);
}
}
}
}
}
return 0;
}
#endif
4.8 实现简单的 HTTP 客户端功能
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
const char query[]="GET / HTTP/1.0\r\n"
"Host:www.baidu.com\r\n"
"\r\n";
const char hostname[]="www.baidu.com";
struct hostent*enrty= gethostbyname(hostname);
if(enrty==NULL){
herror("gethostbyname");
return -1;
}
struct sockaddr_in addr;
addr.sin_port= htons(80);
addr.sin_family=AF_INET;
memcpy(&addr.sin_addr.s_addr,enrty->h_addr_list[0],sizeof (addr.sin_addr));
int sockfd = socket(AF_INET,SOCK_STREAM,0);
connect(sockfd,(struct sockaddr*)&addr,sizeof (addr));
send(sockfd,query,sizeof (query),0);
char buffer[4096]={};
while (1) {
bzero(buffer,4096);
ssize_t sret = recv(sockfd, buffer, sizeof(buffer), 0);
if (sret == 0) break;
printf("sret = %ld\n,buffer=%s\n", sret, buffer);
}
return 0;
}
#endif
4.9 简单的Sever和Client 的实现 基于UDP协议
Server
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
// 192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family= AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2])) ;
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int ret = bind(sockfd,(struct sockaddr*)&ServerAddr,sizeof (ServerAddr));
struct sockaddr_in ClientAddr;
socklen_t socklen=sizeof (ClientAddr);
//服务端接收客户端的消息
char buffer[4096]={0};
recvfrom(sockfd,buffer,sizeof (buffer),0,(struct sockaddr*)&ClientAddr,&socklen);
printf("buffer=%s,ip=%s,port=%d\n",buffer, inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));
sendto(sockfd,"world",5,0,(struct sockaddr*)&ClientAddr,socklen);
return 0;
}
#endif
Client
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
// 192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family= AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2])) ;
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
//第一次必须是客户端向服务端发消息
sendto(sockfd,"hello",5,0,(struct sockaddr*)&ServerAddr,sizeof (ServerAddr));
char buffer[4096]={0};
recvfrom(sockfd,buffer,sizeof (buffer),0,NULL,NULL);
printf("buffer=%s\n",buffer);
return 0;
}
#endif
4.10 基于select实现的UDP服务器通信
Server
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
// 192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family= AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2])) ;
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int ret = bind(sockfd,(struct sockaddr*)&ServerAddr,sizeof (ServerAddr));
struct sockaddr_in ClientAddr;
socklen_t socklen=sizeof (ClientAddr);
//服务端接收客户端的消息
char buffer[4096]={0};
recvfrom(sockfd,buffer,sizeof (buffer),0,(struct sockaddr*)&ClientAddr,&socklen);
printf("one buffer =%s\n",buffer);
fd_set rdset;
while (1){
FD_ZERO(&rdset);
FD_SET(sockfd,&rdset);
FD_SET(STDIN_FILENO,&rdset);
select(sockfd+1,&rdset,NULL,NULL,NULL);
if (FD_ISSET(STDIN_FILENO,&rdset)){
bzero(buffer,sizeof (buffer));
read(STDIN_FILENO,buffer,sizeof (buffer));
sendto(sockfd,buffer,sizeof (buffer),0,(struct sockaddr*)&ClientAddr,socklen);
}
if (FD_ISSET(sockfd,&rdset)){
bzero(buffer,sizeof (buffer));
recvfrom(sockfd,buffer,sizeof (buffer),0,NULL,NULL);
printf("buffer=%s\n",buffer);
}
}
return 0;
}
#endif
Client
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
// 192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family= AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2])) ;
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
//第一次必须是客户端向服务端发消息
sendto(sockfd,"hello",5,0,(struct sockaddr*)&ServerAddr,sizeof (ServerAddr));
char buffer[4096]={0};
fd_set rdset;
while (1){
FD_ZERO(&rdset);
FD_SET(sockfd,&rdset);
FD_SET(STDIN_FILENO,&rdset);
select(sockfd+1,&rdset,NULL,NULL,NULL);
if (FD_ISSET(STDIN_FILENO,&rdset)){
bzero(buffer,sizeof (buffer));
read(STDIN_FILENO,buffer,sizeof (buffer));
sendto(sockfd,buffer,sizeof (buffer),0,(struct sockaddr*)&ServerAddr,sizeof (ServerAddr));
}
if (FD_ISSET(sockfd,&rdset)){
bzero(buffer,sizeof (buffer));
recvfrom(sockfd,buffer,sizeof (buffer),0,NULL,NULL);
printf("buffer=%s\n",buffer);
}
}
return 0;
}
#endif
4.11 基于epoll实现的TCP服务器通信
Server
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
//服务端
//192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2]));
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int reuse =1; //允许重用
int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
if (ret==-1) perror("setsockopt");
ret = bind(sockfd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr));
if (ret==-1) perror("bind");
listen(sockfd,50);
//sockfd是监听socket文件描述符
struct sockaddr_in ClientAddr;
socklen_t socklen =sizeof (ClientAddr);
//socklen 必须赋初值
int netfd= accept(sockfd,(struct sockaddr*)&ClientAddr,&socklen);
printf("Client is connected \n");
printf("ip = %s,port = %d\n", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));
int epfd = epoll_create(1);//创建epoll文件对象
//设置监听 因为epoll文件对象的监听集合和就绪集合在内核态是分开的 所以可以直接写在while循环外面
//设置属性
struct epoll_event events;//什么情况就绪,就绪了怎么办
events.events=EPOLLIN;//读就绪
events.data.fd=STDIN_FILENO;//就绪之后放入STDIN_FILENO
epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&events);
events.events=EPOLLIN;
events.data.fd=netfd;//就绪之后放入netfd
epoll_ctl(epfd,EPOLL_CTL_ADD,netfd,&events);
char buffer[4096]={};
while (1){
struct epoll_event readySet[2];//就绪集合---->数组
int readySetNum=epoll_wait(epfd,readySet,2,-1);
for (int i = 0; i < readySetNum; ++i) {
if (readySet[i].data.fd==STDIN_FILENO){
bzero(buffer,sizeof (buffer));
ssize_t sret = read(STDIN_FILENO,buffer,sizeof (buffer));
if (sret==0) break;
send(netfd,buffer,sizeof (buffer),0);
}else if(readySet[i].data.fd==netfd){
bzero(buffer,sizeof (buffer));
ssize_t sret = recv(netfd,buffer,sizeof (buffer),0);
if (sret==0) break;
printf("buffer=%s\n",buffer);
}
}
}
return 0;
}
#endif
Client
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
//客户端
//192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2]));
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int ret = connect(sockfd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr));
if (ret==-1) perror("bind");
int epfd = epoll_create(1);//创建epoll文件对象
//设置监听 因为epoll文件对象的监听集合和就绪集合在内核态是分开的 所以可以直接写在while循环外面
//设置属性
struct epoll_event events;//什么情况就绪,就绪了怎么办
events.events=EPOLLIN;//读就绪
events.data.fd=STDIN_FILENO;//就绪之后放入STDIN_FILENO
epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&events);
events.events=EPOLLIN;
events.data.fd=sockfd;//就绪之后放入netfd
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&events);
char buffer[4096]={};
while (1){
struct epoll_event readySet[1024];
int readySetNum=epoll_wait(epfd,readySet,1024,-1);
for (int i = 0; i < readySetNum; ++i) {
if (readySet[i].data.fd==STDIN_FILENO){
bzero(buffer,sizeof (buffer));
ssize_t sret = read(STDIN_FILENO,buffer,sizeof (buffer));
if (sret==0) break;
send(sockfd,buffer,sizeof (buffer),0);
}else if(readySet[i].data.fd==sockfd){
bzero(buffer,sizeof (buffer));
ssize_t sret = recv(sockfd,buffer,sizeof (buffer),0);
if (sret==0) break;
printf("buffer=%s\n",buffer);
}
}
}
return 0;
}
#endif
4.12 基于epoll实现了一个可以接受客户端断线重连的TCP服务器
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
//服务端
//192.168.1.104 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2]));
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int reuse =1; //允许重用
int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
if (ret==-1) perror("setsockopt");
ret = bind(sockfd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr));
if (ret==-1) perror("bind");
listen(sockfd,50);
//sockfd是监听socket文件描述符
struct sockaddr_in ClientAddr;
socklen_t socklen =sizeof (ClientAddr);
//socklen 必须赋初值
int epfd = epoll_create(1);//创建epoll文件对象
//设置监听 因为epoll文件对象的监听集合和就绪集合在内核态是分开的 所以可以直接写在while循环外面
//设置属性
struct epoll_event events;//什么情况就绪,就绪了怎么办
events.events=EPOLLIN;//读就绪
events.data.fd=sockfd;//就绪之后放入sockfd
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&events);//最开始只监听sockfd
int netfd=-1;//netfd=-1 代表阿强不存在
char buffer[4096]={};
while (1){
struct epoll_event readySet[1024];//就绪集合---->数组
int readySetNum=epoll_wait(epfd,readySet,1024,-1);
for (int i = 0; i < readySetNum; ++i) {
if (readySet[i].data.fd==sockfd){
netfd= accept(sockfd,(struct sockaddr*)&ClientAddr,&socklen);
printf("a qiang is connected\n");
printf("ip = %s,port = %d\n", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);
events.events=EPOLLIN;//读就绪
events.data.fd=netfd;//就绪之后放入sockfd
epoll_ctl(epfd,EPOLL_CTL_ADD,netfd,&events);
events.events=EPOLLIN;//读就绪
events.data.fd=STDIN_FILENO;//就绪之后放入sockfd
epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&events);
}else if ( netfd!=-1 && readySet[i].data.fd==STDIN_FILENO){
bzero(buffer,sizeof (buffer));
ssize_t sret = read(STDIN_FILENO,buffer,sizeof (buffer));
if (sret==0){
epoll_ctl(epfd,EPOLL_CTL_DEL,STDIN_FILENO,NULL);
epoll_ctl(epfd,EPOLL_CTL_DEL,netfd,NULL);
events.events=EPOLLIN;//读就绪
events.data.fd=sockfd;//就绪之后放入sockfd
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&events);
close(netfd);
netfd=-1;
printf("ni shi yi ge hao ren\n");
continue;
}
send(netfd,buffer,sizeof (buffer),0);
} else if (netfd!=-1 && readySet[i].data.fd==netfd){
bzero(buffer,sizeof (buffer));
ssize_t sret = recv(netfd,buffer,sizeof (buffer),0);
if (sret==0){
epoll_ctl(epfd,EPOLL_CTL_DEL,STDIN_FILENO,NULL);
epoll_ctl(epfd,EPOLL_CTL_DEL,netfd,NULL);
events.events=EPOLLIN;//读就绪
events.data.fd=sockfd;//就绪之后放入sockfd
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&events);
close(netfd);
netfd=-1;
printf("wo hui hao hao de\n");
continue;
}
printf("buffer=%s\n",buffer);
}
}
}
return 0;
}
#endif
4.13 基于epoll IO 多路复用实现的一个TCP简单的聊天室
#if 1
#include "func.h"
typedef struct Conn_s{
int isAlive;
int netfd;
time_t lastActive;
}Conn_t;
int main(int argc, char *argv[]) {
//服务端
//192.168.1.102 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2]));
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int reuse =1; //允许重用
int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
if (ret==-1) perror("setsockopt");
ret = bind(sockfd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr));
if (ret==-1) perror("bind");
listen(sockfd,50);
//sockfd是监听socket文件描述符
int epfd = epoll_create(1);
struct epoll_event events;
events.events = EPOLLIN;//读就绪
events.data.fd=sockfd;//就绪之后放入sockfd
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&events);
Conn_t conn[1024];
for (int i = 0; i < 1024; ++i) {
conn[i].isAlive=0;
}
int fdtoidx[1024];
for (int i = 0; i < 1024; ++i) {
fdtoidx[i]=-1;
}
int curidx = 0;
char buffer[4096]={0};
time_t now;
while (1){
now= time(NULL);
printf("now=%s\n", ctime(&now));
struct epoll_event readySet[1024];
int readyNum = epoll_wait(epfd,readySet,1024,1000);
for (int i = 0; i < readyNum; ++i) {
if (readySet[i].data.fd==sockfd){
int netfd = accept(sockfd,NULL,NULL);
printf("curidx=%d,netfd=%d\n",curidx,netfd);
conn[curidx].isAlive=1;
conn[curidx].netfd=netfd;
conn[curidx].lastActive= time(NULL);
fdtoidx[netfd]=curidx;
events.events=EPOLLIN;
events.data.fd=netfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,netfd,&events);
++curidx;
}else{
int netfd = readySet[i].data.fd;
bzero(buffer,sizeof (buffer));
ssize_t sret = recv(netfd,buffer,sizeof (buffer),0);
if (sret==0){
printf("a Client is closed\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,netfd,NULL);
int idx = fdtoidx[netfd];
conn[idx].isAlive=0;
fdtoidx[netfd]=-1;
close(netfd);
continue;
}
int idx =fdtoidx[netfd];
conn[idx].lastActive= time(NULL);
for (int j = 0; j < curidx; ++j) {
if (conn[j].isAlive==1 && conn[j].netfd!=netfd){
send(conn[j].netfd,buffer,strlen(buffer),0);
}
}
}
}
for (int i = 0; i < curidx; ++i) {
if (conn[i].isAlive==1 && now-conn[i].lastActive>10){
epoll_ctl(epfd,EPOLL_CTL_DEL,conn[i].netfd,NULL);
conn[i].isAlive=0;
fdtoidx[conn[i].netfd]=-1;
close(conn[i].netfd);
}
}
}
return 0;
}
#endif
4.14 将水平触发设置成边缘触发
#if 1
#include "func.h"
int main(int argc, char *argv[]) {
//服务端
//192.168.1.102 1234
if (argc<3) return -1;
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ServerAddr;
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port= htons(atoi(argv[2]));
ServerAddr.sin_addr.s_addr= inet_addr(argv[1]);
int reuse =1; //允许重用
int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
if (ret==-1) perror("setsockopt");
ret = bind(sockfd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr));
if (ret==-1) perror("bind");
listen(sockfd,50);
//sockfd是监听socket文件描述符
struct sockaddr_in ClientAddr;
socklen_t socklen =sizeof (ClientAddr);
//socklen 必须赋初值
int netfd= accept(sockfd,(struct sockaddr*)&ClientAddr,&socklen);
printf("Client is connected \n");
printf("ip = %s,port = %d\n", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));
int epfd = epoll_create(1);//创建epoll文件对象
//设置监听 因为epoll文件对象的监听集合和就绪集合在内核态是分开的 所以可以直接写在while循环外面
//设置属性
struct epoll_event events;//什么情况就绪,就绪了怎么办
events.events=EPOLLIN|EPOLLET;//读就绪
events.data.fd=STDIN_FILENO;//就绪之后放入STDIN_FILENO
epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&events);
events.events=EPOLLIN|EPOLLET;//边缘触发 edge-triggered
events.data.fd=netfd;//就绪之后放入netfd
epoll_ctl(epfd,EPOLL_CTL_ADD,netfd,&events);
char buffer[3]={};
while (1){
struct epoll_event readySet[2];//就绪集合---->数组
int readySetNum=epoll_wait(epfd,readySet,2,-1);
printf("epoll wait ready\n");
for (int i = 0; i < readySetNum; ++i) {
if (readySet[i].data.fd==STDIN_FILENO){
bzero(buffer,sizeof (buffer));
read(STDIN_FILENO,buffer,sizeof (buffer));
send(netfd,buffer,sizeof (buffer),0);
}
else if(readySet[i].data.fd==netfd){
bzero(buffer,sizeof (buffer));
while (1){
ssize_t sret = recv(netfd,buffer,2,MSG_DONTWAIT);//msg_dontwait 临时非阻塞
if (sret==-1 || sret==0) break; //sret = -1 缓冲区读完了 sret = 0 客户端断开了
printf("buffer=%s\n",buffer);
sleep(1);
}
}
}
}
return 0;
}
#endif
4.15 补充 fcntl函数
#if 1
#include "func.h"
int SetNonblock(int fd){
int flag = fcntl(fd,F_GETFL);
flag=flag|O_NONBLOCK;
int ret = fcntl(fd,F_SETFL,flag);
if (ret==-1) perror("fcntl");
return 0;
}
int main(int argc, char *argv[]) {
if (argc<2) return -1;
int fd = open(argv[1],O_RDONLY);
SetNonblock(fd);
if (fd==-1) perror("open");
char buffer[3]={0};
while (1){
bzero(buffer,sizeof (buffer));
ssize_t sret = read(fd,buffer,2);
printf("sret = %ld,buffer=%s\n",sret,buffer);
sleep(1);
}
return 0;
}
#endif
可以修改对管道和网络设备的读取数据时候应对没有数据的情况下设置成非阻塞状态
五、总结
基础能力:掌握 socket API 与 TCP/IP 协议
性能优化:优先使用 epoll+ET 模式处理高并发
工程实践:注重错误处理、资源管理与可维护性
通过合理选择 I/O 模型和事件触发模式,配合高效的数据处理逻辑,我们可以构建出高性能、高可靠性的 Linux 网络服务。