半关闭只能实现数据单方向的传输;当TCP 接中A向 B 发送 FIN 请求关闭,另一端 B 回应ACK 之后 (A 端进入 FIN_WAIT_2 状态),并没有立即发送 FIN 给 A,A 方处于半连接状态 (半开关),此时 A 可以接收 B 发送的数据,但是 A 已经不能再向 B 发送数据

close不会影响到其他进程,shutdown会影响到其他进程;
网络信息相关的命令
netstat
-a 所有的Socket
-p 正在所用socket的程序名称
-n 直接使用IP地址,不通过域名服务器
端口复用
1. 防止服务器重启时之前绑定的端口还没释放
2. 程序突然退出而系统没有释放端口

I/O多路复用使程序可以同时监听多个文件描述符,提高程序性能;select/poll/epoll
阻塞等待:不占用CPU宝贵时间;但同一时刻只能处理一个操作,效率低。
非阻塞,忙轮询:提高了程序执行效率;但会占用更多的CPU资源。
select/poll:委托内核进行检测,但仍需要进行遍历
epoll:同样委托内核,但无需进行遍历
主旨思想:
1. 构造关于文件描述符的列表,将要监听的文件描述符添加到表中
2. 调用系统函数,监听该列表中的文件描述符,知道描述符中的一个/多个进行了I/O操作,函数才返回(该函数是阻塞的,且该函数对于文件描述符的检测是由内核完成的)
3. 返回时,告诉进程有多少描述符要进行I/O操作

返回值: 失败 - -1,成功 - 检测到的描述符个数

- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- int main(){
-
- // 创建socket
- int lfd = socket(PF_INET , SOCK_STREAM , 0);
- struct sockaddr_in saddr;
- saddr.sin_port = htons(9999);
- saddr.sin_family = AF_INET;
- saddr.sin_addr.s_addr = INADDR_ANY;
- // 绑定
- bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
- // 监听
- listen(lfd , 8);
-
- fd_set rdset , tmp;
- FD_ZERO(&rdset);
- FD_SET(lfd , &rdset);
- int maxfd = lfd+1;
- while(1){
- tmp = rdset;
- // 调用select 系统检测
- int ret = select(maxfd+1 , &tmp , NULL , NULL , NULL);
- if(ret == -1){
- perror("select");
- exit(-1);
- }
- else if(ret == 0){
- continue;
- }
- else{
- // 检测到了文件描述符的数据发生了改变
- if(FD_ISSET(lfd , &tmp)){
- // 有客户端连接进来
- struct sockaddr_in caddr;
- socklen_t len = sizeof(caddr);
- int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
- // 添加进去
- FD_SET(cfd , &rdset);
-
- maxfd = maxfd>cfd?maxfd:cfd+1;
- }
- for(int i = lfd+1 ; i <= maxfd ; i++){
- if(FD_ISSET(i , &tmp)){
- // 说明客户端发来了数据
- char buf[1024];
- int len = read(i , buf , sizeof(buf));
- if(len == -1){
- perror("read");
- exit(-1);
- }
- else if(len == 0){
- cout<<"client close..."<
- close(i);
- FD_CLR(i,&rdset);
- }
- else{
- cout<<"发来了数据:"<
- write(i , buf , strlen(buf)+1);
- }
- }
- }
- }
- }
- close(lfd);
- return 0;
- }
poll

- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- int main(){
-
- // 创建socket
- int lfd = socket(PF_INET , SOCK_STREAM , 0);
- struct sockaddr_in saddr;
- saddr.sin_port = htons(9999);
- saddr.sin_family = AF_INET;
- saddr.sin_addr.s_addr = INADDR_ANY;
- // 绑定
- bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
- // 监听
- listen(lfd , 8);
-
- // 初始化检测文件描述符数组
- struct pollfd fds[1024];
- for(int i = 0 ; i<1024 ; i++){
- fds[i].fd = -1;
- fds[i].events = POLLIN;
- }
- fds[0].fd = lfd;
-
- int nfds = 0;
-
- while(1){
- // poll 系统检测
- int ret = poll(fds , nfds+1 , -1);
- if(ret == -1){
- perror("poll");
- exit(-1);
- }
- else if(ret == 0){
- continue;
- }
- else{
- // 检测到了文件描述符的数据发生了改变
- if(fds[0].revents & POLLIN){
- // 有客户端连接进来
- struct sockaddr_in caddr;
- socklen_t len = sizeof(caddr);
- int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
- // 添加进去
- for(int i = 1 ; i < 1024 ; i++){
- if(fds[i].fd == -1){
- fds[i].fd = cfd;
- fds[i].events = POLLIN;
- break;
- }
- }
-
- nfds = nfds>cfd?nfds:cfd;
- }
- for(int i = 1 ; i <= nfds ; i++){
- if(fds[i].revents & POLLIN){
- // 说明客户端发来了数据
- char buf[1024];
- int len = read(fds[i].fd , buf , sizeof(buf));
- if(len == -1){
- perror("read");
- exit(-1);
- }
- else if(len == 0){
- cout<<"client close..."<
- close(fds[i].fd);
- fds[i].fd = -1;
- }
- else{
- cout<<"发来了数据:"<
- write(fds[i].fd , buf , strlen(buf)+1);
- }
- }
- }
- }
- }
- close(lfd);
- return 0;
- }
epoll
内核,红黑树记录要检测的文件描述符,避免了用户态到内核态的数据拷贝开销;
内核,双链表存放数据改变的文件描述符




- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- int main(){
- // 创建socket
- int lfd = socket(PF_INET , SOCK_STREAM , 0);
- struct sockaddr_in saddr;
- saddr.sin_port = htons(9999);
- saddr.sin_family = AF_INET;
- saddr.sin_addr.s_addr = INADDR_ANY;
- // 绑定
- bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
- // 监听
- listen(lfd , 8);
- // 创建epoll实例
- int epfd = epoll_create(100);
- // 添加监听文件描述符
- struct epoll_event epev;
- epev.events = EPOLLIN;
- epev.data.fd = lfd;
- epoll_ctl(epfd , EPOLL_CTL_ADD , lfd , &epev);
-
- struct epoll_event epevs[1024];
- while(1){
- int ret = epoll_wait(epfd , epevs , 1024 , -1);
- if(ret == -1){
- perror("epoll");
- exit(-1);
- }
- cout<
"个发生了改变"< - for(int i = 0 ; i
- int curfd = epevs[i].data.fd;
- if(curfd == lfd){
- // 监听的文件描述符有客户端连接
- struct sockaddr_in caddr;
- socklen_t len = sizeof(caddr);
- int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
- epev.events = EPOLLIN;
- epev.data.fd = cfd;
- epoll_ctl(epfd , EPOLL_CTL_ADD , cfd , &epev);
- }
- else{
- // 有数据到达
- char buf[1024];
- int len = read(curfd , buf , sizeof(buf));
- if(len == -1){
- perror("read");
- exit(-1);
- }
- else if(len == 0){
- cout<<"client close..."<
- epoll_ctl(epfd , EPOLL_CTL_DEL , curfd , NULL);
- close(curfd);
- }
- else{
- cout<<"发来了数据:"<
- write(curfd , buf , strlen(buf)+1);
- }
- }
- }
- }
- close(lfd);
- close(epfd);
-
- return 0;
- }
epoll的两种工作模式
LT模式 - 水平触发
默认的工作模式,支持block/no-block;,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行IO操作。如果你不作任何操作,内核还是会继续通知你

- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- int main(){
- // 创建socket
- int lfd = socket(PF_INET , SOCK_STREAM , 0);
- struct sockaddr_in saddr;
- saddr.sin_port = htons(9999);
- saddr.sin_family = AF_INET;
- saddr.sin_addr.s_addr = INADDR_ANY;
- // 绑定
- bind(lfd , (struct sockaddr*)&saddr , sizeof(saddr));
- // 监听
- listen(lfd , 8);
- // 创建epoll实例
- int epfd = epoll_create(100);
- // 添加监听文件描述符
- struct epoll_event epev;
- epev.events = EPOLLIN;
- epev.data.fd = lfd;
- epoll_ctl(epfd , EPOLL_CTL_ADD , lfd , &epev);
-
- struct epoll_event epevs[1024];
- while(1){
- int ret = epoll_wait(epfd , epevs , 1024 , -1);
- if(ret == -1){
- perror("epoll");
- exit(-1);
- }
- cout<
"个发生了改变"< - for(int i = 0 ; i
- int curfd = epevs[i].data.fd;
- if(curfd == lfd){
- // 监听的文件描述符有客户端连接
- struct sockaddr_in caddr;
- socklen_t len = sizeof(caddr);
- int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
- epev.events = EPOLLIN;
- epev.data.fd = cfd;
- epoll_ctl(epfd , EPOLL_CTL_ADD , cfd , &epev);
- }
- else{
- // 有数据到达
- char buf[5];
- int len = read(curfd , buf , sizeof(buf));
- if(len == -1){
- perror("read");
- exit(-1);
- }
- else if(len == 0){
- cout<<"client close..."<
- epoll_ctl(epfd , EPOLL_CTL_DEL , curfd , NULL);
- close(curfd);
- }
- else{
- cout<<"发来了数据:"<
- write(curfd , buf , strlen(buf)+1);
- }
- }
- }
- }
- close(lfd);
- close(epfd);
-
- return 0;
- }
ET模式 - 边沿触发
告诉工作方式,只支持no-block,在这种模式下,当描述符从未就绪变为就绪时,内核通过epol告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意如果一直不对这个 fd 作IO操作,内核不会发送更多的通知 (only once) 。但是缓冲区中的数据不会丢失

ET模式效率比LT模式高,ET模式下必须使用非阻塞套接口,避免由于一个描述符的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死;
要设置边沿触发
1. 需要在epoll_event中设置EPOLLET
2.

UDP通信实现 - 无需多进程/多线程的并发实现



- // server
- #include
- #include
- #include
- #include
- #include
- #include
- using namespace std;
-
- int main(){
- // 创建socket
- int fd = socket(PF_INET , SOCK_DGRAM , 0);
- if(fd == -1){
- perror("socket");
- exit(-1);
- }
- // 绑定
- struct sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_port = htons(9999);
- addr.sin_addr.s_addr = INADDR_ANY;
-
- int ret = bind(fd ,(struct sockaddr*) &addr , sizeof(addr));
- if(ret == -1){
- perror("bind");
- exit(-1);
- }
-
- // 通信
- while(1){
- // 接收数据
- char buf[128];
- char ip[16];
- struct sockaddr_in caddr;
- socklen_t len = sizeof(caddr);
- int num = recvfrom(fd , buf , sizeof(buf) , 0 , (struct sockaddr*)&caddr , &len);
- string s1 = "IP: ";
- s1 += inet_ntop(AF_INET , &caddr.sin_addr.s_addr , ip , sizeof(ip));
- string s2 = "Port: ";
- s2 += ntohs(caddr.sin_port);
- cout<
" "< - cout<<"rcv data: "<
-
- sendto(fd , buf , strlen(buf)+1 , 0 , (struct sockaddr*)&caddr , len);
- }
-
- close(fd);
- return 0;
- }
- // client
- #include
- #include
- #include
- #include
- #include
- #include
- using namespace std;
-
- int main(){
- // 创建socket
- int fd = socket(PF_INET , SOCK_DGRAM , 0);
- if(fd == -1){
- perror("socket");
- exit(-1);
- }
-
- struct sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_port = htons(9999);
- inet_pton(AF_INET , "127.0.0.1" , &addr.sin_addr.s_addr);
-
- // 通信
- int num = 0;
- socklen_t len = sizeof(addr);
- while(1){
- char buf[128];
- sprintf(buf , "hello 647 %d" , num++);
- sendto(fd , buf , strlen(buf)+1 , 0 , (struct sockaddr*)&addr , len);
- // 接收数据
- int num = recvfrom(fd , buf , sizeof(buf) , 0 , NULL , NULL);
- cout<<"rcv data: "<
-
- sleep(1);
-
- }
-
- close(fd);
- return 0;
- }
广播和组播 - 只能使用UDP
广播 - 向子网中多台计算机发送消息,每个广播消息都包含一个特殊的IP地址,这个IP中子网内主机标志部分的二进制全部为1;
1. 只能在局域网中使用
2. 客户端需要绑定服务器广播使用的端口,才能接收到广播消息

组播 (多播) - 标识一组IP接口,是在单播和广播之间的一种折中方案,多播数据包只由对其感兴趣的接口接收;
1. 组播可以用于局域网和广域网
2. 客户端需要加入多播组才能接收到

