socket通信与select函数

int main(int argc, const char *argv[])
{
int sockfd, acceptfd;
struct sockaddr_in serveraddr, clientaddr;
socklen_t addrlen = sizeof(serveraddr);
char buf[N] = {};
ssize_t bytes;


//初始化结构体 memset bzero
bzero(&serveraddr, addrlen);
bzero(&clientaddr, addrlen);


if(argc < 3)
{
fprintf(stderr, "Usage: %s ip port\n", argv[0]);
exit(1);
}


//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
errlog("fail to socket");
}
printf("sockfd = %d\n", sockfd);


//第二步:填充服务器网络信息结构体
//./a.out 127.0.0.1 9999
//inet_addr:将点分十进制ip地址转化为网络字节序的整形数据
//htons:将主机字节序转化为网络字节序
//atoi:将数字形字符串转化为整形数据
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));


//第三步:将套接字与服务器网络信息结构体绑定
if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0)
{
errlog("fail to bind");
}


//第四步:将套接字设置为被动监听模式
if(listen(sockfd, 5) < 0)
{
errlog("fail to listen");
}


//使用select函数实现tcp并发服务器

fd_set readfds, tempfds;
int maxfd, ret, i;

//第一步:清空集合
FD_ZERO(&readfds);


//第二步:将需要的文件描述符添加到集合里面
FD_SET(sockfd, &readfds);
现在是把它添加进去了,但是select函数可能会把它拿出来

因为accept 完成一次接收之后,sockfd对应的文件缓冲区中就没有内容了,

        被accept函数一次性拿走了

         且accept完成接收之后,就陷入阻塞,等待下次与新的客户端的连接

        所以,它自然从时间就绪表中出来了,所以,这个sockfd不是一直都在时间就绪表中的。

maxfd = sockfd;
//该进程中最大的文件描述符
//主要目的就是让服务器跟多个客户端实现连接,并且与每个客户端都可以实现连接


while(1)

