利用socket搭建一个多客户端/服务器的框架

    在设计分布式系统或者涉及到及时通信的相关内容时,我们常常需要利用socket(套接字)来进行网络通信及文件传输,如果只是一个客户端跟一个服务器端进行通信,那么这将是一件很好办的事情,但是实际情况往往是有多个客户端需要跟服务器端进行通信,那么在这种情况下我们如何实现呢?

    我们先从原理出发:客户端和服务器端一旦建立连接,套接字连接的行为就类似于打开的底层文件描述符,而且在很多方面类似于双向管道。当考虑到多个客户同时连接一个服务器的时候,我们可以看到,服务器程序在接受来自客户的一个新的连接的时候,会创建出一个新的套接字,而原先的监听套接字将被保留以继续监听以后的连接。如果服务器不能立刻接受后来的连接,他们将被放到队列中以等待处理。

    原先的套接字仍然可用并且套接字的行为就像文件描述符,这一事实给我们提供了一种同时服务多个客户的方法,如果服务器调用fork()为自己创建第二份副本,打开的套接字就将被新的子进程所继承。新的子进程可以和连接的客户进行通信,而服务器进程可以继续接受以后的客户连接,这是我们所说的第一种方式:创建子进程。这里要注意的是当我们创建好子进程之后,我们并不等待它们完成,所以必须安排服务器忽略SIGCHLD信号以避免出现僵尸进程。

    这里我们着重要描述的是第二种方式:通过select系统调用来同时处理多个客户从而避免依赖于子进程。服务器可以让select系统调用同时检查监听套接字和客户的连接套接字。一旦select调用指示有活动发生,就可以用FD_ISSET来遍历所有可能的文件描述符,以检查是那个上面有活动发生。

    如果是监听套接字可读,这说明正有一个客户试图建立连接,此时就可以调用accept而不用担心发生阻塞的可能。如果是某个客户描述符准备好了,这说明该描述符上有一个客户请求需要我们读取和处理。如果读操作返回零字节,这表示有一个客户进程已经结束,你可以关闭该套接字并把它从描述符集合中删除。

    下面给出完整的可运行C程序示例,我在中间做了英文注释:


服务器端代码:server.cpp

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>


int main()
{
	int server_socketfd, client_socketfd;
	int server_len, client_len;
	struct sockaddr_in server_address;
	struct sockaddr_in client_address;
	int result;
	fd_set readfds, testfds;

	//create a socket for server
	server_socketfd = socket(AF_INET, SOCK_STREAM, 0);

	server_address.sin_family = AF_INET;
	server_address.sin_addr.s_addr = htonl(INADDR_ANY);
	server_address.sin_port = htons(atoi("9092"));
	server_len = sizeof(server_address);

	bind(server_socketfd, (struct sockaddr *)&server_address, server_len);

	//create a connection queue, init readfds to process the input of server_sockfd
	listen(server_socketfd, 5);

	FD_ZERO(&readfds);
	FD_SET(server_socketfd,&readfds);

	//wait for the ask of clients
	while(1)
	{
		char ch;
		int fd;
		int nread;

		testfds = readfds;

		printf("server waiting...\n");
		result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0);

		if(result < 1)
		{
			perror("server");
			exit(1);
		}

		//once you know the activity happen,use FD_ISSET to inspect every fd to find which one did the activity happen
		for(fd = 0; fd < FD_SETSIZE; fd++)
		{
			if(FD_ISSET(fd, &testfds))
			{
				//if the activity is happened in server_socketfd, it means it is a new connection
				if(fd == server_socketfd){
					client_len = sizeof(client_address);
					client_socketfd = accept(server_socketfd, (struct sockaddr *)&client_address, &client_len);
					FD_SET(client_socketfd, &readfds);
					printf("adding client on fd %d\n", client_socketfd);
				}
				//if the activity is not happened in server_socketfd, it means the client has left
				else
				{
					ioctl(fd, FIONREAD, &nread);

					if(nread == 0)
					{
						close(fd);
						FD_CLR(fd, &readfds);
						printf("removeing client on fd %d\n", fd);
					}
					else
					{
						read(fd, &ch, 1);
						sleep(5);
						printf("serving client on fd %d\n", fd);
						ch++;
						write(fd, &ch, 1);
					}
				}
			}
		}
	}
}

客户端:client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>

int main()
{
	char ch = 'A';
		int sockfd;
		struct sockaddr_in skaddr;

		if ((sockfd =socket(AF_INET, SOCK_STREAM, 0)) <0 ) {
			printf("Problem creating socket \n");
			return -1;
		}

		skaddr.sin_family = AF_INET;

		inet_aton("127.0.0.1", &skaddr.sin_addr);
		skaddr.sin_port = htons (atoi("9092"));//port number

		if (connect (sockfd, (struct sockaddr *) &skaddr, sizeof(skaddr))<0) {
			printf("Problem connecting socket \n");
			return -1;
		}

	write(sockfd, &ch, 1);
	read(sockfd, &ch, 1);
	printf("char from server = %c\n", ch);
	close(sockfd);
	exit(0);
}

在linux下使用gcc编译成可执行文件后,运行./server启动服务端

然后使用./client & ./client & ./client 来模拟三个客户端访问时的操作情况。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值