本地套接字通信
作用:用于进程间的通信;实现流程和网络套接字类似,一般采用TCP的通信流程

- 服务器端
-
- 1. 创建监听的套接字
-
- int lfd = socket(AF_UNIX/AF_LOCAL , SOCK_STREAM , 0);
-
- 2. 监听套接字绑定本地的套接字文件
-
- struct sockaddr_un addr;
-
- bind(lfd, addr, len); // 绑定成功后sun_path中的套接字文件会自动生成
-
- 3. 监听
-
- 4. 等待并接受客户端请求
-
- 5. 通信
-
- 6. 关闭连接
- 客户端
-
- 1. 创建通信的套接字
-
- 2. 绑定本地IP端口
-
- 3. 连接服务器
-
- 4. 通信
-
- 5. 关闭连接
- // 服务端
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- using namespace std;
-
- int main(){
- unlink("server.sock");
- // 创建监听套接字
- int lfd = socket(AF_LOCAL , SOCK_STREAM , 0);
- if(lfd == -1){
- perror("socket");
- exit(-1);
- }
- // 绑定本地套接字文件
- struct sockaddr_un addr;
- addr.sun_family = AF_LOCAL;
- strcpy(addr.sun_path , "server.sock");
- int ret = bind(lfd , (struct sockaddr*)&addr , sizeof(addr));
- if(ret == -1){
- perror("bind");
- exit(-1);
- }
- // 监听
- ret = listen(lfd , 100);
- if(ret == -1){
- perror("listen");
- exit(-1);
- }
- // 等待客户端连接
- struct sockaddr_un caddr;
- socklen_t len = sizeof(caddr);
- int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
- if(cfd == -1){
- perror("accept");
- exit(-1);
- }
-
- cout<<"客户端文件:"<
- // 通信
- while(1){
- char buf[128];
- int len = recv(cfd , buf , sizeof(buf) , 0);
- if(len == -1){
- perror("recv");
- exit(-1);
- }
- else if(len == 0){
- cout<<"client close..."<
- break;
- }
- else{
- cout<<"recv data: "<
- send(cfd , buf , len , 0);
- }
- }
- close(cfd);
- close(lfd);
- return 0;
- }
- // 客户端
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- using namespace std;
-
- int main(){
- unlink("client.sock");
- // 创建监听套接字
- int cfd = socket(AF_LOCAL , SOCK_STREAM , 0);
- if(cfd == -1){
- perror("socket");
- exit(-1);
- }
- // 绑定本地套接字文件
- struct sockaddr_un addr;
- addr.sun_family = AF_LOCAL;
- strcpy(addr.sun_path , "client.sock");
- int ret = bind(cfd , (struct sockaddr*)&addr , sizeof(addr));
- if(ret == -1){
- perror("bind");
- exit(-1);
- }
- // 连接服务器
- struct sockaddr_un saddr;
- saddr.sun_family = AF_LOCAL;
- strcpy(saddr.sun_path , "server.sock");
- ret = connect(cfd , (struct sockaddr*)&saddr , sizeof(saddr));
- if(ret == -1){
- perror("connect");
- exit(-1);
- }
-
- // 通信
- int num = 0;
- while(1){
- char buf[128];
- sprintf(buf , "hello 647 %d\n" , num++);
- send(cfd , buf , strlen(buf)+1 , 0);
-
- int len = recv(cfd , buf , sizeof(buf) , 0);
- if(len == -1){
- perror("recv");
- exit(-1);
- }
- else if(len == 0){
- cout<<"Server close..."<
- break;
- }
- else{
- cout<<"recv data: "<
- }
- sleep(1);
- }
- close(cfd);
- return 0;
- }
-
相关阅读:
系分 - 操作系统 - 嵌入式
【PID优化】基于樽海鞘算法PID控制器优化设计含Matlab源码
Maven引入Aspose依赖
六级高频词汇——Group02
模拟一个火车站售票小例子
Spark RDD算子
mysql主从和mycat读写分离的安装及验证
1.Javascript-JavaScript事件
Jetson Nano TensorRT C++加速 YOLOV5,集成进qt项目中
计算机网络:随机访问介质访问控制之令牌传递协议
-
原文地址:https://blog.csdn.net/rygy_/article/details/132677809