基于linux poll模型的tcp服务器------一个服务器如何与多个客户端进行通信?

972 篇文章 329 订阅
148 篇文章 34 订阅

        在之前的博文中, 我们一起玩了Windows下的select函数, 实现了一个服务器与多个客户端进行通信。 如果一直到linux, 那几乎是大同小异的。 现在, 我们不说select, 而来接续说说linux poll.

        在前面的博文中, 我们也介绍了linux poll检测键盘终端输入, 主要是想在网络编程之前, 对poll的用法有个基本的了解。现在, 来玩一下基于linux poll模型的tcp服务器------一个服务器与多个客户端进行通信。

       服务端代码如下:

 

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h> 
#include <sys/poll.h>  
#define BACKLOG 100  

#if 0
struct pollfd
{
  	int fd;          //文件描述符
  	short events;    //请求的事件
  	short revents;   //返回的事件
  };
#endif


int main()  
{
    int iListenSock = socket(AF_INET, SOCK_STREAM, 0);
    int arrayConnSock[BACKLOG] = {0}; // 这个初始化并不好,但其实无大碍
    sockaddr_in addr;  
    memset(&addr, 0, sizeof(addr));  
    inet_aton("0.0.0.0", &addr.sin_addr);  
    addr.sin_family = AF_INET;  
    addr.sin_port = htons(8888);  

	int iOpt = 1;
    setsockopt(iListenSock, SOL_SOCKET, SO_REUSEADDR, &iOpt, sizeof(iOpt));  // 标配
    bind(iListenSock, (sockaddr*)&addr, sizeof(addr));  
    listen(iListenSock, 100);  
  
	int flag = -1;
	pollfd fds[BACKLOG + 1];        // BACKLOG个通信socket和1个监听socket
	memset(fds, flag, sizeof(fds)); // 设置一个标志
	fds[0].fd = iListenSock;        // 监听socket
	fds[0].events = POLLIN;

	int ndfs = 1;
	int timeoutMS = -1; // 永不超时
	int i = 0;
	for(int i = 1; i <= BACKLOG; i++)
	{  
	    int iRet = poll(fds, ndfs, timeoutMS);  
	    if (iRet < 0)
		{
			printf("poll error, iRet %d\n", iRet);
	        continue;  
	    }

		if (iRet < 0)
		{
			printf("poll error, iRet %d\n", iRet);
	        continue;  
	    }

	    if (fds[0].revents & POLLIN) // fds[0]对应iListenSock, 此处表明检测到有新客户端连接过来
		{
            arrayConnSock[i - 1] = accept(iListenSock, NULL, NULL); // arrayConnSock是通信socket
            fds[i].fd = arrayConnSock[i - 1];  
            fds[i].events = POLLIN;
			ndfs++;  
            printf("new client came, local fd is [%d]\n", arrayConnSock[i - 1]);  
	    }  

	    for (int j = 0; j < BACKLOG; j++)
		{
			char szBuf[1024] = {0}; 
	        if (arrayConnSock[j] > 0 && (fds[j + 1].revents & POLLIN)) // 此处检测的是已经链接的socket的读事件
			{  
	            int recvLen = recv(arrayConnSock[j], szBuf, sizeof(szBuf) - 1, 0);  
	            if (recvLen > 0)
				{  
	                printf("recv data [%s] from local fd [%d] \n", szBuf, arrayConnSock[j]);  
	            }
				else if(0 == recvLen)
	            {
	                close(arrayConnSock[j]);  
	                memset(&fds[j + 1], flag, sizeof(pollfd));  
	                printf("connection closed fd [%d]\n", arrayConnSock[j]);  
	                arrayConnSock[j] = 0; 
	            }
				else
				{
					close(arrayConnSock[j]);  
	                memset(&fds[j + 1], flag, sizeof(pollfd));  
					printf("recv error, recvLen is %d", recvLen);
					arrayConnSock[j] = 0;
				}
	        }  
	    }  
	
	}
  
    close(iListenSock);  
    return 0;  
}

 

 

        客户端代码如下:

 

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h>

int main()
{
    int sockClient = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in addrSrv;
    addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(8888);
    connect(sockClient, ( const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));

	char szSendBuf[100] = "this is me";	
	while(1)
	{
		send(sockClient, szSendBuf, strlen(szSendBuf) + 1, 0);   
		scanf("%s", szSendBuf);
	}

    close(sockClient);
	return 0;
}

      makefile代码如下:

 

 

all: server client

server: server.o
	g++ -o server  server.o

client: client.o
	g++ -o client  client.o

server.o: server.cpp
	g++ -c server.cpp
	
client.o:client.cpp
	g++ -c client.cpp

clean:
	rm -f server client *.o

         

 

      编译链接后, 先启动服务端, 然后分别在同一linux机器的三个窗口上启动三个客户端, 此时, 服务端的结果为:

 

xxxxxx:~/network> ./server
new client came, local fd is [4]
recv data [this is me] from local fd [4] 
new client came, local fd is [5]
recv data [this is me] from local fd [5] 
new client came, local fd is [6]
recv data [this is me] from local fd [6] 


        好了, 实现了简单的一个服务器与多个客户端进行通信。 随后, 这三个客户端可以继续与服务端进行同行, 同时, 服务端也允许更多的新客户端来连接。 当然, 客户端也可以选择断开连接(ctrl c). 多的不说, 玩一下程序就明白了, 上述poll的用法值得好好体会, 可以看到, 跟linux几乎没有太大的差别, 上述程序如果转成select模型,那也是易如反掌的。

 

       最后, 思考一个有意思的小问题, 为什么上述的fd是4, 5, 6 ?  

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值