您的位置:首页 > 其它

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

2013-05-15 10:06 399 查看
在设计分布式系统或者涉及到及时通信的相关内容时,我们常常需要利用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 来模拟三个客户端访问时的操作情况。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