上古TCP服务器
上一篇的远古服务器,是单线程+阻塞IO模式的,一次只能连接一个客户端,无法同时为多个客户端提供连接。现在我们为每个客户端的连接新建一个线程,收发客户端数据都放在各自的线程里面,互不干扰。模式如下图:
一 服务端编码
#include<unistd.h>
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<signal.h>
#include<thread>
#include<vector>
const uint16_t recv_len = 1024;
const uint16_t port = 8880;
bool bLoop = true;
void callback_func(int conn_fd){
std::string send_buf = R"({"radio": {"proto": "11axg","country": "Britain","bandwidth": "HT20","usrlimit": "32","chanid": "auto","bswitch": "1"}})";
std::string recv_buff = "";
recv_buff.resize(recv_len);
while (bLoop){
int r_len = recv(conn_fd, &recv_buff.at(0),recv_buff.size(),0);
if(r_len == -1){
std::cout << "recv data error!" << std::endl;
close(conn_fd);
break;
}else if(r_len == 0){
std::cout << "remote client " << conn_fd << " closed!" << std::endl;
close(conn_fd);
break;
}else{
std::cout << "recive client data : " << recv_buff << std::endl;
}
int s_len = send(conn_fd,&send_buf.at(0),send_buf.size(),0);
if(s_len == -1){
std::cout << "send data error!" << std::endl;
close(conn_fd);
break;
}
}
}
int main(int argc, char const *argv[])
{
std::vector<std::thread> vec_conn;
int fd = socket(AF_INET,SOCK_STREAM,0);
if (fd == -1){
printf("create socket error!\n");
return 0;
}
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(fd,(struct sockaddr*)&serv_addr,sizeof(sockaddr)) == -1){
std::cout << "bind addr error!" << std::endl;
return 0;
}
if(listen(fd,1024) == -1){
std::cout << "listen addr error!" << std::endl;
return 0;
}
while (1){
int conn_fd = accept(fd,(struct sockaddr*)NULL,NULL);
if(-1 == conn_fd){
std::cout << "connect error!" << std::endl;
continue;
}else{
std::cout << "accept a new connet client!" << "conn id=" << conn_fd << std::endl;
}
std::thread t(callback_func,conn_fd);
t.detach();
vec_conn.push_back(std::move(t));
}
close(fd);
vec_conn.clear();
std::cout <<"quit server!" <<std::endl;
return 0;
}
二 服务端编译运行
编译: g++ -std=c++11 server2.cpp -lpthread -o server2 运行: ./server2
三 客户端编译运行
客户端代码client1.cpp 沿用上一节的代码,编译步骤和启动方法不变。
编译客户端代码: g++ client1.cpp -o client1
启动客户端:
./client1 192.168.49.128
可以再启动多个客户端,验证服务器能否并发。
./client1 192.168.49.128
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string>
#include<signal.h>
const uint16_t recv_buf_len = 1024;
const uint16_t port = 8880;
bool bLoop = true;
void handler(int sig){
bLoop = false;
}
int main(int argc, char const *argv[])
{
if(argc < 2){
std::cout << "usage: ./client1 <ip address>" << std::endl;
return -1;
}
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd){
std::cout << "socket create error!" << std::endl;
return -1;
}
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
int ret = connect(fd,(struct sockaddr*)&servaddr,sizeof(servaddr));
if(-1 == ret){
std::cout << "connet server error!" << std::endl;
close(fd);
return -1;
}
std::string send_buf = "client1 keep alived.";
std::string recv_buff = "";
recv_buff.resize(recv_buf_len);
signal(SIGINT,handler);
while (bLoop){
int send_len = send(fd,&send_buf.at(0),send_buf.size(),0);
if(send_len == -1){
std::cout << "send data error!" << std::endl;
break;
}
sleep(0.5);
int recv_len = recv(fd,&recv_buff.at(0),recv_buff.size(),0);
if(recv_len == -1){
std::cout << "receive error!" << std::endl;
break;
}else if(recv_len == 0){
std::cout << " socket closed!" << std::endl;
break;
}
std::cout << " receive server data: " << recv_buff << std::endl;
}
std::cout << "client1 quit!" <<std::endl;
close(fd);
return 0;
}
四 问题与总结
我们看到这个服务器可以同时为多个客户端提供连接服务,我们用主线程+多子线程+阻塞IO 模型,服务器具备了初步的并发能力。 服务器每收到一个客户端连接就会开辟单独新的线程对象,但是开辟线程是要耗费系统资源的,线程数量不能无限大。而且随着线程数量的上升,线程之间的切换的开销成指数级增长。这种形式的并发量是有限的,远远不能满足现代成千上百万的并发量。
|