在学习主要代码之前先了解下TCP通信两端使用socket函数建立连接的流程:
1、客户端和服务器均使用socket函数各自创建一个套接字
2、服务器用bind绑定IP地址和端口号
3、服务器用listen开启监听模式
3、服务器用accept阻塞自己,等待客户端的连接请求
4、客户端通过connect发出连接请求
5、服务器收到连接请求后,建立连接,之后双方可用recv(接收)和send(发送)进行通信
通信结束后双方通过close()关闭套接字
int socket(int domain,int type,int protocol);
参数:1、指明所用协议族 常用的有AF_INET AF_INET6 AF_LOCAL AF_ROUTE
在TCP连接中一般使用AF_INET表示用ipv4和16位端口号
2、套接字类型。SOCK_STREAM:TCP SOCK_DGRAM:UDP SOCK_RAW:允许对底层访问
3、协议类别。可以为0让系统自动选择
返回值若小于0说明发生错误,否则返回一个值表示创建套接字,类似每个人的身份证号一样,
之后都用套接字描述符来形容这个值
int bind(int sockfd,const struct sockaddr*,socklen_t addrlen)
参数:1、套接字描述符
2、协议地址,用于指定协议族、端口和IP地址
3、第二个参数的长度
发生错误返回-1,绑定成功返回0; 该函数用于将某个套接字与协议地址关联起来
int listen(int sockfd,int backlog)
参数:1、套接字描述符
2、接受请求的最大长度
仅被服务器调用,将套接字变为被动连接,将套接字变为监听套接字,成功返回0,失败返回-1
int accept(int sockfd, struct sockaddr* addr,socklen_t *addrlen);
参数:1、套接字描述符
2、用于存放客户端的协议地址信息
3、第二个参数的长度,注意,这里不要直接用sizeof,会出错。要定义一个socklen_t的变量。
服务器阻塞式等待客户端请求,成功连接后,客户端相关信息会被存储到第二个参数里。
失败返回-1 成功返回客户端的套接字描述符
int connect(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
参数:1、套接字描述符
2、服务器的协议地址
3、第二个参数的长度
客户端发起连接请求并连接 成功返回0 失败返回-1
int send(int sockfd,const void* msg,int len,int flags)
参数:1、发送端套接字描述符
2、发送数据缓冲区
3、第二个参数的长度
4、0个或多个标志的组合体,可直接设置为0 或用“|”连在一起。
MSG_DONTWAIT:非阻塞操作
MSG_DONTROUTE:告知网际层协议,目的主机在网络,不需要查找路由表
MSG_NOSIGNAL:动作不愿被SIGPIPE信号中断
MSG_OOB:表示接受到需要优先处理的数据
用于通信间发送数据
int recv(int sockfd,void buf,int len,unsigned int flags)
参数:1、接受端套接字描述符
2、接受缓冲区基地址
3、以字节计算接受换乘区长度
4、0或多个标志的组合体
MSG_DONTWAIT:设置非阻塞操作
MSG_PEEK:接收数据后,在接收队列保留原有数据
MSG_WAITALL:阻塞操作
MSG_OOB:表示接受到需要优先处理的数据
用于通信接收数据
在前面使用accept()会将服务器阻塞,直到有客户端的连接,这样特别浪费服务器的资源。
接下来介绍使用select()和epoll()实现非阻塞方式监听套接字描述符的变化情况
int select(int maxfd,fd_set *read_set,fd *write_set,fd_set *except_set,struct timeval *timeout)
调用该函数可以找到事件发生的个数,2-4参数分别为读事件、写事件、错误异常事件。
该函数会查询所有套接字描述小于maxfd的事件,对于不想监听的事件传NULL即可
最后一个参数表示阻塞时间,若为NULL则阻塞程序,直到有事件到来,否则阻塞timeout时间等待事件到来
函数返回事件的个数
对于fd_set结构体
int FD_ZERO(fd_set *fdset);//将fdset置0
int FD_CLR(int fd,fd_set *fdset);//在fdset清除fd,fdset作为第二个参数传入select时将不再监听fd的读事件
int FD_SET(int fd,fd_set *fdset);//将fd添加到fdset中去
int FD_ISSET(int fd,fd_set *fdset);//检测fd是否引起fdset变化,即fd发生了事件
对于fdset="0111",表示监听fd=0 1 2,当调用select且fd=0 2有事件发生后
fdset="0101"此时用FD_ISSET即可判断fd是否有事件发生
在服务器中可以通过select采用轮询的方式,即可实现非阻塞式等待连接请求,
显然这种方式会随着轮询的套接字增加使得效率下降
因而可以使用epoll系统调用函数,它的效率不会随着监听套接字数目的增长而降低。
int epoll_create(int size)
参数size表示要监听的套接字的数目,若执行成功返回epoll句柄
这个句柄类似人的身份证号,用于标识不同的epoll句柄
int epoll_ctl(int epfd,int op,int fd, struct epoll_event *event);
参数:1、epoll句柄
2、表示执行的动作, EPOLL_CTL_ADD:注册新的套接字描述符
EPOLL_CTL_MOD:修改已有的套接字描述符(一般为修改监听事件)
EPOLL_CTL_DEL:删除已有的套接字描述符
3、套接字描述符
4、要监听的事件: EPOLLIN:读事件
EPOLLOUT:写事件
EPOLLPRI:有紧急数据可读
EPOLLET:边缘触发
EPOLLONESHOT: 只监听一次事件,监听完一次后,要重新添加
多个事件通过” | “连接
成功返回0 失败返回-1
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
参数:1、epoll句柄
2、事件集合
4、最大可处理事件
5、超时时间,时间为0立即返回
执行成功返回需要处理的事件数目,返回0表示超时
样例:
客户端
1 #include<unistd.h> 2 #include<cstdio> 3 #include<cstring> 4 #include<stdlib.h> 5 #include<sys/socket.h> 6 #include<arpa/inet.h> 7 #include<netinet/in.h> 8 #include<string.h> 9 using namespace std; 10 int main() 11 { 12 int sockfd; 13 struct sockaddr_in sock_addr; 14 int i,j,op; 15 sockfd=socket(AF_INET,SOCK_STREAM,0); 16 if(sockfd<0) 17 { 18 printf("套接字创建失败\n"); 19 return -1; 20 } 21 bzero(&sock_addr,sizeof(sock_addr)); 22 sock_addr.sin_family=AF_INET; 23 sock_addr.sin_port=htons(8000); 24 25 op=connect(sockfd,(struct sockaddr*)&sock_addr,sizeof(sock_addr)); 26 if(op<0) 27 { 28 printf("connect error\n"); 29 close(sockfd); 30 return -1; 31 } 32 33 while(1) 34 { 35 char buf[1010]=""; 36 37 fgets(buf,sizeof(buf),stdin); 38 send(sockfd,buf,strlen(buf),0); 39 //printf("send:%s\n",buf); 40 if((op=recv(sockfd,buf,strlen(buf),0))>0) 41 { 42 buf[op]='\0'; 43 printf("%s\n",buf); 44 } 45 } 46 close(sockfd); 47 return 0; 48 }
服务器(select):
1 #include<iostream> 2 #include<sys/socket.h> 3 #include<arpa/inet.h> 4 #include<netinet/in.h> 5 #include<unistd.h> 6 #include<cstdlib> 7 #include<cstdio> 8 #include<string.h> 9 #include<pthread.h> 10 #include<sys/select.h> 11 #include<algorithm> 12 #include<sys/time.h> 13 #include<sys/types.h> 14 using namespace std; 15 16 int main() 17 { 18 int sockfd,sfd; 19 struct sockaddr_in sock_addr; 20 int i,j,op; 21 int maxfd,maxi,n; 22 23 fd_set rset,allset; 24 int client[1024]; 25 char buf[100]; 26 27 sockfd=socket(AF_INET,SOCK_STREAM,0); 28 if(sockfd<0) 29 { 30 printf("套接字创建失败\n"); 31 return -1; 32 } 33 34 bzero(&sock_addr,sizeof(sock_addr)); 35 sock_addr.sin_family=AF_INET; 36 sock_addr.sin_port=htons(8000); 37 sock_addr.sin_addr.s_addr=htons(INADDR_ANY); 38 39 op=bind(sockfd,(struct sockaddr*)&sock_addr,sizeof(sock_addr)); 40 if(op<0) 41 { 42 printf("套接字绑定失败\n"); 43 close(sockfd); 44 return -1; 45 } 46 47 op=listen(sockfd,4);//4表示套接字最大维护连接数目 48 if(op<0) 49 { 50 printf("listen error\n"); 51 close(sockfd); 52 return -1; 53 } 54 int clientfd; 55 56 struct sockaddr_in client_addr; 57 bzero(&client_addr,sizeof(client_addr)); 58 socklen_t len=sizeof(struct sockaddr); 59 60 maxfd=sockfd; 61 maxi=-1; 62 for(i=0;i<1024;i++) 63 client[i]=-1; 64 65 FD_ZERO(&allset); 66 FD_SET(sockfd,&allset);//构造监听套接字文件描述符 67 68 while(1) 69 { 70 //sleep(5); 71 rset=allset; 72 op=select(maxfd+1,&rset,NULL,NULL,NULL); 73 if(op<0) 74 { 75 cout<<"select error\n"; 76 return -1; 77 } 78 79 if(FD_ISSET(sockfd,&rset)) 80 { 81 printf("111111\n"); 82 clientfd=accept(sockfd,(struct sockaddr*)&client_addr,&len); 83 printf("222222\n"); 84 85 if(clientfd<0) 86 { 87 printf("accept error\n"); 88 return -1; 89 } 90 91 char clientip[32]=""; 92 inet_ntop(AF_INET,&client_addr.sin_addr,clientip,16); 93 printf("ip:%s,port:%d\n",clientip,ntohs(client_addr.sin_port)); 94 95 for(i=0;i<1024;i++) 96 { 97 if(client[i]<0) 98 { 99 client[i]=clientfd; 100 break; 101 } 102 else cout<<clientfd<<endl; 103 } 104 105 if(i==1024) 106 { 107 cout<<"to many clients\n"; 108 return -1; 109 } 110 111 FD_SET(clientfd,&allset); 112 if(clientfd>maxfd) 113 maxfd=clientfd; 114 115 maxi=max(maxi,i); 116 117 if(--op==0) 118 continue; 119 } 120 //sleep(5); 121 for(i=0;i<=maxi;i++) 122 { 123 sfd=client[i]; 124 if(sfd<0) 125 continue; 126 if(FD_ISSET(sfd,&rset)) 127 { 128 cout<<op<<endl; 129 if((n=read(sfd,buf,90))==0) 130 { 131 cout<<"close"<<sfd<<endl; 132 close(sfd); 133 FD_CLR(sfd,&allset); 134 client[i]=-1; 135 } 136 else if(n>0) 137 { 138 buf[n]='\0'; 139 cout<<"recv data:"<<buf<<endl; 140 bzero(&buf,sizeof(buf)); 141 cout<<"send:"; 142 fgets(buf,sizeof(buf),stdin); 143 send(sfd,buf,strlen(buf),0); 144 } 145 if(--op==0) 146 break; 147 } 148 } 149 //cout<<"oooooooooooooooooooo"<<endl; 150 } 151 cout<<"wryyyyyyyyyyyyyyyyy"<<endl; 152 close(clientfd); 153 close(sockfd); 154 return 0; 155 }
服务器(epoll):
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<iostream> 5 #include<sys/socket.h> 6 #include<sys/types.h> 7 #include<netinet/in.h> 8 #include<sys/epoll.h> 9 #include<fcntl.h> 10 #include<errno.h> 11 #include<string.h> 12 #include<arpa/inet.h> 13 14 #define MAXLINE 5 15 #define LISTENQ 20 16 using namespace std; 17 int main(int argc,char **argv) 18 { 19 int i,j; 20 int maxi,listenfd,connfd,sockfd,epfd,nfds,portnumber=8000; 21 ssize_t n; 22 char line[5]; 23 socklen_t clilen; 24 char buf[512]; 25 26 struct epoll_event ev,events[20]; 27 epfd=epoll_create(256); 28 29 struct sockaddr_in clientaddr; 30 struct sockaddr_in serveraddr; 31 32 listenfd=socket(AF_INET,SOCK_STREAM,0); 33 if(listenfd<0) 34 { 35 cout<<"socket error"<<endl; 36 return -1; 37 } 38 39 ev.data.fd=listenfd; 40 ev.events=EPOLLIN|EPOLLET; 41 42 epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); 43 bzero(&serveraddr,sizeof(serveraddr)); 44 serveraddr.sin_family=AF_INET; 45 serveraddr.sin_port=htons(portnumber); 46 serveraddr.sin_addr.s_addr=htons(INADDR_ANY); 47 48 int op=bind(listenfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)); 49 if(op<0) 50 { 51 cout<<"bind error"<<endl; 52 return -1; 53 } 54 op=listen(listenfd,20); 55 if(op<0) 56 { 57 cout<<"listen error"<<endl; 58 return -1; 59 } 60 61 maxi=0; 62 while(1) 63 { 64 nfds=epoll_wait(epfd,events,20,500); 65 66 for(i=0;i<nfds;i++) 67 { 68 if(events[i].data.fd==listenfd) 69 { 70 clilen=sizeof(sockaddr); 71 connfd=accept(listenfd,(struct sockaddr*)&clientaddr,&clilen); 72 if(connfd<0) 73 { 74 cout<<"accept error"<<endl; 75 return -1; 76 } 77 78 char *str=inet_ntoa(clientaddr.sin_addr); 79 int port_=ntohs(clientaddr.sin_port); 80 cout<<"accept a connection from "<<port_<<endl; 81 82 ev.data.fd=connfd; 83 ev.events=EPOLLIN|EPOLLET; 84 epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); 85 } 86 else if(events[i].events&EPOLLIN) 87 { 88 cout<<"EPOLLIN"<<endl; 89 sockfd=events[i].data.fd; 90 if(sockfd<0) 91 continue; 92 n=read(sockfd,line,500); 93 if(n<0) 94 { 95 if(errno==ECONNRESET) 96 { 97 close(sockfd); 98 events[i].data.fd=-1; 99 } 100 else cout<<"readline"<<endl; 101 } 102 else if(n==0) 103 { 104 cout<<"close!"<<endl; 105 close(sockfd); 106 events[i].data.fd=-1; 107 continue; 108 } 109 110 cout<<"recv:"<<line<<endl; 111 bzero(&line,sizeof(line)); 112 ev.data.fd=sockfd; 113 ev.events=EPOLLOUT|EPOLLET; 114 115 epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); 116 } 117 else if(events[i].events&EPOLLOUT) 118 { 119 sockfd=events[i].data.fd; 120 cout<<"send:"; 121 fgets(buf,sizeof(buf),stdin); 122 buf[strlen(buf)-1]=0; 123 send(sockfd,buf,strlen(buf),0); 124 ev.data.fd=sockfd; 125 ev.events=EPOLLIN|EPOLLET; 126 epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); 127 } 128 } 129 } 130 return 0; 131 }
参考书籍——《Linux网络编程》
浙公网安备 33010602011771号