半關(guān)閉、端口復(fù)用
半關(guān)閉只能實現(xiàn)數(shù)據(jù)單方向的傳輸;當(dāng)TCP 接中A向 B 發(fā)送 FIN 請求關(guān)閉,另一端 B 回應(yīng)ACK 之后 (A 端進(jìn)入 FIN_WAIT_2 狀態(tài)),并沒有立即發(fā)送 FIN 給 A,A 方處于半連接狀態(tài) (半開關(guān)),此時 A 可以接收 B 發(fā)送的數(shù)據(jù),但是 A 已經(jīng)不能再向 B 發(fā)送數(shù)據(jù)
close不會影響到其他進(jìn)程,shutdown會影響到其他進(jìn)程;
網(wǎng)絡(luò)信息相關(guān)的命令
netstat
? ? ? ? -a 所有的Socket
? ? ? ? -p 正在所用socket的程序名稱
? ? ? ? -n 直接使用IP地址,不通過域名服務(wù)器
端口復(fù)用
1. 防止服務(wù)器重啟時之前綁定的端口還沒釋放
2. 程序突然退出而系統(tǒng)沒有釋放端口
IO多路復(fù)用簡介
I/O多路復(fù)用使程序可以同時監(jiān)聽多個文件描述符,提高程序性能;select/poll/epoll
阻塞等待:不占用CPU寶貴時間;但同一時刻只能處理一個操作,效率低。
非阻塞,忙輪詢:提高了程序執(zhí)行效率;但會占用更多的CPU資源。
select/poll:委托內(nèi)核進(jìn)行檢測,但仍需要進(jìn)行遍歷
epoll:同樣委托內(nèi)核,但無需進(jìn)行遍歷
select
主旨思想:
1. 構(gòu)造關(guān)于文件描述符的列表,將要監(jiān)聽的文件描述符添加到表中
2. 調(diào)用系統(tǒng)函數(shù),監(jiān)聽該列表中的文件描述符,知道描述符中的一個/多個進(jìn)行了I/O操作,函數(shù)才返回(該函數(shù)是阻塞的,且該函數(shù)對于文件描述符的檢測是由內(nèi)核完成的)
3. 返回時,告訴進(jìn)程有多少描述符要進(jìn)行I/O操作
返回值: 失敗 - -1,成功 - 檢測到的描述符個數(shù)
#include <iostream>
#include <cstdio>
#include <arpa/inet.h>
#include <cstdlib>
#include <string.h>
#include <sys/select.h>
#include <unistd.h>
using namespace std;
int main(){
// 創(chuàng)建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));
// 監(jiān)聽
listen(lfd , 8);
fd_set rdset , tmp;
FD_ZERO(&rdset);
FD_SET(lfd , &rdset);
int maxfd = lfd+1;
while(1){
tmp = rdset;
// 調(diào)用select 系統(tǒng)檢測
int ret = select(maxfd+1 , &tmp , NULL , NULL , NULL);
if(ret == -1){
perror("select");
exit(-1);
}
else if(ret == 0){
continue;
}
else{
// 檢測到了文件描述符的數(shù)據(jù)發(fā)生了改變
if(FD_ISSET(lfd , &tmp)){
// 有客戶端連接進(jìn)來
struct sockaddr_in caddr;
socklen_t len = sizeof(caddr);
int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
// 添加進(jìn)去
FD_SET(cfd , &rdset);
maxfd = maxfd>cfd?maxfd:cfd+1;
}
for(int i = lfd+1 ; i <= maxfd ; i++){
if(FD_ISSET(i , &tmp)){
// 說明客戶端發(fā)來了數(shù)據(jù)
char buf[1024];
int len = read(i , buf , sizeof(buf));
if(len == -1){
perror("read");
exit(-1);
}
else if(len == 0){
cout<<"client close..."<<endl;
close(i);
FD_CLR(i,&rdset);
}
else{
cout<<"發(fā)來了數(shù)據(jù):"<<buf<<endl;
write(i , buf , strlen(buf)+1);
}
}
}
}
}
close(lfd);
return 0;
}
poll
#include <iostream>
#include <cstdio>
#include <arpa/inet.h>
#include <cstdlib>
#include <string.h>
#include <poll.h>
#include <unistd.h>
using namespace std;
int main(){
// 創(chuàng)建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));
// 監(jiān)聽
listen(lfd , 8);
// 初始化檢測文件描述符數(shù)組
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 系統(tǒng)檢測
int ret = poll(fds , nfds+1 , -1);
if(ret == -1){
perror("poll");
exit(-1);
}
else if(ret == 0){
continue;
}
else{
// 檢測到了文件描述符的數(shù)據(jù)發(fā)生了改變
if(fds[0].revents & POLLIN){
// 有客戶端連接進(jìn)來
struct sockaddr_in caddr;
socklen_t len = sizeof(caddr);
int cfd = accept(lfd , (struct sockaddr*)&caddr , &len);
// 添加進(jìn)去
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){
// 說明客戶端發(fā)來了數(shù)據(jù)
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..."<<endl;
close(fds[i].fd);
fds[i].fd = -1;
}
else{
cout<<"發(fā)來了數(shù)據(jù):"<<buf<<endl;
write(fds[i].fd , buf , strlen(buf)+1);
}
}
}
}
}
close(lfd);
return 0;
}
epoll
內(nèi)核,紅黑樹記錄要檢測的文件描述符,避免了用戶態(tài)到內(nèi)核態(tài)的數(shù)據(jù)拷貝開銷;
內(nèi)核,雙鏈表存放數(shù)據(jù)改變的文件描述符
#include <iostream>
#include <cstdio>
#include <arpa/inet.h>
#include <cstdlib>
#include <string.h>
#include <sys/epoll.h>
#include <unistd.h>
using namespace std;
int main(){
// 創(chuàng)建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));
// 監(jiān)聽
listen(lfd , 8);
// 創(chuàng)建epoll實例
int epfd = epoll_create(100);
// 添加監(jiān)聽文件描述符
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<<ret<<"個發(fā)生了改變"<<endl;
for(int i = 0 ; i<ret ; i++){
int curfd = epevs[i].data.fd;
if(curfd == lfd){
// 監(jiān)聽的文件描述符有客戶端連接
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{
// 有數(shù)據(jù)到達(dá)
char buf[1024];
int len = read(curfd , buf , sizeof(buf));
if(len == -1){
perror("read");
exit(-1);
}
else if(len == 0){
cout<<"client close..."<<endl;
epoll_ctl(epfd , EPOLL_CTL_DEL , curfd , NULL);
close(curfd);
}
else{
cout<<"發(fā)來了數(shù)據(jù):"<<buf<<endl;
write(curfd , buf , strlen(buf)+1);
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
epoll的兩種工作模式
LT模式 - 水平觸發(fā)
默認(rèn)的工作模式,支持block/no-block;,內(nèi)核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的 fd 進(jìn)行IO操作。如果你不作任何操作,內(nèi)核還是會繼續(xù)通知你
#include <iostream>
#include <cstdio>
#include <arpa/inet.h>
#include <cstdlib>
#include <string.h>
#include <sys/epoll.h>
#include <unistd.h>
using namespace std;
int main(){
// 創(chuàng)建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));
// 監(jiān)聽
listen(lfd , 8);
// 創(chuàng)建epoll實例
int epfd = epoll_create(100);
// 添加監(jiān)聽文件描述符
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<<ret<<"個發(fā)生了改變"<<endl;
for(int i = 0 ; i<ret ; i++){
int curfd = epevs[i].data.fd;
if(curfd == lfd){
// 監(jiān)聽的文件描述符有客戶端連接
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{
// 有數(shù)據(jù)到達(dá)
char buf[5];
int len = read(curfd , buf , sizeof(buf));
if(len == -1){
perror("read");
exit(-1);
}
else if(len == 0){
cout<<"client close..."<<endl;
epoll_ctl(epfd , EPOLL_CTL_DEL , curfd , NULL);
close(curfd);
}
else{
cout<<"發(fā)來了數(shù)據(jù):"<<buf<<endl;
write(curfd , buf , strlen(buf)+1);
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
ET模式 - 邊沿觸發(fā)
告訴工作方式,只支持no-block,在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w時,內(nèi)核通過epol告訴你。然后它會假設(shè)你知道文件描述符已經(jīng)就緒,并且不會再為那個文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導(dǎo)致那個文件描述符不再為就緒狀態(tài)了。但是請注意如果一直不對這個 fd 作IO操作,內(nèi)核不會發(fā)送更多的通知 (only once) 。但是緩沖區(qū)中的數(shù)據(jù)不會丟失
ET模式效率比LT模式高,ET模式下必須使用非阻塞套接口,避免由于一個描述符的阻塞讀/阻塞寫操作把處理多個文件描述符的任務(wù)餓死;
要設(shè)置邊沿觸發(fā)
1. 需要在epoll_event中設(shè)置EPOLLET
2.?
UDP通信實現(xiàn) - 無需多進(jìn)程/多線程的并發(fā)實現(xiàn)
// server
#include <iostream>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
int main(){
// 創(chuàng)建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){
// 接收數(shù)據(jù)
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<<s1<<" "<<s2<<endl;
cout<<"rcv data: "<<buf<<endl;
sendto(fd , buf , strlen(buf)+1 , 0 , (struct sockaddr*)&caddr , len);
}
close(fd);
return 0;
}
// client
#include <iostream>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
int main(){
// 創(chuàng)建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);
// 接收數(shù)據(jù)
int num = recvfrom(fd , buf , sizeof(buf) , 0 , NULL , NULL);
cout<<"rcv data: "<<buf<<endl;
sleep(1);
}
close(fd);
return 0;
}
廣播和組播 - 只能使用UDP
廣播 - 向子網(wǎng)中多臺計算機(jī)發(fā)送消息,每個廣播消息都包含一個特殊的IP地址,這個IP中子網(wǎng)內(nèi)主機(jī)標(biāo)志部分的二進(jìn)制全部為1;
1. 只能在局域網(wǎng)中使用
2. 客戶端需要綁定服務(wù)器廣播使用的端口,才能接收到廣播消息
組播 (多播) - 標(biāo)識一組IP接口,是在單播和廣播之間的一種折中方案,多播數(shù)據(jù)包只由對其感興趣的接口接收;
1. 組播可以用于局域網(wǎng)和廣域網(wǎng)
2. 客戶端需要加入多播組才能接收到
本地套接字通信
作用:用于進(jìn)程間的通信;實現(xiàn)流程和網(wǎng)絡(luò)套接字類似,一般采用TCP的通信流程文章來源:http://www.zghlxwxcb.cn/news/detail-704697.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-704697.html
服務(wù)器端
1. 創(chuàng)建監(jiān)聽的套接字
? ? ? int lfd = socket(AF_UNIX/AF_LOCAL , SOCK_STREAM , 0);
2. 監(jiān)聽套接字綁定本地的套接字文件
? ? ? ? struct sockaddr_un addr;
? ? ? ? bind(lfd, addr, len); // 綁定成功后sun_path中的套接字文件會自動生成
3. 監(jiān)聽
4. 等待并接受客戶端請求
5. 通信
6. 關(guān)閉連接
客戶端
1. 創(chuàng)建通信的套接字
2. 綁定本地IP端口
3. 連接服務(wù)器
4. 通信
5. 關(guān)閉連接
// 服務(wù)端
#include <iostream>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/un.h>
using namespace std;
int main(){
unlink("server.sock");
// 創(chuàng)建監(jiān)聽套接字
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);
}
// 監(jiān)聽
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<<"客戶端文件:"<<caddr.sun_path<<endl;
// 通信
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..."<<endl;
break;
}
else{
cout<<"recv data: "<<buf<<endl;
send(cfd , buf , len , 0);
}
}
close(cfd);
close(lfd);
return 0;
}
// 客戶端
#include <iostream>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/un.h>
using namespace std;
int main(){
unlink("client.sock");
// 創(chuàng)建監(jiān)聽套接字
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);
}
// 連接服務(wù)器
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..."<<endl;
break;
}
else{
cout<<"recv data: "<<buf<<endl;
}
sleep(1);
}
close(cfd);
return 0;
}
到了這里,關(guān)于linux并發(fā)服務(wù)器 —— IO多路復(fù)用(八)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!