{

tempfds = readfds;

//开始更新temfds中的内容,刚才是把4放进了readfds,现在tempfds里面有3(sockfd)4(acceptfds)
//随着开启的客户端越来越多,每一个id都存进temp里面,虽然select操作之后,剩下的只有准备就绪
//的文件描述符,但是在这里是对tempfds进行操作的,readfds里面中的fd只会越来越多
//最后可以打印看一下readfds中包含的文件描述符


//第三步:阻塞等待文件描述符准备就绪
if((ret = select(maxfd + 1, &tempfds, NULL, NULL, NULL)) < 0)
{


//如果是此时开启一个客户端,那么就是sockfd指向的空间中存入了客户端的网络信息结构体
//此时,sockfd进入时间就绪表中
注意:accept 函数将这个网络信息结构体的信息取走之后
1.accept函数重新陷入了阻塞状态,因为现在的信息被完全取走了,所以accept重新陷入了阻塞状态

直到下一个客户端开启,accpet才跳出阻塞状态,不是说跟一个客户端一直接收着,接受完,accept

                        就继续陷入阻塞中了。

2.此时服务器已经跟客户端连接上了,虽然时间就绪表里的sockfd中没有这个客户端的网络信息了
但是连接上了,就不会断开,下次使用该客户端时可以直接进行通信。
3.如果该客户端中发来了数据,recv 函数就跳出阻塞,直接跟该客户端进行通信

时间就绪表中可能出现的几种情况
1.该服务器已经开启了几个客户端(3,4,5),此时再开启一个客户端6
因为在服务器开启客户端3的时候,在刚刚开启的那一刻,sockfd进入了时间就绪表中,马上开始执行
accept函数,然后 sockfd 中的信息被取走了,此时accept陷入了阻塞状态,且sockfd已经不在时间就绪表中
所以此时,时间就绪表中只有一个sockfd
2.该服务器想利用3发送数据,此时数据进入acceptid中,recv函数不再阻塞, acceptid进入时间就绪表中
3.如果可以同时做到开启一个客户端,和同时利用已有客户端发送数据,那么时间就绪表会出现sockfd和                                 accepfd
4.如果可以同时做到利用两个客户端发送数据,那么可以做到两个accept进入时间就绪表中
5.一般不太可能做到同时,所以,时间就绪表中只出现一个变量,开启客户端就i是sockfd(这个值是一开始指                           定好的不变),
发送数据就是客户端fd

errlog("fail to select");
}


//printf("ret = %d\n", ret);
//由于select函数运行结束后,集合里面只剩下准备就绪的,所以只要判断到底是那个文件描述符还在集合里面即可
for(i = 0; i < maxfd + 1; i++)
{
//需要拿出当前所有的文件描述符,因为上一步已经把文件描述符移除了
if(FD_ISSET(i, &tempfds) == 1)
{

//这时在判断文件描述符4也就是新连接上的acceptid是否在 tempfds里面
//上次的readfds里面只有sockfd3,没有这个4,所以这一步不执行
//跳回到while1处
//先判断一下这个文件描述符是否在时间就绪表里
//因为比如0,1,2这种就不在时间就绪表里,比较这些没有意义
if(i == sockfd)
{
if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) < 0)
{

//如果是此时已经有了一个连接,但是没有新连接,走到这里
//就会陷入阻塞状态
//如果此时再来一个客户端,accept照样将sockfd中的内容也就是客户端的网络信息结构体
//给拿走,所以,下次,accept函数又陷入了阻塞模式。
//所以,当执行完accept时,sockfd就已经不在时间就绪表中了
//
//当开启了另一个客户端时,首先是,sockfd文件描述符指向的文件缓冲区中写入了新的内容
//然后第一步,应该是sockfd进入时间就绪表中
//然后,如果此时这个时间就绪表中就只有一个sockfd,这个sockfd与刚刚的sockfd里面存放的内容不一样
//然后就可以执行这个accept过程了
//如果在这个过程中,其他客户端要发送数据,也是可以的。
//因为一旦比如accept5中有内容了
errlog("fail to accept"); 
}


printf("ip: %s, port: %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
printf("acceptfd = %d\n", acceptfd);


//将需要执行io操作的文件描述符添加到集合里面
FD_SET(acceptfd, &readfds);


//需要获取到最大的文件描述符
maxfd = acceptfd > maxfd ? acceptfd : maxfd; 

//因为每来一个新的accept都会跟这个最大的fd比较,因为现在不能保证我们
//新创建的accept比之前的accept要大,所以,这里需要拿到最大的 fd
}
else 
{
//如果只有4就绪了,那么此时执行下面的代码
//执行接收和发送的代码
if((bytes = recv(i, buf, N, 0)) < 0)
{
//此时既然是上次开启的客户端要给我发送消息了
//说明之前的连接已经成功了,如果是accept==5这个客户端里有消息了
//此时这个5就进入了时间就绪表中,此时不用在考虑,服务器跟客户端
//中间是不是断了,这里会不会找不到客户端,既然,5给服务器发送了消息
//服务器接受了5,那么这个客户端5与服务器就是一直连接着
//5进入时间就绪表之后,就可以跟服务器发送数据
//考虑5进入时间就绪表的同时,时间就绪表中还有没有其他的fd
//如果此时没有同时开启一个客户端,那么sockfd文件缓冲区中没有内容
//sockfd不会进入时间就绪表,之前开启的其他客户端的信息也已经被清除
//此时accpet函数处于阻塞状态
//如果此时有其他的客户端也要发送数据,如果是可以做得到同时的话
//那么,6也会进入到时间就绪表中
//recv函数就是相当于read函数,也就是说accept指向的文件缓冲区中必须有内容,才能向服务器端发送
//服务器端才有这个数据可读,所以accpet文件描述符指向的文件缓冲区中存放的是客户端发送的信息
//得有信息发送来,这里才不会陷入阻塞状态
errlog("fail to recv");
}

阅读更多
上一篇fork函数
下一篇函数地址传参
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