tcp服务器的实现
Posted ych9527
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tcp服务器的实现相关的知识,希望对你有一定的参考价值。
文章目录
1.服务端需要绑定,客户端不需要
1.1服务器为什么需要绑定
1.一般服务器端是固定的,是总所周知的,ip和port不需要也不能轻易的更改。比如:http对应的端口号是80 https:443 ssh:22
2.服务器面对的客户很多,服务器一旦改了,客户端立马找不到,就无法访问服务器了。比如在日常生活中110对应的就是报警,报警的第一反应就是110
3.服务器是一对多的,一旦发生变化,很多客户端都要做出相对的变动
1.2客户端为什么不需要绑定
1.客户有很多客户端,如果绑定,就需要让不同的公司商量一下,什么软件用什么端口,端口是标识进程的,一个端口只能对应一个进程,如果多个进程使用同一个端口,就会导致绑定是失败。并且这种让不同的公司进行沟通进行约定,是很不现实的。
如果进行了bind很容易发生端口冲突,导致客户端无法启动
2.客户端需要唯一性,不需要明确是那个端口,但是需要IP和port。
客户端使用udp服务器进行数据的交互之时,系统会自动进行Ip和端口号的绑定(OS最清楚端口号情况)。端口号是16位,是有限的,OS是需要管理它的,因此OS清楚哪些端口没有被使用。而自己去绑定是不确定的,只能是"碰运气",看那个端口没有被绑定
2.什么是本地环回
IP:127.0.0.1 叫做本地环回 -> 通常用来进行网络通信代码的测试,一般本地跑通说明本地环境以及代码基本没有大问题
3.INADDR_ANY
**[注]**云服务器有些地方是不需要端口的,这是考虑到安全问题,因此在绑定公网IP的时候可能会失败
网络地址为INADDR_ANY,这个宏表示本地的任意ip地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个ip地址,这样设置可以在所有的IP地址上进行监听,直到与某个客户建立了链接时才确下来到底用哪个ip地址
绑定的是INADDR_ANY该主机的所有IP层收到的数据都会交给主机,否则只有绑定的IP才会将数据交给主机
4.四字节和点分十进制的相互转换
#include <iostream>
using namespace std;
#include <string>
#include <vector>
typedef union IP
{
unsigned ip;//四字节ip
struct{
unsigned part1:8;
unsigned part2:8;
unsigned part3:8;
unsigned part4:8;
}ip_seg;
}IP;
void GetString(string & str,IP &ip)//从part之中读取1个个字节的整形转换成字符串
{
str+=to_string(ip.ip_seg.part1);
str+='.';
str+=to_string(ip.ip_seg.part2);
str+='.';
str+=to_string(ip.ip_seg.part3);
str+='.';
str+=to_string(ip.ip_seg.part4);
}
void GetNum(string &str,IP &ip)//将字符串之中的内容填入ip,读取part
{
string temp[4];
int sub=0;
for(int i=0;i<(int)str.size();i++)
{
if(str[i]!='.')
temp[sub]+=str[i];
else
{
sub++;
}
}
ip.ip_seg.part1=stoi(temp[0]);
ip.ip_seg.part2=stoi(temp[1]);
ip.ip_seg.part3=stoi(temp[2]);
ip.ip_seg.part4=stoi(temp[3]);
}
int main()
{
//将整形ip转换成点分十进制
IP ip;//定义一个联合结构体,用来转换字符串
ip.ip=987654321;
string str;
GetString(str,ip);
cout<<ip.ip<<"->"<<str<<endl;
//将点分十进制转换成整形四字节ip
cout<<"________________"<<endl;
IP ip2;//定义一个联合结构体,用来转换成整形
GetNum(str,ip2);
cout<<str<<"->"<<ip2.ip<<endl;
return 0;
}
效果演示:
5.udp字典服务器
#pragma once
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <map>
class udpServer
{
private:
string ip;
int port;
int sock;
map<string,string> dict;
public:
udpServer(int _port)
:port(_port)
{
dict.insert(pair<string,string>("apple","苹果"));
dict.insert(pair<string,string>("banana","香蕉"));
dict.insert(pair<string,string>("school","学校"));
}
void initServer()//初始化服务器
{
sock=socket(AF_INET,SOCK_DGRAM,0);
cout<<sock<<endl;
//当前套接字文件里面只有文件信息
//这个文件对应的是网络文件、因此需要加入ip、port信息->绑定
//传入的ip、port是在用户层的,下面需要绑定在内核里面
//因此需要填充结构体,sockaddr_in(ipv4)
struct sockaddr_in local;
local.sin_family=AF_INET;//填充协议
local.sin_port=htons(port);//填充端口->转成网络字节序
local.sin_addr.in_addr::s_addr = INADDR_ANY;//绑定本机上的任意IP
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
cerr<<"bind error"<<endl;//往显示器上打印,但是对应的文件描述符不一样
exit(1);//绑定失败终止整个进程
}
}
//服务器启动之后、永不退出
//写一个回应服务器
void start()
{
while(1)
{
struct sockaddr_in end_point;//保存是谁送人信息->ip 和port
socklen_t len=sizeof(end_point);//发送人结构体大小
char buff[88];//接收消息缓冲区
buff[0]='\\0';//清空缓冲区
ssize_t size=recvfrom(sock,buff,sizeof(buff)-1,0,(struct sockaddr*)&end_point,&len);//接收消息
if(size>0)//收到消息了
{
string cli=inet_ntoa(end_point.sin_addr);//,将整形转换成str,获取客户端的点分十进制ip
cli+=":";
cli+=to_string(ntohs(end_point.sin_port));//获取客户端的port,获得的是整形,需要转换成字符串
cli+=':';
buff[size]='\\0';//在末尾添加\\0
cout<<cli<<buff<<endl;//打印客户端发送过来的消息
// string echo_string = buff;//回显字符串
// echo_string+="-> server echo";
//
string echo_string;
map<string,string>::iterator it=dict.find(buff);//通过key值查找
if(it==dict.end())//没找到
echo_string+="not find";
else
echo_string+=it->second;
sendto(sock,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&end_point,len);
}
}
}
~udpServer()
{
close(sock);//关闭文件描述符
}
};
#include "udpServer.hpp"
void Usage()
{
cout<<"Usage:"<<"please enter server ip and port"<<endl;
}
int main(int argc,char*argv[])
{
if(argc!=2)
{
Usage();
exit(1);
}
udpServer *up=new udpServer(atoi(argv[1]));
up->initServer();//初始化
up->start();//运行起来
return 0;
}
#pragma once
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/inet.h>
class udpClient
{
private:
string ip;
int port;
int sock;
public:
//连接服务器->所以填写server的ip和端口-> 下载软件的时候就是下载客户端
udpClient(string _ip,int _port)
:ip(_ip)
,port(_port)
{}
void initClient()//初始化客户端
{
sock=socket(AF_INET,SOCK_DGRAM,0);
cout<<sock<<endl;
//客户端不需要绑定
}
//服务器启动之后、永不退出
//写一个回应服务器
void start()
{
string send_string;
//发送给谁,填充信息
struct sockaddr_in peer;
peer.sin_family=AF_INET;
peer.sin_port=htons(port);
peer.sin_addr.s_addr=inet_addr(ip.c_str());//将字符串转成4字节再转成网络字节序
char buff[88];
while(1)
{
cout<<"please enter your message#"<<endl;
getline(cin,send_string);
sendto(sock,send_string.c_str(),send_string.size(),0,(struct sockaddr*)&peer,sizeof(peer));
buff[0]='\\0';
ssize_t size=recvfrom(sock,buff,sizeof(buff)-1,0,nullptr,nullptr);
if(size>0)
{
buff[size]='\\0';
cout<<"Serve:"<<buff<<endl;
}
}
}
~udpClient()
{
close(sock);//关闭文件描述符
}
};
#include "udpClient.hpp"
void Usage()
{
cout<<"Usage:"<<"please enter Server ip and port"<<endl;
}
int main(int argc,char *argv[])
{
if(argc!=3)
{
Usage();
exit(2);
}
udpClient *uc =new udpClient(argv[1],atoi(argv[2]));
uc->initClient();
uc->start();
return 0;
}
6.TCP协议
6.1监听套接字 1listen
让套接字处理监听状态listen:允许任何时刻客户端来链接我(udp可以不链接,ip+port是表示发给谁)
什么是监听状态呢?
好比餐馆,老板就处于监听你的状态,只要你去了就有饭吃
6.2建立链接accpet
此时的返回值也是一个文件描述符,它和socket的返回值有什么区别呢?
sock:专注于底层获取链接(监听套接字)
accept返回值:专注于进程之间的通信
比如:一个餐馆,有在外面迎宾的和在里面招待客人的,他们就是两种套接字。迎宾的只负责将客人(链接)带进餐馆,带进餐馆之后他就不管了,之后与客人进行沟通,则是在里面的服务员了 -> 类比 sock(迎宾,负责获取链接)、accept返回值(服务员,负责进程之间的通信)
6.3 文件和流的概念
一般的文件都是基于流的 -> 比如通过open、fopen、pipe等方式打开的文件都有对应的概念 stream -> 在c/c++之中打开一个文件都叫做打开一个文件流。
tcp sock也是流的概念,因此读取sock文件时,也可以用read和write,但是常用的是recv
6.4代码实现tcp服务器和客户端
6.4.1服务器
#ifndef _TCPSERVER_HPP_
#define _TCPSERVER_HPP_
#include <iostream>
using namespace std;
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#endif
#define BACKLOCK 5
class tcpServer
{
private:
int port;
int lsock;//监听套接字
public:
tcpServer(int _port)
:port(_port)
,lsock(-1)
{}
void initServer()
{
lsock=socket(AF_INET,SOCK_STREAM,0);
if(lsock<0)
{
cerr<<"socket fail"<<endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;//填充协议
local.sin_port=htons(port);//填充端口,转换成网网络字节序
local.sin_addr.s_addr=INADDR_ANY;//填写ip
if(bind(lsock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
{
cerr<<"bind error"<<endl;
exit(3);
}
if(listen(lsock,BACKLOCK)<0)
{
cerr<<"listen error"<<endl;
exit(4);
}
}
void service(int sock)
{
char buff[200];
while(true)
{
size_t size=recv (sock,buff,sizeof(buff)-1,0);
if(size>0)//读取到了
{
buff[size]='\\0';//末尾添加\\0
cout<<"client:"<<buff<<endl;
send(sock,buff,strlen(buff),0);//不需要+1,\\0是语言的标准,文件不认识
}
}
}
void stat()
{
sockaddr_in end_point;//获取客户结构体信息,和udp作用一样
while(true)
{
//udp直接通信,tcp第一件事是获取链接
socklen_t len=sizeof(end_point);
int sock=accept(lsock,(struct sockaddr*)&end_point,&len);
if(sock<0)
{
cerr<<"accpet error"<<endl;
continue;
}
cout<<"get a new accept"<<endl;
service(sock);//封装一个服务接口
}
}
~tcpServer()
{
close(lsock);
}
};
#include "tcpServer.hpp"
void Usage()
{
cout<<"please enter your port"<<endl;
}
int main(int argc,char *argv[])
{
if(argc!=2)
{
Usage();
exit(1);
}
tcpServer *tp=new tcpServer(stoi(argv[1]));//传输的是字符串 -> int
tp->initServer();
tp->stat();
delete tp;
return 0;
}
6.4.2客户端
知识点预知:
1.客户端链接服务器接口:
2.服务器如何知道客户端退出了:
3.将一个进程放置后台,拿到前台:
4.效果展示:
5.代码:
#ifndef _TCP_CLIENT_CPP_
#define _TCP_CLIENT_CPP_
#include <stdio.h>
using namespace std;
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
class tcpClient
{
private:
string ip;
int port;
int sock;
public:
tcpClient(string _ip,int _port)
:ip(_ip)
,port(_port)
,sock(-1)
{}
void initClient()
{
sock =socket(AF_INET,SOCK_STREAM,0);//绑定套接字
if(sock==-1)
{
cerr<<"client sock error"<<endl;
exit(1);
}
//链接服务器 -> udp不需要连接诶
struct sockaddr_in peer;
peer.sin_family=AF_INET;
peer.sin_port=htons(port);//主机转成网络序列
peer.sin_addr.s_addr=inet_addr(ip.c_str());//将字符串转成网络整形
if(connect(sock,(struct sockaddr*)&peer,sizeof(peer))!=0)//链接服务器
{
cerr<<"connect fail"<<endl;
exit(2);
}
}
//链接成功......
void start()
{
char msg[100];
while(true)
{
cout<<"please your message"<<endl;
size_t s=read(0,msg,sizeof(msg)-1);//从标准输入读取
if(s>0)
{
msg[s-1]=0;//read会将换行读进去
send(sock,msg,s,0);//给服务器发送信息
size_t ss=recv(sock,msg,sizeof(msg)-1,0);
if(ss>0)//表示读取到了信息
{
msg[ss]=0;
cout<<"server:"<<msg<<endl;
}
}
}
}
~tcpClient()
{
close(sock);
}
};
#include "tcpClient.hpp"
#include <stdio.h>
using namespace std;
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
void Usage(char *arg)
{
cout<<"usage:"<<arg<<":"<<"please enter yout ip and port"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(-1);
}
tcpClient *tc=new tcpClient(argv[1],atoi(argv[2]));
tc->initClient();
tc->start();
delete tc;
return 0;
}
6.5tcp服务器优化
6.5.1多进程版本
#ifndef _TCPSERVER_HPP_
#define _TCPSERVER_HPP_
#include <iostream>
using namespace std;
#include <stdlib.h>
#include <signal.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#define BACKLOCK 5
class tcpServer
{
private:
int port;
int lsock;//监听套接字
public:
tcpServer(int _port)
:port(_port)
,lsock(-1)
{}
void initServer()//初始化,创建套接字
{
signal(SIGCHLD,SIG_IGN);//父进程忽略子进程发送过来的信号,即子进程自己清理资源
lsock=socket(AF_INET,SOCK_STREAM,0);
if(lsock<0)
{
cerr<<"socket fail"<<endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;//填充协议
local.sin_port=htons(port);//填充端口,转换成网网络字节序
local.sin_addr.s_addr=INADDR_ANY;//填写ip
if(bind(lsock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
{
cerr<<"bind error"<<endl;
exit(3);
}
if(listen(lsock,BACKLOCK)<0)
{
cerr<<"listen error"<<endl;
exit(4);
}
}
void service(int sock)//执行任务函数
{
char buff[200];
while(true)
{
size_t size=recv (sock,buff,sizeof(buff)-1,0);
if(size>0)//读取到了
{
buff[size]='\\0';//末尾添加\\0
cout<<"client:"<<buff<<endl;
send(sock,buff,strlen(buff),0);//不需要+1,\\0是语言的标准,文件不认识
}
else if(size==0)
{
cout<<"client quit..."<<endl;
close(sock);//关闭对应的套接字
break;
}
else//读取错误
{
cerr<<"read error"<<endl;
close(sock);
break;
}
}
}
void stat()
{
sockaddr_in end_point;//获取客户结构体信息,和udp作用一样
while(true)
{
//udp直接通信,tcp第一件事是获取链接
socklen_t len=sizeof(end_point);
int sock=accept(lsock,(struct sockaddr*)&end_point,&len);
if(sock<0)
{
cerr<<"accpet error"<<endl;
continue;
}
string cl;//客户端信息
cl+=inet_ntoa(end_point.sin_addr);//网->主,整->字符串
cl+=':';
cl+=to_string(ntohs(end_point.sin_port));
cout<<"get a new accept:"<<cl<<"-"<<"sock:"<<sock<<endl;
//创建多进程,可以有多个执行流
pid_t id=fork();
if(id==0)//子进程提供服务
{
//子进程不关心lsock
close(lsock);//子进程拷贝了父进程的文件描述表
service(sock);//封装一个服务接口
exit(0);//执行完任务退出
}
//父进程的主要任务是获取链接
//然后链接越多,对应的为服务创建的sock文件描述符也越多
//父进程可用的资源就少了,因此不如关闭
close(sock);//父进程不关心后面的服务
}
}
~tcpServer()
{
close(lsock);
}
};
另外一种写法___________________________________
pid_t id=fork();
if(id==0)
{
if(fork()>0)//子进程退出
exit(0);
}
//接下来的这一段是孙子进行在运行
close(lsock);//子进程拷贝了父进程的文件描述表
service(sock);//封装一个服务接口
exit(0);//执行完任务退出
wait(id,null,0);//父进程立马可以等待子进程
-> 不推荐这样写,fork是有成本的,创建进程越多,代价越大
效果展示:
6.5.2多线程版本
#ifndef _TCPSERVER_HPP_
#define _TCPSERVER_HPP_
#include <iostream>
using namespace std;
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#define BACKLOCK 5
class tcpServer
{
private:
int port;
int lsock;//监听套接字
public:
tcpServer(int _port)
:port(_port)
,lsock(-1)
{}
void initServer()
{
signal(SIGCHLD,SIG_IGN);//父进程忽略子进程发送过来的信号,即子进程自己清理资源
lsock=socket(AF_INET,SOCK_STREAM,0);
if(lsock<0)
{
cerr<<"socket fail"<<endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;//填充协议
local.sin_port=htons(port);//填充端口,转换成网网络字节序
local.sin_addr.s_addr=INADDR_ANY;//填写ip
if(bind(lsock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
{
cerr<<"bind error"<<endl;
exit(3);
}
if(listen(lsock,BACKLOCK)<0)
{
cerr<<"listen error"<<endl;
exit(4);
}
}
static void service(int sock)
{
char buff[200];
while(true)
{
size_t size=recv (sock,buff,sizeof(buff)-1,0);
if(size>0)//读取到了
{
buff[size]='\\0';//末尾添加\\0
cout<<"client:"<<buff<<endl;
send(sock,buff,strlen(buff),0);//不需要+1,\\0是语言的标准,文件不认识
}
else if(size==0)
{
cout<<"client quit..."<<endl;
close(sock);//关闭对应的套接字
break;
}
else//读取错误
{
cerr<<"read error"<<endl;
close(sock);
break;
}
}
}
static void *sevrun(void *arg)//只能有一个参数,因此必须是static
{
pthread_detach(pthread_self());//线程分离,不用再进行等待
cout<<"create a new prhread"<<endl;
int *p=(int*)arg;
int sock=*p;
service(sock);//执行任务
delete p;
return NULL;
}
void stat()
{
sockaddr_in end_point;//获取客户结构体信息,和udp作用一样
while(true)
{
//udp直接通信,tcp第一件事是获取链接
socklen_t len=sizeof(end_point);
int sock=accept(lsock,(struct sockaddr*)&end_point,&len);
if(sock<0)
{
cerr<<"accpet error"<<endl;
continue;
}
string cl;//客户端信息
cl+=inet_ntoa(end_point.sin_addr);//网->主,整->字符串
cl+=':';
cl+=to_string(ntohs(end_point.sin_port));
cout<<"get a new accept:"<<cl<<"-"<<"sock:"<<sock<<endl;
pthread_t tid;
//防止新线程还未执行,老线程又创建了新的链接,sock就被改变了
//new出来一段空间,只要传参就发生了拷贝,所以不会有影响
int *p=new int(sock);
pthread_create(&tid,nullptr,sevrun,(void*)p);//创建线程
}
}
~tcpServer()
{
close(lsock);
}
};
//makefile
FLAG= -lpthread
.PHONY:all
all:tcpClient tcpServer -lpthread
tcpClient:tcpClient.cpp
g++ -o $@ $^ $(FLAG)
tcpServer:tcpServer.cpp
g++ -o $@ $^ $(FLAG)
.PHONY:clean
clean:
rm -rf tcpClient tcpServer
效果展示:
6.5.3线程池版本
#pragma once
#include <iostream>
#include <math.h>
#include <pthread.h>
#include <queue>
#include <unistd.h>
#include <map>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
using namespace std;
#define NUM 5
class Task
{
private:
int sock;
map<string,string>dict;
public:
Task()
{}
Task(int _sock)
:sock(_sock)
{
dict.insert(make_pair("banana","香蕉"));
dict.insert(make_pair("apple","苹果"));
}
void Run()//来一个链接,执行一个任务(打印语句)
{
cout<<"Task is run....."<<endl;
char buff[100];
size_t size=recv (sock,buff,sizeof(buff)-1,0);
if(size>0)//读取到了
{
buff[size]='\\0';//末尾添加\\0
string key=buff;
send(sock,dict[key].c_str(),dict[key].size(),0);//不需要+1,\\0是语言的标准,文件不认识
}
else if(size==0)
{
cout<<"client quit..."<<endl;
close(sock);//关闭对应的套接字
}
else//读取错误
{
cerr<<"read error"<<endl;
close(sock);
}
}
~Task()
{
close(sock);
}
};
class Pool
{
private:
int _max;//线程个数
queue<Task*> q;//存指针,减少数据拷贝消耗
pthread_mutex_t lock;
pthread_cond_t cond;
public:
Pool(int max=NUM)
:_max(max)
{}
void TaskLock()
{
pthread_mutex_lock(&lock);
}
void TaskUnlock()
{
pthread_mutex_unlock(&lock);
}
void ThreadWait()
{
pthread_cond_wait(&cond,&lock);
}
void ThreadWakeUp()
{
pthread_cond_signal(&cond);//一次唤醒一个
}
void AllWakeUp()
{
pthread_cond_broadcast(&cond);//全部唤醒
}
bool IsEmpty()
{
return q.size()==0;
}
void Put(Task &data)//外部放入数据
{
TaskLock();
q.push(&data);
TaskUnlock();
ThreadWakeUp();//唤醒一个线程
}
void Get(Task **data)//内部线程池完成任务
{
Task *t=q.front();
q.pop();
*data=t;
}
public:
//必须设置为静态的,因为成员函数会有this形参
static void *RunTask(void *arg)//处理任务函数
{
Pool* p_this=(Pool*)arg;
pthread_detach(pthread_self());//分离
while(true)
{
p_this->TaskLock();
//不退出才进行判断
while(p_this->IsEmpty())//没有任务则挂起
{
p_this->ThreadWait();
}
//在这里一定不为空,将任务拿出来
Task *t;
p_this->Get(&t);
p_this->TaskUnlock();
t->Run();
delete t;
}
}
//尽量不要在构造函数内做有风险的事情
void Init()
{
//锁和环境变量初始化
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&cond,nullptr);
//线程池的创建
pthread_t tid;
for(int i=0;i<_max;i++)
{
pthread_create(&tid,nullptr,RunTask,this);//每个线程都去处理任务,不关心线程ID
}
}
~Pool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
#ifndef _TCPSERVER_HPP_
#define _TCPSERVER_HPP_
#include "pool.hpp"
#include <iostream>
using namespace std;
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#define BACKLOCK 5
class tcpServer
{
private:
int port;
int lsock;//监听套接字
Pool *pl;
public:
tcpServer(int _port)
:port(_port)
,lsock(-1)
,pl(nullptr)
{}
void initServer()
{
signal(SIGCHLD,SIG_IGN);//父进程忽略子进程发送过来的信号,即子进程自己清理资源
lsock=socket(AF_INET,SOCK_STREAM,0);
if(lsock<0)
{
cerr<<"socket fail"<<endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;//填充协议
local.sin_port=htons(port);//填充端口,转换成网网络字节序
local.sin_addr.s_addr=INADDR_ANY;//填写ip
if(bind(lsock,(struct sockaddr*)&local,sizeof(local))<0)//进行绑定
{
cerr<<"bind error"<<endl;
exit(3);
}
if(listen(lsock,BACKLOCK)<0)
{
cerr<<"listen error"<<endl;
exit(4);
}
pl=new Pool();
pl->Init();//初始化线程池
}
static void service(int sock)
{
char buff[200];
while(true)
{
size_t size=recv (sock,buff,sizeof(buff)-1,0);
if(size>0)//读取到了
{
buff[size]='\\0';//末尾添加\\0
cout<<"client:"<<buff<<endl;
send(sock,buff,strlen(buff),0);//不需要+1,\\0是语言的标准,文件不认识
}
else if(size==0)
{
cout<<"client quit..."<<endl;
close(sock);//关闭对应的套接字
break;
}
else//读取错误
{
cerr<<"read error"<<endl;
close(sock);
break;
}
}
}
static void *sevrun(void *arg)//只能有一个参数,因此必须是static
{
pthread_detach(pthread_self());//线程分离
cout<<"create a new prhread"<<endl;
int *p=(int*)arg;
int sock=*p;
service(sock);//执行任务
delete p;
return NULL;
}
void stat()
{
sockaddr_in end_point;//获取客户结构体信息,和udp作用一样
while(true)
{
//udp直接通信,tcp第一件事是获取链接
socklen_t len=sizeof(end_point);
int sock=accept(lsock,(struct sockaddr*)&end_point,&len);
if(sock<0)
{
cerr<<"accpet error"<<endl;
continue;
}
string cl;//客户端信息
cl+=inet_ntoa(end_point.sin_addr);//网->主,整->字符串
cl+=':';
cl+=to_string(ntohs(end_point.sin_port));
cout<<"get a new accept:"<<cl<<"-"<<"sock:"<<sock<<endl;
//此时获得了新的连接 -> 将任务put至线程池
Task *t= new Task(sock);
pl->Put(*t);//将任务put进去
}
}
~tcpServer()
{
close(lsock);
}
};
效果展示:
6.5.4版本特点
1.单进程版本:只有一个执行流,一般不使用,多进程/多线程版本通常小型应用(局域网,少量机)
2.多进程版本:进程之间是独立的,健壮性强,但是比较吃资源,效率低下
3.多线程版本:健壮性不强,较吃资源,效率相对较高
大量的客户端:系统会存在大量的执行流,切换有可能称为效率低下的原因
4.线程池版本:可以减轻一大批客户端来进行链接时,服务器的压力。同时、由于线程池的线程数量有限,可以有效防止外部非法请求攻击、也在一定程度上保护了计算机
以上是关于tcp服务器的实现的主要内容,如果未能解决你的问题,请参考以下文章