基于Linux的socket网络编程(select IO多路复用)
一、基本函数
1、socket创建
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol); success:描述符 fail:-1
2、bind:与套接字进行绑定
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
3、listen 监听模式
准备接收连接请求 listen()函数的作用是设置监听上限(同一时刻接收到连接的个数),而不是设置监听。accept()函数才是阻塞监听客户端连接。
int listen(int sockfd, int backlog);
4、connect:客户端发送连接请求
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
5、accept:接受客户端连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
6、send:发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
7、recv:接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
8、fd_set 与select
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
fd_set结合select()判断句柄状态 fd_set是一个长度为64的数组 fd_set set; FD_ZERO(&set); /* 将set清零使集合中不含任何fd*/ FD_SET(fd, &set); /* 将fd加入set集合 / FD_CLR(fd, &set); / 将fd从set集合中清除 / FD_ISSET(fd, &set); / 测试fd是否在set集合中*/ FD_ZERO就是把当前fd_set所有位的数字都置为0。 FD_SET实现了句柄和fd_set的联系,可以把fd(FD_SET(int fd, fd_set *fdset); // 将fd加入set集合),也就是句柄/文件描述符加入到fd_set中。 FD_CLR清除所绑定的联系,这里只清除你传进去的fd和fd_set之间的联系。 需要注意的是,FD_CLR的操作类似于链表节点的删除(后续节点会填补被删除节点)。
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功能:测试指定的fd可读、可写、有异常条件待处理 select()的第一个参数nfds的意思是“最大文件描述符编号加1”, 需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1 (如在readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所有1024位。 设置为FD_SETSIZE。这是<sys/select.h>的一个常量,它指定最大描述符数(通常是1024)。 但是一般情况下,该数过于大,一般的程序也就是3~10个描述符。所以一般情况下,选择手动指定。 readset来检查可读性的一组文件描述字。 writeset用来检查可写性的一组文件描述字。 exceptset用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内) timeout=NULL(阻塞:直到有一个fd位被置为1函数才返回) timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回) timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回) 稍后会在代码实例中说明使用方法。
二、socket编程流程
TCP socket编程基本流程: 客户端:socket----------------------connect----send------recv----close 服务器:socket—bind—listen-----accept-----recv-----send—close 服务器需要先运行 进行socket、bind、listen、accept后,服务器阻塞,等待客户端连接;客户端后运行,进行socket、connect流程以连接服务器。
三、代码实例
服务器端样例:支持多个同时连接 但不支持同时通信(没有子进程/子线程) creatlink连接子函数:
#include "../include/server.h"
int creatlink(const char *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket error");
return -1;
}
printf("sock ok\n");
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[2]));
seraddr.sin_addr.s_addr = inet_addr(argv[1]);
int len = sizeof(seraddr);
int ret = bind(sockfd,(struct sockaddr *)&seraddr,len);
if(ret < 0)
{
perror("bind error");
exit(-1);
}
printf("bind ok\n");
ret = listen(sockfd,5);
if(ret < 0)
{
perror("listen error");
exit(-1);
}
printf("listen...\n");
return sockfd;
}
服务端主函数
#include "../include/server.h"
int main(int argc,const char *argv[])
{
if(argc != 3)
{
printf("CMDLINE NOT EQUAL THREE!\n");
return -1;
}
int sockfd = creatlink(argv);
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
fd_set rest,allset;
FD_ZERO(&rest);
FD_ZERO(&allset);
FD_SET(sockfd,&allset);
int maxfd = sockfd;
while(1)
{
rest = allset;
if(select(maxfd+1,&rest,NULL,NULL,NULL) < 0)
{
printf("select error\n");
exit(-1);
}
if(FD_ISSET(sockfd,&rest))
{
int confd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);
if(confd < 0)
{
perror("accept error");
exit(-1);
}
printf("accept ok\n");
FD_SET(confd,&allset);
if(confd > maxfd)
maxfd = confd;
}
else
{
for(int i = 0;i < maxfd+1;i++)
{
if(FD_ISSET(i,&rest))
{
int ret = srecv(i);
if(ret == 0)
{
printf("closed client\n");
exit(-1);
}
}
}
}
}
}
将监听产生的listenfd加入fd_set集合中,使用select函数判断有无可读事件产生,可读事件有两种情况,第一种是有新的客户端连接,第二种是文件描述符有数据可读,即客户端发来了消息,当可读事件产生时,rest集合中会只剩下产生可读事件的文件描述符/监听套接字;此时使用FD_ISSET判断监听套接字是否在rest集合中,如果是,即新客户端连接,使用accept直接接收并建立连接(此时已经完成三次握手,accept不阻塞),然后把accept产生的connect fd加入到allset集合中即可;若不是,即客户端传送了数据,调用recv函数即可。
客户端样例: creatlink子函数 在主函数中直接调用即可
int creatlink(const char *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket error");
exit(-1);
}
printf("socket ok\n");
struct sockaddr_in servaddr;
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2]));
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
int ret = connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
if(ret < 0)
{
perror("connect error");
exit(-1);
}
printf("connect ok\n");
return sockfd;
}
|