[ 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。
下面是代码:
服务器端:
客户端:
先说一下思路:客户端不必关注,和简单的一对一相同, 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; }
相关文章推荐
- Linux系统下连接Oracle数据库,创建新的数据库用户及分配权限
- linux中c/c++连接mysql示例程序
- Unix/Linux C++应用开发-Linux下用户管理
- C++ linux epoll并发服务器模型初探
- linux eclipse中C++连接mysql数据库
- linux下C++连接mysql
- 在Linux下面使用Mysql的客户端工具WorkBench创建数据库和用户并连接
- Linux root用户不能通过SSH连接的问题
- linux c/c++网络编程之—epoll 模型
- linux 下tcp连接 c++ 线程处理
- linux 下c/c++ 连接mysql数据库全过程-----已经通过验证
- linux下安装oracle,本地PL/SQL连接Linux下的oracle导入表并新建用户和密码
- Linux断开用户连接
- 在Linux下面使用Mysql的客户端工具WorkBench创建数据库和用户并连接
- linux下用非root用户重启导致ssh无法连接的问题
- Cocos2d-x-3.6 用户交互原理---------如何通过JNI连接Java和C++
- MySQL学习笔记_11_Linux下C++/C连接MySQL数据库(一)
- 关闭linux用户连接
- linux中c/c++连接mysql示例程序
- Linux下配置C/C++连接Mysql