6.状态转移
- 三次握手:
- SYN_SENT:client端第一次握手时发起端的状态,
- LISTEN:server端未接受到握手时一直处于的状态
- SYN_RCVD:第一次握手后server端的状态
- ESTABLISHED:建立连接后client端和server端的状态
- 四次挥手
- FIN_WAIT_1:结束发起者的状态,第一次挥手
- CLOSE_WAIT:接收到FIN_WAIT_1后的状态
- LAST_ACK:发送ACK后的接收端的状态
- FIN_WAIT_2:接收到接收端ACK信号的状态
- TIME_WAIT:发送端接收到接收端发来的FIN信号的状态
- 查看网络相关状态信息:
- 命令:netstat
- 参数:
- -a:显示所有选项,默认不显示LISTEN相关
- -p:显示建立相关链接的程序名
- -n:拒绝显示别名,能显示数字的全部转化位数字
- -t:仅显示tcp相关
- -u:仅显示udp相关
- -l:仅列出在listen的服务状态
7.端口复用
- 用途:
- 防止服务器重启时之前绑定的端口还未释放
- 程序突然退出而系统没有释放端口
- 设置方法
- int opt = 1;
- setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,(const void(*)&opt,sizeof(opt)));
- 注意事项:绑定之前设置端口复用的属性
8.IO多路转接
-
是什么:
- 先构建一张有关文件描述符的列表,将要监听的文件描述符添加到该列表中
- 然后调用一个函数,监听该表中的文件描述符,直到这些描述符表中的一个进行IO操作时,该函数返回、
- 该函数为阻塞函数
- 函数对文件描述符的检测操作是由内核完成的
- 在返回时告诉进程有多少(哪些)描述符要进行IO操作
-
IO操作方式
- 阻塞等待:不占用CPU的宝贵时间片,同一时间只能处理一个操作,效率低
- 非阻塞等待,忙轮询:提高了效率,需要占用更多的cpu和系统资源
-
解决办法:IO多路转接技术,select、poll,epoll
-
select
-
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds
:要检测的文件描述符最大的fd+1 (或者直接用1024)readfds
:读集合writefds
:写集合exceptfds
:异常集合timeout
:NULL
:永久堵塞,检测到fd变化的时候返回struct timeval a
:a.tv_sec = 10;
a.tv_sec = 0;
-
文件描述符集类型:fd_set rdset;
-
文件描述符操作函数:
- 全部清空:
void FD_ZERO(fd_set *set);
- 从集合中删除一项
void FD_CLR(int fd, fd_set *set);
- 将文件描述符添加到集合
void FD_SET(int fd, fd_set *set);
- 判断文件描述符是否在集合中
int FD_ISSET(int fd,fd_set *set);
- 全部清空:
-
优缺点:
- 优点:跨平台
- 缺点:
- 每次调用,需要把fd集合从用户态拷贝到内核态,开销很大
- 需要在内核遍历传递进来所有的fd,开销也很大
- 支持的文件描述符数量调小,默认是1024
-
code
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <ctype.h> int main(int argc, const char* argv[]) { if(argc < 2) { printf("eg: ./a.out port\n"); exit(1); } struct sockaddr_in serv_addr; socklen_t serv_len = sizeof(serv_addr); int port = atoi(argv[1]); // 创建套接字 int lfd = socket(AF_INET, SOCK_STREAM, 0); // 初始化服务器 sockaddr_in memset(&serv_addr, 0, serv_len); serv_addr.sin_family = AF_INET; // 地址族 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP serv_addr.sin_port = htons(port); // 设置端口 // 绑定IP和端口 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); // 设置同时监听的最大个数 listen(lfd, 36); printf("Start accept ......\n"); struct sockaddr_in client_addr; socklen_t cli_len = sizeof(client_addr); // 最大的文件描述符 int maxfd = lfd; // 文件描述符读集合 fd_set reads, temp; // init FD_ZERO(&reads); FD_SET(lfd, &reads); while(1) { // 委托内核做IO检测 temp = reads; int ret = select(maxfd+1, &temp, NULL, NULL, NULL); if(ret == -1) { perror("select error"); exit(1); } // 客户端发起了新的连接 if(FD_ISSET(lfd, &temp)) { // 接受连接请求 - accept不阻塞 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); if(cfd == -1) { perror("accept error"); exit(1); } char ip[64]; printf("new client IP: %s, Port: %d\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(client_addr.sin_port)); // 将cfd加入到待检测的读集合中 - 下一次就可以检测到了 FD_SET(cfd, &reads); // 更新最大的文件描述符 maxfd = maxfd < cfd ? cfd : maxfd; } // 已经连接的客户端有数据到达 for(int i=lfd+1; i<=maxfd; ++i) { if(FD_ISSET(i, &temp)) { char buf[1024] = {0}; int len = recv(i, buf, sizeof(buf), 0); if(len == -1) { perror("recv error"); exit(1); } else if(len == 0) { printf("客户端已经断开了连接\n"); close(i); // 从读集合中删除 FD_CLR(i, &reads); } else { printf("recv buf: %s\n", buf); send(i, buf, strlen(buf)+1, 0); } } } } close(lfd); return 0; }
-
-
poll
-
int poll(struct pollfd *fd, nfds_t nfds, int timeout);
- pollfd:数组的地址
- nfds:数组的最大长度,数组中最后一个使用的元素下标+1
- 内核会轮询检测fd数组的每个文件描述符
- timeout:
- -1:永久阻塞
- 0:调用完立即返回
>0
:等待的时间毫秒级
- 返回值:IO发送变化的文件描述符个数
-
code
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <ctype.h> #include <poll.h> #define SERV_PORT 8989 int main(int argc, const char* argv[]) { int lfd, cfd; struct sockaddr_in serv_addr, clien_addr; int serv_len, clien_len; // 创建套接字 lfd = socket(AF_INET, SOCK_STREAM, 0); // 初始化服务器 sockaddr_in memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; // 地址族 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP serv_addr.sin_port = htons(SERV_PORT); // 设置端口 serv_len = sizeof(serv_addr); // 绑定IP和端口 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); // 设置同时监听的最大个数 listen(lfd, 36); printf("Start accept ......\n"); // poll结构体 struct pollfd allfd[1024]; int max_index = 0; // init for(int i=0; i<1024; ++i) { allfd[i].fd = -1; } allfd[0].fd = lfd; allfd[0].events = POLLIN; while(1) { int i = 0; int ret = poll(allfd, max_index+1, -1); if(ret == -1) { perror("poll error"); exit(1); } // 判断是否有连接请求 if(allfd[0].revents & POLLIN) { clien_len = sizeof(clien_addr); // 接受连接请求 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); printf("============\n"); // cfd添加到poll数组 for(i=0; i<1024; ++i) { if(allfd[i].fd == -1) { allfd[i].fd = cfd; break; } } // 更新最后一个元素的下标 max_index = max_index < i ? i : max_index; } // 遍历数组 for(i=1; i<=max_index; ++i) { int fd = allfd[i].fd; if(fd == -1) { continue; } if(allfd[i].revents & POLLIN) { // 接受数据 char buf[1024] = {0}; int len = recv(fd, buf, sizeof(buf), 0); if(len == -1) { perror("recv error"); exit(1); } else if(len == 0) { allfd[i].fd = -1; close(fd); printf("客户端已经主动断开连接。。。\n"); } else { printf("recv buf = %s\n", buf); for(int k=0; k<len; ++k) { buf[k] = toupper(buf[k]); } printf("buf toupper: %s\n", buf); send(fd, buf, strlen(buf)+1, 0); } } } } close(lfd); return 0; }
-
-
epoll
- 三个函数:
int epoll_create(int size);
- 生成一个epoll专用的文件描述符
size
:epoll上能关注的最大描述符,可以扩展
int epoll_ctl(int epdf, int op,int fd, struct epoll_event *event);
- 用于控制某个epoll文件描述符事件,可以注册,修改,删除
- epfd:epoll_create 生成的epoll专用描述符
- op:
- EPOLL_CTL_ADD --注册
- EPOLL_CTL_MOD – 修改
- EPOLL_CTL_DEL – 删除
- fd:关联的文件描述符
- event: 告诉内核要监听什么事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- 等待IO事件发生 - 可以设置阻塞的函数
- epfd:要检测的句柄
- events:用于回传待处理事件的数组
- maxevents: 告诉内核这个events的大小
- timeout: 为超时时间
- -1:永久阻塞
- 0:立即返回
>0
:
- 三个函数:
code
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <ctype.h> #include <sys/epoll.h> int main(int argc, const char* argv[]) { if(argc < 2) { printf("eg: ./a.out port\n"); exit(1); } struct sockaddr_in serv_addr; socklen_t serv_len = sizeof(serv_addr); int port = atoi(argv[1]); // 创建套接字 int lfd = socket(AF_INET, SOCK_STREAM, 0); // 初始化服务器 sockaddr_in memset(&serv_addr, 0, serv_len); serv_addr.sin_family = AF_INET; // 地址族 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP serv_addr.sin_port = htons(port); // 设置端口 // 绑定IP和端口 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); // 设置同时监听的最大个数 listen(lfd, 36); printf("Start accept ......\n"); struct sockaddr_in client_addr; socklen_t cli_len = sizeof(client_addr); // 创建epoll树根节点 int epfd = epoll_create(2000); // 初始化epoll树 struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = lfd; epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); struct epoll_event all[2000]; while(1) { // 使用epoll通知内核fd 文件IO检测 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); // 遍历all数组中的前ret个元素 for(int i=0; i<ret; ++i) { int fd = all[i].data.fd; // 判断是否有新连接 if(fd == lfd) { // 接受连接请求 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); if(cfd == -1) { perror("accept error"); exit(1); } // 将新得到的cfd挂到树上 struct epoll_event temp; temp.events = EPOLLIN; temp.data.fd = cfd; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp); // 打印客户端信息 char ip[64] = {0}; printf("New Client IP: %s, Port: %d\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(client_addr.sin_port)); } else { // 处理已经连接的客户端发送过来的数据 if(!all[i].events & EPOLLIN) { continue; } // 读数据 char buf[1024] = {0}; int len = recv(fd, buf, sizeof(buf), 0); if(len == -1) { perror("recv error"); exit(1); } else if(len == 0) { printf("client disconnected ....\n"); // fd从epoll树上删除 epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); close(fd); } else { printf(" recv buf: %s\n", buf); write(fd, buf, len); } } } } close(lfd); return 0; }
-
文件描述符突破1024限制
-
select - 突破不了
-
poll和epoll可以突破
- poll 链表
- epoll 红黑树
-
查看计算机硬件限制的文件描述符上限
cat/vi /proc/sys/fs/file-max
-
通过配置文件修改上限值
/etc/security/limits.conf
- 添加:
* soft nofile 8000
* hard nofile 8000
- 重启或注销
-
-
epoll的三种工作模式
- 水平触发模式
- 边沿触发模式
- 边沿非阻塞触发
-
3.UDP通信
- 通信流程
- 服务器端
- 创建套接字
- 第二个参数为SOCK_DGRAM
- 绑定IP和Port:bind
- fd
- struct sockaddr
- 通信
- 接收数据:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
fd
文件描述符buf
接受数据的缓冲区len
buf 的最大容量flag
0src_addr
另一端的IP和PORT,传出参数addrlen
传入传出参数
- 发送数据:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd
socket函数创建的buf
要发送的数据len
发送数据的最大长度flags
0dest_addr
另一端的IP和PORTaddrlen
dest_addr的长度
- 接收数据:
- 创建套接字
- 客户端:
- 创建套接字
- 通信
- 发送数据:
sendto
- 接受数据:
recvfrom
- 发送数据:
- 服务器端
code-server
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc, const char* argv[])
{
// 创建套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
{
perror("socket error");
exit(1);
}
// fd绑定本地的IP和端口
struct sockaddr_in serv;
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(8765);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv));
if(ret == -1)
{
perror("bind error");
exit(1);
}
struct sockaddr_in client;
socklen_t cli_len = sizeof(client);
// 通信
char buf[1024] = {0};
while(1)
{
int recvlen = recvfrom(fd, buf, sizeof(buf), 0,
(struct sockaddr*)&client, &cli_len);
if(recvlen == -1)
{
perror("recvform error");
exit(1);
}
printf("recv buf: %s\n", buf);
char ip[64] = {0};
printf("New Client IP: %s, Port: %d\n",
inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)),
ntohs(client.sin_port));
// 给客户端发送数据
sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client));
}
close(fd);
return 0;
}
code - client
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc, const char* argv[])
{
// create socket
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
{
perror("socket error");
exit(1);
}
// 初始化服务器的IP和端口
struct sockaddr_in serv;
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(8765);
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
// 通信
while(1)
{
char buf[1024] = {0};
fgets(buf, sizeof(buf), stdin);
// 数据的发送 - server - IP port
sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&serv, sizeof(serv));
// 等待服务器发送数据过来
recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
printf("recv buf: %s\n", buf);
}
close(fd);
return 0;
}