1 Berkeley Socket
2 地址信息设置
struct sockaddr 和 struct sockaddr_in
大小端转换
- 大小端的判断
- 使用gdb
- 程序判断
#include <stdio.h>
int main()
{
int i = 0x12345678;
char *p = (char*)&i; // 78 低地址存高位置数--小端
printf("p = %x\n",*p);
return 0;
}
- 注意端口号是2字节,16位置,所以一般使用htons
#include <54func.h>
int main()
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
char ip[] = "192.168.72.128";
//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;
}
域名和IP地址的对应关系
- gethostbyname只是用ipv4,断网不能使用
- 报错,不会设置errno
3 TCP通信
鸟瞰TCP通信
socket
connect
bind
- bind只能绑定本机IP。
如何排查网络故障
- 使用
netstat -an
- 抓包
- 使用tcpdump命令可以查看包的状态。
将tcpdump后的结果打包为文件然后使用法国wireshirk解读
- 使用tcpdump命令可以查看包的状态。
listen
DDOS攻击
accept
- read返回值
- 大于0,实际读到的字节数,并且buf=1024
- 如果read读到的数据的长度等于buf,返回的就是1024
- 如果read读到的数据长度小于buf,那就是小于1024的数值。
- 返回值为0,数据读完(读到文件、管道、socket末尾 —对端关闭)
- 返回值为-1,表明出现异常
- errno == EINTR 说明被信号中断 所以需要重启或者退出
- errno == EAGAIN(EWOULDBLOCK)非阻塞方式读,并且没有数据
- 其他值 真的出现错误 perror打印 exit
- 大于0,实际读到的字节数,并且buf=1024
send和recv
使用select实现TCP即时聊天
TIME_WAIT和setsockopt
- TCP断开连接,主动方会进入TIME_WASIT状态,bind重复绑定时会报错。
select监听socket支持断开重连
双人聊天
- 阿珍
#include <54func.h>
int main(int argc,char *argv[])
{
// ./4_server 10.102.1.35 1234
ARGS_CHECK(argc,3);
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));
ERROR_CHECK(ret,-1,"bind");
listen(sockfd,50);
// sockefd为监听socket
struct sockaddr_in clientAddr;
socklen_t socklen = sizeof(clientAddr);
// socklen 必须赋初值
// netfd为通信socket
int netfd = accept(sockfd,NULL,NULL);
char buf[4096] = {0};
fd_set rdset;
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))
{
memset(buf,0,sizeof(buf));
printf("Me:");
int ret = read(STDIN_FILENO,buf,sizeof(buf));
if(ret == 0)
{
break;
}
printf("\n");
send(netfd,buf,strlen(buf)-1,0);
}
if(FD_ISSET(netfd,&rdset))
{
memset(buf,0,sizeof(buf));
ret = recv(netfd,buf,sizeof(buf),0);
// 当对端关闭的之后,netfd一直可读,此时recv读取数据的返回值为0
if(ret == 0)
{
printf("阿强关闭了与你的对话\n");
break;
}
printf("阿强: %s\n",buf);
}
}
close(netfd);
close(sockfd);
return 0;
}
- 阿强
#include <54func.h>
int main(int argc,char *argv[])
{
// ./4_client 10.102.1.35 1234
ARGS_CHECK(argc,3);
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));
char buf[4096] = {0};
fd_set rdset;
while(1)
{
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO,&rdset);
FD_SET(sockfd,&rdset); // 是将sockfd不是将ret
select(sockfd+1,&rdset,NULL,NULL,NULL);
if(FD_ISSET(STDIN_FILENO,&rdset))
{
printf("Me:");
memset(buf,0,sizeof(buf));
ret = read(STDIN_FILENO,buf,sizeof(buf));
if(ret == 0)
{
break;
}
printf("\n");
send(sockfd,buf,strlen(buf)-1,0); // 这里为什么-1
}
else if(FD_ISSET(sockfd,&rdset))
{
memset(buf,0,sizeof(buf));
ret = recv(sockfd,buf,sizeof(buf),0);
if(ret == 0)
{
printf("阿珍关闭了对话\n");
break;
}
printf("阿珍:%s\n",buf);
}
}
close(sockfd);
return 0;
}
断开重连:客户端终止服务端仍然可以接受新连接
- accept本质上是一个读操作,读的对象就是分配监听队列中的连接,所以可以使用select监听。这样就可以有新连接到来再做accept操作,这样服务器既可以处理旧连接的收发和新连接的建立
- 只修改服务器
#include <54func.h>
int main(int argc,char *argv[])
{
// ./4_server 10.102.1.35 1234
ARGS_CHECK(argc,3);
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));
ret = bind(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
ERROR_CHECK(ret,-1,"bind");
listen(sockfd,50);
// sockefd为监听socket
struct sockaddr_in clientAddr;
socklen_t socklen = sizeof(clientAddr);
// socklen 必须赋初值
// netfd为通信socket
char buf[4096] = {0};
fd_set monitorSet; // 监听集合
fd_set readySet; // 就绪集合
FD_ZERO(&monitorSet);
FD_SET(sockfd,&monitorSet);
int netfd = -1; //netfd为-1s时阿强不存在
while(1)
{
// 这样实际的目的是监听集合里面的内容不会随着selct变换
memcpy(&readySet,&monitorSet,sizeof(monitorSet));
select(20,&readySet,NULL,NULL,NULL);
if(FD_ISSET(sockfd,&readySet))
{
netfd = accept(sockfd,NULL,NULL);
printf("阿强来了\n");
//阿强已经来了,我只监听阿强给我发的消息以及我发的消息
FD_CLR(sockfd,&monitorSet);
FD_SET(STDIN_FILENO,&monitorSet);
FD_SET(netfd,&monitorSet);
}
if(FD_ISSET(netfd,&readySet))
{
memset(buf,0,sizeof(buf));
ret = recv(netfd,buf,sizeof(buf),0);
// 当对端关闭的之后,netfd一直可读,此时recv读取数据的返回值为0
if(ret == 0)
{
FD_SET(sockfd,&monitorSet);
FD_CLR(STDIN_FILENO,&monitorSet);
FD_CLR(netfd,&monitorSet);
close(netfd);
netfd = -1;
printf("阿强关闭了与你的对话\n");
continue;
}
printf("阿强: %s\n",buf);
}
if(FD_ISSET(STDIN_FILENO,&readySet))
{
memset(buf,0,sizeof(buf));
int ret = read(STDIN_FILENO,buf,sizeof(buf));
if(ret == 0) // Ctrl+D
{
FD_SET(sockfd,&monitorSet);
FD_CLR(STDIN_FILENO,&monitorSet);
FD_CLR(netfd,&monitorSet);
printf("我要断开了\n");
break;
}
send(netfd,buf,strlen(buf)-1,0);
}
}
close(netfd);
close(sockfd);
return 0;
}
聊天室
- 服务端:建立连接和转发数据
#include <54func.h>
// 聊天室代码
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,3);
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));
ret = bind(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
ERROR_CHECK(ret,-1,"bind");
listen(sockfd,50);
fd_set monitorSet;
fd_set readySet;
FD_ZERO(&monitorSet);
FD_SET(sockfd,&monitorSet);
// 设计数据结构,存储所有客户端的netfd
int netfd[1024];
for(int i = 0;i < 1024;++i)
{
netfd[i] = -1;
}
int curidx = 0; // 下一次加入的netfd的下标,不用的直接置为-1
//用于查找的哈希表 netfd--->idx
int fdToIdx[1024];
for(int i =0;i<1024;i++)
{
fdToIdx[i] = -1;
}
char buf[1024];
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);
// 根据通信描述符找到找到对应的下标 ,从而在netfd中找到次通信描述符
fdToIdx[netfd[curidx]] = curidx;
printf("i = %d, netfd = %d \n",curidx,netfd[curidx]);
FD_SET(netfd[curidx],&monitorSet);
++curidx;
}
for(int i =0;i<curidx;i++)
{
if(netfd[i] != -1 && FD_ISSET(netfd[i],&readySet)) //netfd[i] 没有断掉,而且以及就绪
{
bzero(buf,1024);
ssize_t sret = recv(netfd[i],buf,sizeof(buf),0);
if(sret == 0)
{
// 某个客户端断开连接
FD_CLR(netfd[i],&monitorSet);
close(netfd[i]);
fdToIdx[netfd[i]] = -1;
netfd[i] = -1;
continue;
}
for(int j = 0;j<curidx;j++) // 给所有已连接的sockfd发送消息
{
if(netfd[j] != -1 && j!=i) // 该链接没有断开而且不是自己
{
send(netfd[j],buf,strlen(buf),0);
printf("i = %d %d %s \n",j,netfd[j],buf);
}
}
}
}
}
return 0;
}
将sockfd放入monitiorfd中
while()
{
复制monitiorfd到readyfd
select轮询readyfd
soclfd---》有新连接到来
{
将连接套接字放入monitiorfd中
}
一次轮询所有的已连接套接字
{
某一一个发送消息
发送给其他人
有一个断开了,从monitior中删除
}
}
#include <54func.h>
typedef struct Conn_s{
int isConnected; // 0:未连接 1:已经连接
int netfd;
time_t lastActive;
}Conn_t;
// 聊天室代码
int main(int argc,char *argv[])
{
ARGS_CHECK(argc,3);
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));
ret = bind(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
ERROR_CHECK(ret,-1,"bind");
listen(sockfd,50);
fd_set monitorSet;
fd_set readySet;
FD_ZERO(&monitorSet);
FD_SET(sockfd,&monitorSet);
// 设计数据结构,存储所有客户端的netfd
Conn_t client[1024];
for(int i = 0;i < 1024;++i)
{
client[i].isConnected = 0;
}
int curidx = 0; // 下一次加入的netfd的下标,不用的直接置为-1
//用于查找的哈希表 netfd--->idx
int fdToIdx[1024];
for(int i =0;i<1024;i++)
{
fdToIdx[i] = -1;
}
time_t now;
char buf[1024];
while(1)
{
memcpy(&readySet,&monitorSet,sizeof(fd_set));
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
select(1024,&readySet,NULL,NULL,&timeout);
now = time(NULL);
printf("now = %s\n",ctime(&now));
if(FD_ISSET(sockfd,&readySet))
{
// 将得到的通信新描述符放到数组里
client[curidx].netfd = accept(sockfd,NULL,NULL);
client[curidx].lastActive = time(NULL);
client[curidx].isConnected = 1;
// 根据通信描述符找到找到对应的下标 ,从而在netfd中找到次通信描述符
fdToIdx[client[curidx].netfd] = curidx;
printf("i = %d, netfd = %d time=%s \n",curidx,client[curidx].netfd,ctime(&client[curidx].lastActive));
FD_SET(client[curidx].netfd,&monitorSet);
++curidx;
}
for(int i =0;i<curidx;i++)
{
if(client[i].isConnected == 1 && FD_ISSET(client[i].netfd,&readySet)) //netfd[i] 没有断掉,而且以及就绪
{
bzero(buf,1024);
ssize_t sret = recv(client[i].netfd,buf,sizeof(buf),0);
if(sret == 0)
{
// 某个客户端断开连接
FD_CLR(client[i].netfd,&monitorSet);
close(client[i].netfd);
fdToIdx[client[i].netfd] = -1;
client[i].isConnected = 0;
continue;
}
client[i].lastActive = time(NULL);
printf("i = %d, netfd = %d time=%s \n",i,client[i].netfd,ctime(&client[i].lastActive));
for(int j = 0;j<curidx;j++)
{
if(client[j].isConnected != 0 && j!=i) // 该链接没有断开而且不是自 己
{
send(client[j].netfd,buf,strlen(buf),0);
printf("i = %d %d %s \n",j,client[j].netfd,buf);
}
}
}
}
for(int i=0;i<curidx;i++)
{
if(client[i].isConnected ==1 && (now-client[i].lastActive)>5)
{
printf("netfd = %d timeout~~~~~~~~~~~\n",client[i].netfd);
FD_CLR(client[i].netfd,&monitorSet);
close(client[i].netfd);
fdToIdx[client[i].netfd] = -1;
client[i].isConnected = 0;
}
}
}
return 0;
}
服务端程序设计思路
4 UDP通信
鸟瞰UDP通信
sendto和recvfrom
#include <54func.h>
int main(int argc,char **argv)
{
ARGS_CHECK(argc,3);
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
ERROR_CHECK(sockfd,-1,"sockfd");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret = bind(sockfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
struct sockaddr_in cliAddr;
memset(&cliAddr,0,sizeof(cliAddr));
char buf[1024] = {0};
socklen_t len = sizeof(cliAddr);
recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&cliAddr,&len);
printf("buf = %s\n",buf);
recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&cliAddr,&len);
printf("buf = %s\n",buf);
sendto(sockfd,"hello client",12,0,(struct sockaddr*)&cliAddr,len);
close(sockfd);
return 0;
}
#include <54func.h>
int main(int argc,char **argv)
{
ARGS_CHECK(argc,3);
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
ERROR_CHECK(sockfd,-1,"sockfd");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
char buf[1024] = {0};
socklen_t len = sizeof(serAddr);
sendto(sockfd,"hello udp",9,0,(struct sockaddr*)&serAddr,len);
sendto(sockfd,"hello server",12,0,(struct sockaddr*)&serAddr,len);
recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&serAddr,&len);
printf("buf = %s\n",buf);
close(sockfd);
return 0;
}
使用UDP的即时聊天
-
当有多个客户端发送消息时,分不清是谁是谁,因为没有像TCP的netfd
-
使用UDP的即时聊天
#include<54func.h>
int main(int argc,char* argv[]){
ARGS_CHECK(argc,3);
int sfd = socket(AF_INET,SOCK_DGRAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
char buf[64]={0};
struct sockaddr_in cliAddr;
memset(&cliAddr,0,sizeof(cliAddr));
socklen_t len = sizeof(cliAddr);
recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cliAddr,&len);
printf("buf = %s\n",buf);
fd_set rdset;
while(1)
{
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO,&rdset);
FD_SET(sfd,&rdset);
select(10,&rdset,NULL,NULL,NULL);
if(FD_ISSET(STDIN_FILENO,&rdset))
{
memset(buf,0,sizeof(buf));
ret = read(STDIN_FILENO,buf,sizeof(buf));
if(ret == 0)
break;
sendto(sfd,buf,strlen(buf)-1,0,(struct sockaddr*)&cliAddr,len);
}
else if(FD_ISSET(sfd,&rdset))
{
memset(buf,0,sizeof(buf));
ret = recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cliAddr,&len);
//对端断开的时候,newFd一直可读
//recv读数据的返回值是0
if(0 == ret)
{
printf("byebye\n");
close(sfd);
return 0;
}
printf("buf=%s\n",buf);
}
}
close(sfd);
return 0;
}
#include <54func.h>
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,3);
int sfd = socket(AF_INET,SOCK_DGRAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
char buf[64]={0};
socklen_t len = sizeof(serAddr);
int ret = sendto(sfd,"hello server",12,0,(struct sockaddr*)&serAddr,len);
printf("ret=%d\n",ret);
fd_set rdset;
while(1){
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO,&rdset);
FD_SET(sfd,&rdset);
select(sfd+1,&rdset,NULL,NULL,NULL);
if(FD_ISSET(STDIN_FILENO,&rdset)){
memset(buf,0,sizeof(buf));
ssize_t ret = read(STDIN_FILENO,buf,sizeof(buf));
if(ret == 0)
{
sendto(sfd,buf,0,0,(struct sockaddr*)&serAddr,len);
break;
}
}
else if(FD_ISSET(sfd,&rdset)){
memset(buf,0,sizeof(buf));
recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&serAddr,&len);
printf("buf=%s\n",buf);
}
}
close(sfd);
return 0;
}
- 断开连接对方不知道
- sendto可以发送长度为0的报
- 当主动方要退出时,按照Ctl+D,被动放收到长度为0的报,断开
- 但是不能对付意外退出,例如客户端输入Ctrl+C
- 可以注册该信号,当捕获到之后,发送长度为0的包即可。
UNIX 域套接字(stream sockets)和原始套接字(raw sockets)
5 epoll系统调用
- select的缺陷
epoll的基本原理
使用epoll取代select
- epoll的四个步骤
- epoll_create
- epoll_ctl
- epoll_wait
- 遍历就绪集合
#include<54func.h>
int main(int argc,char* argv[]){
ARGS_CHECK(argc,3);
// 创建监听套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret =0;
//reuse=1表示允许地址重用
int reuse = 1;
ret = setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
ERROR_CHECK(ret,-1,"setsockopt");
ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
ret = listen(sfd,10);
ERROR_CHECK(ret,-1,"listen");
//newFd代表的是跟客户端的TCP连接
int newFd = accept(sfd,NULL,NULL);
ERROR_CHECK(newFd,-1,"accept");
char buf[64]={0};
int epfd = epoll_create(1);
ERROR_CHECK(epfd,-1,"epoll_create");
struct epoll_event event,evs[2];
memset(&event,0,sizeof(event));
//把关心的描述符和对应的时间填到结构体里
event.data.fd = STDIN_FILENO;
event.events = EPOLLIN;
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&event);
ERROR_CHECK(ret,-1,"epoll_ctl");
event.data.fd = newFd;
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,newFd,&event);
ERROR_CHECK(ret,-1,"epoll_ctl");
int readyNum=0;
while(1){
readyNum = epoll_wait(epfd,evs,2,-1);//不用每次都重置监听集合了
for(int i=0;i<readyNum;i++){
if(evs[i].data.fd == STDIN_FILENO){
memset(buf,0,sizeof(buf));
read(STDIN_FILENO,buf,sizeof(buf));
send(newFd,buf,strlen(buf)-1,0);
}
else if(evs[i].data.fd == newFd){
memset(buf,0,sizeof(buf));
ret = recv(newFd,buf,sizeof(buf),0);
//对端断开的时候,newFd一直可读
//recv读数据的返回值是0
if(0 == ret){
printf("byebye\n");
close(sfd);
close(newFd);
return 0;
}
printf("buf=%s\n",buf);
}
}
}
close(newFd);
close(sfd);
return 0;
}
重新连接
#include<54func.h>
int main(int argc,char* argv[]){
ARGS_CHECK(argc,3);
// 创建监听套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret =0;
//reuse=1表示允许地址重用
int reuse = 1;
ret = setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
ERROR_CHECK(ret,-1,"setsockopt");
ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
ret = listen(sfd,10);
ERROR_CHECK(ret,-1,"listen");
//newFd代表的是跟客户端的TCP连接
int netFd = -1; // 最开始没有阿强连入
char buf[64]={0};
int epfd = epoll_create(1);
ERROR_CHECK(epfd,-1,"epoll_create");
struct epoll_event event;
memset(&event,0,sizeof(event));
//把关心的描述符和对应的时间填到结构体里
event.data.fd = sfd;
event.events = EPOLLIN; // 读就绪
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); // 最开始j只监控sockfd
ERROR_CHECK(ret,-1,"epoll_ctl");
int readyNum=0;
struct epoll_event readySet[1024];
while(1)
{
readyNum = epoll_wait(epfd,readySet,2,-1);//不用每次都重置监听集合了
for(int i=0;i<readyNum;i++)
{
if(readySet[i].data.fd == sfd)
{
netFd = accept(sfd,NULL,NULL);
printf("阿强连接上了 netfd = %d\n ",netFd);
// 不接受t其他阿强了,
epoll_ctl(epfd,EPOLL_CTL_DEL,sfd,NULL);
event.events = EPOLLIN;
event.data.fd = STDIN_FILENO;
epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&event);
event.events = EPOLLIN;
event.data.fd = netFd;
epoll_ctl(epfd,EPOLL_CTL_ADD,netFd,&event);
}
else if(netFd != -1 && readySet[i].data.fd == netFd)
{
bzero(buf,sizeof(buf));
ssize_t sret = recv(netFd,buf,sizeof(buf),0);
if(sret ==0)
{
printf("阿强关闭了\n");
event.events = EPOLLIN;
event.data.fd = sfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);
epoll_ctl(epfd,EPOLL_CTL_DEL,netFd,NULL);
epoll_ctl(epfd,EPOLL_CTL_DEL,STDIN_FILENO,NULL);
close(netFd);
netFd = -1;
break;
}
printf("buf= %s\n",buf);
}
else if(netFd != -1 && readySet[i].data.fd == STDIN_FILENO)
{
bzero(buf,sizeof(buf));
ssize_t sret = read(STDIN_FILENO,buf,sizeof(buf));
if(sret ==0) // Ctrl+D
{
printf("我要踢走q阿强了\n");
event.events = EPOLLIN;
event.data.fd = sfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); //加入监听描述,需要最后的参数。删除描述符不需要最后的参数
epoll_ctl(epfd,EPOLL_CTL_DEL,netFd,NULL);
epoll_ctl(epfd,EPOLL_CTL_DEL,STDIN_FILENO,NULL);
close(netFd);
netFd = -1;
break;
}
send(netFd,buf,strlen(buf),0);
}
}
}
close(netFd);
close(sfd);
return 0;
}
使用epoll关闭长期不发消息的连接
#include<54func.h>
int main(int argc,char* argv[]){
ARGS_CHECK(argc,3);
//创建监听套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret =0;
//reuse=1表示允许地址重用
int reuse = 1;
ret =
setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
ERROR_CHECK(ret,-1,"setsockopt");
ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
ret = listen(sfd,10);
ERROR_CHECK(ret,-1,"listen");
//newFd代表的是跟客户端的TCP连接
int newFd = 0;
char buf[4]={0};
int epfd = epoll_create(1);
ERROR_CHECK(epfd,-1,"epoll_create");
struct epoll_event event,evs[2];
memset(&event,0,sizeof(event));
//把关心的描述符和对应的时间填到结构体里
event.data.fd = STDIN_FILENO;
event.events = EPOLLIN;
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&event);
ERROR_CHECK(ret,-1,"epoll_ctl");
event.data.fd = sfd;
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);
ERROR_CHECK(ret,-1,"epoll_ctl");
int readyNum=0;
time_t lastTime,nowTime;
lastTime = nowTime = time(NULL);
int isCliLogin = 0;
while(1){
readyNum = epoll_wait(epfd,evs,2,1000);
printf("readyNum=%d \n",readyNum);
if(0 == readyNum && 1 == isCliLogin ){
nowTime = time(NULL);
if(nowTime - lastTime > 5){
printf("close\n");
close(newFd);
isCliLogin = 0;
}
}
for(int i=0;i<readyNum;i++){
lastTime = time(NULL);
printf("readyNum=%d fd=%d\n",readyNum,evs[i].data.fd);
if(evs[i].data.fd == STDIN_FILENO){
memset(buf,0,sizeof(buf));
read(STDIN_FILENO,buf,sizeof(buf));
send(newFd,buf,strlen(buf)-1,0);
}
else if(evs[i].data.fd == newFd){
memset(buf,0,sizeof(buf));
ret = recv(newFd,buf,sizeof(buf),0);
//对端断开的时候,newFd一直可读
//recv读数据的返回值是0
if(0 == ret){
printf("byebye\n");
close(newFd);
isCliLogin = 0;
continue;
}
printf("buf=%s\n",buf);
}
else if(evs[i].data.fd == sfd){
newFd = accept(sfd,NULL,NULL);
ERROR_CHECK(newFd,-1,"accept");
printf("newFd=%d\n",newFd);
isCliLogin = 1;//表示有客户端登录
event.data.fd = newFd;
event.events = EPOLLIN;
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,newFd,&event);
ERROR_CHECK(ret,-1,"epoll_ctl");
}
}
}
close(newFd);
close(sfd);
return 0;
}
#include<54func.h>
typedef struct Conn_s{
int isAlive;
int netfd;
time_t lastActive;
}Conn_t;
int main(int argc,char* argv[]){
ARGS_CHECK(argc,3);
// 创建监听套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(sockfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret =0;
//reuse=1表示允许地址重用
int reuse = 1;
ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
ERROR_CHECK(ret,-1,"setsockopt");
ret = bind(sockfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
ret = listen(sockfd,10);
ERROR_CHECK(ret,-1,"listen");
int epfd = epoll_create(1);
struct epoll_event events;
events.events = EPOLLIN;
events.data.fd = 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]; // fdtoidx[fd] ---> idx conn[idx]
for(int i = 0;i<1024;i++)
{
fdtoidx[i] = -1;
}
int curidx = 0;
char buf[1024];
time_t now;
while(1)
{
struct epoll_event readySet[1024];
int readyNum = epoll_wait(epfd,readySet,1024,1000); // 每1秒就绪一次
now = time(NULL);
printf("now = %s\n",ctime(&now));
for(int i =0;i<readyNum;i++)
{
if(readySet[i].data.fd == sockfd)
{
int netfd = accept(sockfd,NULL,NULL);
conn[curidx].isAlive = 1;
conn[curidx].netfd = netfd;
conn[curidx].lastActive = time(NULL);
fdtoidx[netfd] = curidx;
printf("id = %d, netfd = %d\n",curidx,netfd);
events.events = EPOLLIN;
events.data.fd = netfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,netfd,&events);
curidx++;
}else
{
int netfd = readySet[i].data.fd;
bzero(buf,sizeof(buf));
ssize_t sret = recv(netfd,buf,sizeof(buf),0);
if(sret == 0 )
{
printf("%d client closed\n",netfd);
epoll_ctl(epfd,EPOLL_CTL_DEL,netfd,NULL);
int idx = fdtoidx[netfd]; // 找到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 && fdtoidx[netfd] != j)
if(conn[j].isAlive == 1 && conn[j].netfd != netfd)
{
printf(" %d发送给%d : %s\n",netfd,conn[j].netfd,buf);
send(conn[j].netfd,buf,strlen(buf),0);
}
}
}
}
for(int i =0;i<curidx;i++)
{
if(conn[i].isAlive==1 && (now-conn[i].lastActive)>10)
{
printf("%d 超时\n",conn[i].netfd);
epoll_ctl(epfd,EPOLL_CTL_DEL,conn[i].netfd,NULL);
close(conn[i].netfd);
conn[i].isAlive = 0;
fdtoidx[conn[i].netfd] = -1;
}
}
}
return 0;
}
非阻塞读操作
阻塞式的,无数据可读时,阻塞。写端关闭,读端直接返回0
while+recv/read 必须是非阻塞的
- 读管道变成非阻塞的
- 5种IO模型
同步,read和write有明确的先后顺序
异步:read和write无顺序
-
同步阻塞
-
同步非阻塞
-
同步的IO多路复用
本质上是对同步非阻塞模型的优化,将数据是否准备好(轮询)交给内核去完成,准备好之后让内核通知线程
-
异步
-
信号驱动IO
epoll的边缘触发
- epoll水平触犯->边缘触发
- 边缘触发存在数据残留:如何解决 while多次读取
#include<54func.h>
int main(int argc,char* argv[]){
ARGS_CHECK(argc,3);
// 创建监听套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(sfd,-1,"socket");
struct sockaddr_in serAddr;
memset(&serAddr,0,sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret =0;
//reuse=1表示允许地址重用
int reuse = 1;
ret = setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
ERROR_CHECK(ret,-1,"setsockopt");
ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
ERROR_CHECK(ret,-1,"bind");
ret = listen(sfd,10);
ERROR_CHECK(ret,-1,"listen");
//newFd代表的是跟客户端的TCP连接
int newFd = accept(sfd,NULL,NULL);
ERROR_CHECK(newFd,-1,"accept");
char buf[8]={0};
int epfd = epoll_create(1);
ERROR_CHECK(epfd,-1,"epoll_create");
struct epoll_event event;
memset(&event,0,sizeof(event));
//把关心的描述符和对应的时间填到结构体里
event.data.fd = STDIN_FILENO; // 就绪之后放STDIN_FINENO
event.events = EPOLLIN|EPOLLET; // 读就绪 水平s触发->边缘触发
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&event);
ERROR_CHECK(ret,-1,"epoll_ctl");
event.data.fd = newFd; // 就绪之后放入netfd
event.events = EPOLLIN; // 读就绪
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,newFd,&event);
ERROR_CHECK(ret,-1,"epoll_ctl");
int readyNum=0;
struct epoll_event readySet[2];
while(1){
readyNum = epoll_wait(epfd,readySet,2,-1);//不用每次都重置监听集合了
printf("ready epoll");
for(int i=0;i<readyNum;i++){
if(readySet[i].data.fd == STDIN_FILENO){
memset(buf,0,sizeof(buf));
read(STDIN_FILENO,buf,sizeof(buf));
send(newFd,buf,strlen(buf)-1,0);
}
else if(readySet[i].data.fd == newFd){
bzero(buf,sizeof(buf));
memset(buf,0,sizeof(buf));
ret = recv(newFd,buf,2,MSG_DONTWAIT); // 临时非阻塞
if(ret == -1) // 为-1表示消息读完了
{
break;
}
//对端断开的时候,newFd一直可读
//recv读数据的返回值是0
if(0 == ret){
printf("byebye\n");
close(sfd);
close(newFd);
return 0;
}
printf("buf=%s\n",buf);
}
}
}
close(newFd);
close(sfd);
return 0;
}
是的,对于使用 epoll 的边缘触发(Edge-Triggered, ET)模式,确实需要将文件描述符设置为非阻塞模式。这是因为边缘触发模式只在数据状态发生变化时通知你一次,而不是在数据可读时持续通知。因此,如果不将文件描述符设置为非阻塞模式,可能会导致阻塞在 recv 或 read 调用中,从而无法正确处理其他事件。
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <cstring>
#include <iostream>
void setNonBlocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl F_GETFL");
exit(EXIT_FAILURE);
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) {
perror("fcntl F_SETFL");
exit(EXIT_FAILURE);
}
}
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <IP> <Port>" << std::endl;
return 1;
}
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd < 0) {
perror("socket");
return 1;
}
struct sockaddr_in serAddr;
memset(&serAddr, 0, sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int reuse = 1;
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
perror("setsockopt");
close(sfd);
return 1;
}
if (bind(sfd, (struct sockaddr*)&serAddr, sizeof(serAddr)) < 0) {
perror("bind");
close(sfd);
return 1;
}
if (listen(sfd, 10) < 0) {
perror("listen");
close(sfd);
return 1;
}
int newFd = accept(sfd, NULL, NULL);
if (newFd < 0) {
perror("accept");
close(sfd);
return 1;
}
// 设置新连接的文件描述符为非阻塞模式
setNonBlocking(newFd);
int epfd = epoll_create(1);
if (epfd < 0) {
perror("epoll_create");
close(sfd);
close(newFd);
水平触发,会导致epoll_wait不断就绪,直到数据读取完成
上面的while+边缘触发:则是epoll_wait就绪一次,不断读取数据
- 为什么使用边缘触发
旧版本的epoll支持边缘触发
假如epoll_wait和子线程recv在两个不同的线程执行
6 socket属性调整
7 recv和send的标志
MSG_DONTWAIT
MSG_PEEK
nc命令
- 可以不用些客户端,nc IP port