您的位置:首页 > 运维架构 > Linux

[ Linux ][ C++ ] 多用户连接 c/s 模型

2017-09-17 18:28 176 查看
看了 socket 编程基本知识之后,一般简单的例子是创建一个客户端一个服务器端,用来接收回复消息。但一般都是一对一的 C/S 模型,那么想要多个客户端去连接,该怎么做呢?这个时候就需要用到多线程以及 select I/O 复用机制了。

先说一下思路:客户端不必关注,和简单的一对一相同, socket、connect、send、recv 即可;在服务器端,socket 创建套接字, bind 绑定服务器 address,listen 创建监听队列,规定监听队列大小,创建三个线程:

线程1 循环阻塞地执行 accept ,接收到一个客户端连接请求后,将执行返回的客户端套接字存储在一个全局变量里,这个全局变量是一种合适的数据结构,能够存储与删除数据,循环的意义在于可以一直处于监听是否有客户端握手请求的状态;

线程2 循环执行 select 去查看是否有已连接的客户端套接字可读,可读代表该客户端有消息发送到服务器端,这里 select 中检测的 fd_set 是由全局变量中的客户端套接字数据结构组成的,这样的话有客户端连接上的话,就会被加入检测队列;

线程3 循环阻塞读取 stdin 是否有数据输入,用于向所有客户端发送数据;

存储客户端套接字的数据结构需要能够随意存储删除数据,所以就使用 list。

下面是代码:

服务器端:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>
#include <pthread.h>
#include <list>

using namespace std;

list<int> client_list;//用于存储客户端套接字

int server_fd;

void* getClient(void* arg)//监测客户端连接
{
while(1)
{

struct sockaddr_in client_addr;
socklen_t c_addr_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &c_addr_len);
client_list.push_back(client_fd);
printf("client %d online\n",client_fd);
}
}
void* getData(void* arg)//读取客户端发来的数据
{
while(1)
{
struct timeval tv = {5,0};//设置5秒的超时检测有误客户端发送数据
list<int>::iterator it;
fd_set rfds;
FD_ZERO(&rfds);
int max_fd = -1;
//遍历客户端套接字列表,将每个套接字添加到 fd_set 中
for(it = client_list.begin();it!=client_list.end();it++)
{
FD_SET(*it, &rfds);
if(max_fd < *it)
{
max_fd = *it;
}
}

int ret_val = select(max_fd +1, &rfds, NULL, NULL, &tv);

if(ret_val == -1)
{
printf("select error!\n");
break;
}
else if(ret_val == 0)
{
printf("no message!\n");
continue;
}
else
{
//如果有套接字可读,则遍历每一个套接字判断是否为可读状态,如果是就读取数据
for(it = client_list.begin();it!=client_list.end();it++)
{
if(FD_ISSET(*it, &rfds))
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
int ren = recv(*it, buffer, sizeof(buffer), 0);
cout << "get from" << *it << buffer << endl;
}
}
}
}
}
void* sendMsg(void* arg)//发送数据
{
while(1)
{
char buffer[1024];
fgets(buffer, sizeof(buffer), stdin);
list<int>::iterator it;
for(it = client_list.begin(); it != client_list.end(); it++)
{
send(*it, buffer,sizeof(buffer),0);
}
}
}
int main()
{
int ret;
pthread_t tid1,tid2,tid3;

server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(8000);

ret = bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(ret)
{
perror("bind error");
exit(1);
}

ret = listen(server_fd, 10);
if(ret)
{
perror("listen error");
exit(1);
}

ret = pthread_create(&tid1, NULL, getClient, NULL);
ret = pthread_create(&tid2, NULL, getData, NULL);
ret = pthread_create(&tid3, NULL, sendMsg, NULL);

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
close(server_fd);
return 0;
}


客户端:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet
4000
.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>

using namespace std;

int main()
{
int ret;
fd_set fds;
struct timeval tv = {5, 0};
int ret_val,max_fd;
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
//server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
inet_pton(AF_INET, "127.0.0.1", (void*)&server_addr.sin_addr.s_addr);//这里用新函数将十分点字符串转化为32位地址

ret = connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(ret)
{
perror("connet error");
exit(1);
}

char sendbuf[1024];
char recvbuf[1024];
while(1)
{
FD_ZERO(&fds);
FD_SET(0,&fds);
FD_SET(client_fd, &fds);
max_fd = 0;
if(max_fd < client_fd)
max_fd = client_fd;
//这里使用 select 是检测服务器端有数据读还是 stdin 有数据读,一个是为了接收数据,一个是为了发送数据
ret_val = select(max_fd + 1, &fds, NULL, NULL, &tv);

if(ret_val == -1)
{
cout << "server disconnect!" << endl;
break;
}
else if(ret_val == 0)
{
cout << "timeout no message" << endl;
continue;
}
else
{
if(FD_ISSET(client_fd, &fds))//如果是服务器端有数据
{
recv(client_fd, recvbuf, sizeof(recvbuf), 0);
cout << recvbuf;
}
if(FD_ISSET(0, &fds))//如果是标准输入端有数据可读
{
fgets(sendbuf, sizeof(sendbuf), stdin);
send(client_fd, sendbuf, strlen(sendbuf), 0);
if(strcmp(sendbuf, "exit\n") == 0)
break;
}
}
}
close(client_fd);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  socket linux c语言