UDP
不同的套接字种类指向不同的套接字方法:多态
产生2个可执行程序,客户端可以发给别人,服务器在linux
任何1个进程有2个栈:用户栈、内核栈(操作系统在运行是形成 的临时数据)
标准错误输出cerr、标准输出,都是往显示器打印,但不同的文件描述符打印
ssize_t:有符号int;size_t:无符号int
udpServer.cc
#include"udpServer.hpp"
void Usage(std::string proc)
{//使用说明书
std::cout<<"Usage: "<<proc<<" local_port"<<std::endl;
}
int main(int argc,char *argv[])
{
if(argc != 2){//不传ip时,是2
Usage(argv[0]);
exit(1);
}
udpServer* up = new udpServer(atoi(argv[1]));//定义的Server对象在堆上,堆的空间大,推荐以后都这样写
up->initServer();
up->start();
delete up;
}
udpServer.hpp
#include<iostream>
#include<string>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
class udpServer{
private:
//std::string ip;
int port;//也可以定义为short类型,2字节,端口号
int sock;
public:
//udpServer(std::string _ip = "127.0.0.1",int _port = 8080)默认构造函数
//:ip(_ip),port(_port)初始化
udpServer(int _port = 8080)
:port(_port)
{}
void initServer()//初始化服务器
{ sock = socket(AF_INET,SOCK_DGRAM,0);
struct sockaddr_in local;//local:临时变量,在用户栈上所定义出来变量,写入到操作系统中,调用bind()接口,ip进入内核层
local.sin_family = AF_INET;
local.sin_port = htons(port);
//local.sin_addr.s_addr = inet_addr(ip.c_str()); //ip:STL的容器、字符串,转换为c语言形式
local.sin_addr.s_addr = htonl(INADDR_IN);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{ std::cerr<< "bind error"<<std::endl;
exit(1); //绑定失败,终止进程
}
}
void start()//启动服务器
{
char msg[64]; //定义msg,接收的缓冲区
for(;;){//服务器一直运行下去,死循环
msg[0] = '\0';//对缓冲区进行初始化,清空字符串
struct sockaddr_in end_point; //表示对端
socklen_t len = sizeof(end_point);
ssize_t s = recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);
if(s > 0){
//我还希望知道谁给我发的,所以需要获取到它的IP地址和端口号
char buf[16];
sprintf(buf,"%d",ntohs(end_point.sin_port));
//end_point.sin_port是一个int类型的整数,此时需要把它转换为字符串,sprintf把一个整数转成字符串
std::string cli = inet_ntoa(end_point.sin_addr);
cli += ":";
cli += buf;
msg[s] = '\0';
std::cout<< client<<"#"<< msg <<std::endl;//client发来的信息
//接收以后在Server端打印出来,返回都去的数值再加上一定的标识,说明Server接收到了的应答
std::string echo_string = msg;
echo_string += " [Server echo!] ";//加后缀,客户端给回响
sendto(sock,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&end_point,len);//参数都是要以C语言形式进行传参的
}
}
}
~udpServer()
{
close(sock);
}
};
udpClient.cc
#include"udpClient.hpp"
void Usage(std::string proc)
{//使用手册
std::cout<<"Usage: "<<proc<<"server_ip server_port"<<std::endl;//客户端连服务器,要知道server_ip server_port
}
int main(int argc,char *argv[])
{
if(argc != 3){
Usage(argv[0]);
exit(1);
}
udpClient uc(argv[1],atoi(argv[2]));//此时所定义出来的Client对象在栈上,但是我们知道,栈的空间是有限的,比较小,所以不推荐这种方式,但是此时代码还比较少,所以可以采用这种方式。atoi:字符串转为int
uc.initClient();
uc.start();
return 0;
}
udpClient.hpp
#include<iostream>
#include<string>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
class udpClient{
private:
std::string ip;
int port;//对于端口号当然也可以定义为short类型,2字节
int sock;
public:
//Server端的ip和port:
udpClient(std::string _ip = "127.0.0.1",int _port = 8080)
:ip(_ip),port(_port)
{}
void initClient()
{
sock = socket(AF_INET,SOCK_DGRAM,0);
//客户端是不需要绑定,操作系统完成这一步
}
void start()
{
std::string msg;
struct sockaddr_in peer; //表示服务器
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
peer.sin_addr.s_addr = inet_addr(ip.c_str());
//点分十进制的IP地址转换为网络的4字节,ip地址(string类型)转为C语言格式的字符串
for(;;){
std::cout<< "Please Enter# ";
std::cin>>msg;//用户输入内容
//先给Server端发送数据,然后在接收
sendto(sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));
char echo[128];
ssize_t s = recvfrom(sock,echo,sizeof(echo)-1,0,nullptr,nullptr);
if(s > 0){//读取数据成功
echo[s] = '\0';
std::cout<<"Server# "<<echo<<std::endl;
}
}
}
~udpClient()
{
close(sock);
}
};
Makefile
FLAG=-std=c++11
.PHONY:all
all:udpClient udpServer
udpClient:udpClient.cc
g++ -o $@ $^ $(FLAG)
udpServer:udpServer.cc
g++ -o $@ $^ $(FLAG)
.PHONY:clean
clean:
rm -f udpClient udpServer
英译汉
#include<iostream>
#include<string>
#include<map>
#include<cstdio>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
class udpServer{
private:
int port;
int sock;
std::map<std::string,std::string> dict; //字典
public:
udpServer(int _port = 8080)
:port(_port)
{
dict.insert(std::pair<std::string,std::string>("string","字符串"));
dict.insert(std::pair<std::string,std::string>("sort","排序"));
dict.insert(std::pair<std::string,std::string>("student","学生"));
}
void initServer()
{sock = socket(AF_INET,SOCK_DGRAM,0);
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
std::cerr<< "bind error"<<std::endl;
exit(1);
}
}
void start()
{
char msg[64];
for(;;){
msg[0] = '\0';
//ssize_t 是一个int类型,是一个有符号的
struct sockaddr_in end_point; //表示对端
socklen_t len = sizeof(end_point);
ssize_t s = recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);
if(s > 0){
char buf[16];
sprintf(buf,"%d",ntohs(end_point.sin_port));
std::string cli = inet_ntoa(end_point.sin_addr);
cli += ":";
cli += buf;
msg[s] = '\0';
std::cout<< cli <<" # "<< msg <<std::endl;
std::string echo = "unknow";//如果找不到则显示unknow
//std::map<std::string,std::string>::iterator it = dict.find(msg);
auto it = dict.find(msg);//msg:k值
while(it != dict.end()){
//说明找到了
echo = dict[msg];//通过K值返回V值
break;
}
sendto(sock,echo.c_str(),echo.size(),0,(struct sockaddr*)&end_point,len);
}
}
}
~udpServer()
{
close(sock);
}
};
流式服务:管道
TCP
1、
tcpServer.cc
#include"tcpServer.hpp"
void Usage(std::string proc)
{
std::cout<<"Usage: " << proc<< "port"<<std::endl;
}
int main(int argc,char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
tcpServer *ts = new tcpServer(atoi(argv[1]));
ts->initServer();
ts->start();
delete ts;
}
tcpServer.hpp
#pragma once
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#define BACKLOG 5 //一般这个的值都设置的比较小,表示的意思是此时底层链接队列中等待链接的长度
class tcpServer{
private:
int port;
int lsock;//表示监听的套接字
public:
tcpServer(int _port = 8080,int _lsock = -1)
:port(_port),lsock(_lsock)
{}
void initServer()
{
lsock = socket(AF_INET,SOCK_STREAM,0);
if(lsock < 0){
std::cerr<<"socket error"<<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
std::cerr<<"bind error!"<<std::endl;
exit(3);
}
//走到这里说明bind成功了,但是tcp是面向链接的,所以还需要一个接口
if(listen(lsock,BACKLOG) < 0){
std::cout<<"listen error!"<<std::endl;
exit(4);
}
//tcp必须要将套接字设置为监听状态 这里可以想夜间你去吃饭,什么时候去都可以吃到,是为什么?是因为店里面一直有人在等待着你
//任意时间有客户端来连接我
//成功返回0,失败返回-1
}
//echo服务器
void service(int sock)
{
while(true){
//打开一个文件,也叫打开一个流,所以这里其实使用read和write也是可以的,但是这里是网络,最好使用tcp的recv和send
char buf[64];
ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
if(s > 0){
buf[s] = '\0';
std::cout<<"Client# "<< buf <<std::endl;
send(sock,buf,strlen(buf),0);//此时的网络就是文件,你往文件中发送东西的格式,可不是以\0作为结束,加入\0会出现乱码的情况
}
else if(s == 0){
std::cout<<"Client quit ..."<<std::endl;
break;
}else{
std::cerr<<"recv error"<<std::endl;
break;
}
}
close(sock);
}
void start()
{
struct sockaddr_in endpoint;
while(true){
//第一步应该获取链接
//这里可以理解为 饭店拉客
//lsock 是负责拉客的 accept的返回值是主要负责服务客户的
socklen_t len = sizeof(endpoint);
int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
if(sock < 0){
std::cerr<<"accept error!"<<std::endl;
continue; //相当于拉客失败了,我需要继续拉客
}
std::cout<<"get a new link"<<std::endl;
//此时就是对我拉上来的客人进行服务
service(sock);
}
}
~tcpServer()
{}
};
tcpClient.cc
#include"tcpClient.hpp"
void Usage(std::string proc)
{
std::cout<< "Usage: " << "\n";
std::cout <<"\t"<<"serv_ip serv_port"<< std::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;
}
tcpClient.hpp
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
class tcpClient{
private:
std::string serv_ip;
int serv_port;
int sock;
public:
tcpClient(std::string _ip = "127.0.0.1",int _port = 8080,int _sock = -1)
:serv_ip(_ip),serv_port(_port),sock(_sock)
{}
void initClient()
{
sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
std::cerr<<"socket error"<<std::endl;
exit(2);
}
struct sockaddr_in serv_point;
serv_point.sin_family = AF_INET;
serv_point.sin_port = htons(serv_port);
serv_point.sin_addr.s_addr = inet_addr(serv_ip.c_str());
//成功返回0,失败返回-1
if(connect(sock,(struct sockaddr*)&serv_point,sizeof(serv_point)) < 0){
std::cerr<<"connect error"<<std::endl;
}
}
void start()
{
char msg[64];
while(true){
//对于Client端首先应该发送数据,然后在考虑接收
ssize_t s = read(0,msg,sizeof(msg)-1);//从标准输入中读取数据
if(s > 0){
msg[s] = '\0';
send(sock,msg,strlen(msg),0);
ssize_t ss = recv(sock,msg,sizeof(msg)-1,0);
if(ss > 0){
msg[ss] = '\0';
std::cout<<"Server echo # " << msg<<std::endl;
}
}
}
}
~tcpClient()
{
close(sock);
}
};
2、多进程版
tcpServer.hpp
#pragma once
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<signal.h>
#define BACKLOG 5 //一般这个的值都设置的比较小,表示的意思是此时底层链接队列中等待链接的长度
class tcpServer{
private:
int port;
int lsock;//表示监听的套接字
public:
tcpServer(int _port = 8080,int _lsock = -1)
:port(_port),lsock(_lsock)
{}
void initServer()
{
signal(SIGCHLD,SIG_IGN); //采用忽略的方式
lsock = socket(AF_INET,SOCK_STREAM,0);
if(lsock < 0){
std::cerr<<"socket error"<<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
std::cerr<<"bind error!"<<std::endl;
exit(3);
}
//走到这里说明bind成功了,但是tcp是面向链接的,所以还需要一个接口
if(listen(lsock,BACKLOG) < 0){
std::cout<<"listen error!"<<std::endl;
exit(4);
}
//tcp必须要将套接字设置为监听状态 这里可以想夜间你去吃饭,什么时候去都可以吃到,是为什么?是因为店里面一直有人在等待着你
//任意时间有客户端来连接我
//成功返回0,失败返回-1
}
//echo服务器
void service(int sock)
{
while(true){
//打开一个文件,也叫打开一个流,所以这里其实使用read和write也是可以的,但是这里是网络,最好使用tcp的recv和send
char buf[64];
ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
if(s > 0){
buf[s] = '\0';
std::cout<<"Client# "<< buf <<std::endl;
send(sock,buf,strlen(buf),0);//此时的网络就是文件,你往文件中发送东西的格式,可不是以\0作为结束,加入\0会出现乱码的情况
}
else if(s == 0){
std::cout<<"Client quit ..."<<std::endl;
break;
}else{
std::cerr<<"recv error"<<std::endl;
break;
}
}
close(sock);
}
void start()
{
struct sockaddr_in endpoint;
while(true){
//第一步应该获取链接
//这里可以理解为 饭店拉客
//lsock 是负责拉客的 accept的返回值是主要负责服务客户的
socklen_t len = sizeof(endpoint);
int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);//父进程:获取链接lsock
if(sock < 0){
std::cerr<<"accept error!"<<std::endl;
continue; //相当于拉客失败了,我需要继续拉客
}
//打印获取到的连接的ip和端口号
std::string cli_info = inet_ntoa(endpoint.sin_addr);
cli_info += ":";
cli_info += std::to_string(endpoint.sin_port); //这里获取的端口号是一个整数,需要把该整数转换为字符串,当然这里也可以使用stringstream 比如定义一个对象 , 此时有一个int变量,一个string 对象 你把int输入stringstream 然后再把stringstream在输入string的对象中,就进行了转换
//stringstream ss;
//int a = 100;
//string b ;
//ss << a;
//ss >> b; 这样就进行了整形转字符串的功能
std::cout<<"get a new link "<< cli_info<<" sock: "<<sock<<std::endl;
pid_t id = fork();
if(id == 0){
//说明这是子进程
//此时我让子进程来进行对应的处理服务
//对于子进程来说始会继承父进程的一整套东西的,那么相对应的也会继承他的lsock和sock,那么对于子进程更关心sock,所以还需要把lsock进行关闭
close(lsock);
service(sock);
exit(0);//子进程运行完毕(执行完相应的任务、io任务)以后有可能会继续运行下去,所以让他退出
子进程有sock、lsock2个文件描述符,只关心sock,指向同一个文件,不同的表
}
//但是问题是,我还需要父进程在这里等待
//waitpid(id,NULL,0);父进程在这里阻塞等待,当时父进程可以等待吗?不行呀,因为父进程需要去接受新来的连接。那么就不等待子进程的消息了吗?那不行,还有一种方式就是
//对于子进程退出,其实是会给父进程发送一个SIG_CHILD的信号的,那么当父进程将收到的SIG_CHILD进行忽略子进程的信号,子进程退出的时候,系统会自动回收子进程
//此时就是对我拉上来的客人进行服务
//父进程不关心sock
close(sock);//不要想不通,因为他们使用的是不同的文件描述符表
}
}
~tcpServer()
{}
};
tcpClient.hpp
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
class tcpClient{
private:
std::string serv_ip;
int serv_port;
int sock;
public:
tcpClient(std::string _ip = "127.0.0.1",int _port = 8080,int _sock = -1)
:serv_ip(_ip),serv_port(_port),sock(_sock)
{}
void initClient()
{
sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
std::cerr<<"socket error"<<std::endl;
exit(2);
}
struct sockaddr_in serv_point;
serv_point.sin_family = AF_INET;
serv_point.sin_port = htons(serv_port);
serv_point.sin_addr.s_addr = inet_addr(serv_ip.c_str());
//成功返回0,失败返回-1
if(connect(sock,(struct sockaddr*)&serv_point,sizeof(serv_point)) < 0){
std::cerr<<"connect error"<<std::endl;
}
}
void start()
{
char msg[64];
while(true){
std::cout<<"Please Enter Massage# ";
//这里有一个用户层的c/c++的缓冲区,所以需要强制刷新,这样就不会出现第一行不打印这句的问题
fflush(stdin);
//对于Client端首先应该发送数据,然后在考虑接收
ssize_t s = read(0,msg,sizeof(msg)-1);//从标准输入中读取数据
//因为cin不会会把回车键给过滤掉,但是read又会获得回车键
if(s > 0){
msg[s-1] = '\0';//这一步可以把读到的回车键给过滤掉,这样显示的时候,就不会出现空行
send(sock,msg,strlen(msg),0);
ssize_t ss = recv(sock,msg,sizeof(msg)-1,0);
if(ss > 0){
msg[ss] = '\0';
std::cout<<"Server echo # " << msg<<std::endl;
}
}
}
}
~tcpClient()
{
close(sock);
}
};
tcpServer.hpp
#pragma once
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>
#define BACKLOG 5
class tcpServer{
private:
int port;
int lsock;//表示监听的套接字
public:
tcpServer(int _port = 8080,int _lsock = -1)
:port(_port),lsock(_lsock)
{}
void initServer()
{
lsock = socket(AF_INET,SOCK_STREAM,0);
if(lsock < 0){
std::cerr<<"socket error"<<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
std::cerr<<"bind error!"<<std::endl;
exit(3);
}
//走到这里说明bind成功了,但是tcp是面向链接的,所以还需要一个接口
if(listen(lsock,BACKLOG) < 0){
std::cout<<"listen error!"<<std::endl;
exit(4);
}
}
//echo服务器
static void service(int sock)
{
while(true){
//打开一个文件,也叫打开一个流,所以这里其实使用read和write也是可以的,但是这里是网络,最好使用tcp的recv和send
char buf[64];
ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
if(s > 0){
buf[s] = '\0';
std::cout<<"Client# "<< buf <<std::endl;
send(sock,buf,strlen(buf),0);//此时的网络就是文件,你往文件中发送东西的格式,可不是以\0作为结束,加入\0会出现乱码的情况
}
else if(s == 0){
std::cout<<"Client quit ..."<<std::endl;
break;
}else{
std::cerr<<"recv error"<<std::endl;
break;
}
}
close(sock);
}
static void *serviceRoutine(void *arg)
{
pthread_detach(pthread_self());//采用线程分离,运行完了以后自动释放。
std::cout<<"create a new thread"<<std::endl;
int *p = (int*)arg;
int sock = *p;
service(sock);
delete p;
}
void start()
{
struct sockaddr_in endpoint;
while(true){
socklen_t len = sizeof(endpoint);
int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
if(sock < 0){
std::cerr<<"accept error!"<<std::endl;
continue; //相当于拉客失败了,我需要继续拉客
}
//打印获取到的连接的ip和端口号
std::string cli_info = inet_ntoa(endpoint.sin_addr);
cli_info += ":";
cli_info += std::to_string(endpoint.sin_port);
std::cout<<"get a new link "<< cli_info<<" sock: "<<sock<<std::endl;
pthread_t tid;
//得到这个sock之后,给他保存一份,然后就不怕被覆盖了
int *p = new int(sock); //这样做这个sock就是下面所创建新线程所私有的了
pthread_create(&tid,nullptr,serviceRoutine,(void *)p); //但是这里取sock的地址可能会出现bug,那是因为有可能正在取的时候,主线程又accept获得了一个新连接
//pthread_create(&tid,nullptr,serviceRoutine,(void*)&sock); //但是这里取sock的地址可能会出现bug,那是因为有可能正在取的时候,主线程又accept获得了一个新连接
//那么就有可能,对原先的sock进行覆盖,也就是没有处理前面的那个连接
//主进程还需要等待线程,进行回收
//pthread_join()但是这里如果主线程进行阻塞等待子线程执行完毕的话,那么主线程酒干不了别的事情了,所以不能这样,所以这里还有种方式就是线程分离
}
}
~tcpServer()
{}
};
#pragma once
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>
#include"ThreadPool.hpp"
#define BACKLOG 5 //一般这个的值都设置的比较小,表示的意思是此时底层链接队列中等待链接的长度
class tcpServer{
private:
int port;
int lsock;//表示监听的套接字
ThreadPool *tp;
public:
tcpServer(int _port = 8080,int _lsock = -1)
:port(_port),lsock(_lsock)
{}
void initServer()
{
lsock = socket(AF_INET,SOCK_STREAM,0);
if(lsock < 0){
std::cerr<<"socket error"<<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
std::cerr<<"bind error!"<<std::endl;
exit(3);
}
//走到这里说明bind成功了,但是tcp是面向链接的,所以还需要一个接口
if(listen(lsock,BACKLOG) < 0){
std::cout<<"listen error!"<<std::endl;
exit(4);
}
//tcp必须要将套接字设置为监听状态 这里可以想夜间你去吃饭,什么时候去都可以吃到,是为什么?是因为店里面一直有人在等待着你
//任意时间有客户端来连接我
//成功返回0,失败返回-1
tp = new ThreadPool();
tp->ThreadPoolInit();//初始化线程池threadpool,创造出来一批线程
}
//echo服务器
static void service(int sock)
{
while(true){
//打开一个文件,也叫打开一个流,所以这里其实使用read和write也是可以的,但是这里是网络,最好使用tcp的recv和send
char buf[64];
ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
if(s > 0){
buf[s] = '\0';
std::cout<<"Client# "<< buf <<std::endl;
send(sock,buf,strlen(buf),0);//此时的网络就是文件,你往文件中发送东西的格式,可不是以\0作为结束,加入\0会出现乱码的情况
}
else if(s == 0){
std::cout<<"Client quit ..."<<std::endl;
break;
}else{
std::cerr<<"recv error"<<std::endl;
break;
}
}
close(sock);
}
类内的成员函数,传this指针
static void *serviceRoutine(void *arg)
{
pthread_detach(pthread_self());//采用线程分离,运行完了以后自动释放。
std::cout<<"create a new thread"<<std::endl;
int *p = (int*)arg;
int sock = *p;
service(sock);
delete p;
}
void start()
{
struct sockaddr_in endpoint;
while(true){
socklen_t len = sizeof(endpoint);
int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
if(sock < 0){
std::cerr<<"accept error!"<<std::endl;
continue; //相当于拉客失败了,我需要继续拉客
}
std::string cli_info = inet_ntoa(endpoint.sin_addr);
cli_info += ":";
cli_info += std::to_string(endpoint.sin_port); //这里获取的端口号是一个整数,需要把该整数转换为字符串,当然这里也可以使用stringstream 比如定义一个对象 , 此时有一个int变量,一个string 对象 你把int输入stringstream 然后再把stringstream在输入string的对象中,就进行了转换
std::cout<<"get a new link "<< cli_info<<" sock: "<<sock<<std::endl;
//当获得一个新链接的时候,就不要创建新线程等,只需要构建一个任务,然后把这个任务push到线程池中
//线程池里面的线程都是从任务队列中取的,所以接收到的连接应该打包成为一个Task任务包
Task *t = new Task(sock);
tp->Put(*t);
}
}
~tcpServer()
{}
};
#pragma once
#include<iostream>
#include<queue>
#include<math.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string>
#include<map>
#define NUM 5
class Task
{
public:
int sock;
std::map<std::string,std::string> dict;初始化静态成员:类外
public:
Task()
{}
Task(int _sock)
:sock(_sock)
{
dict.insert(std::make_pair("apple","苹果"));
dict.insert(std::make_pair("banana","香蕉"));
dict.insert(std::make_pair("student","学生"));
dict.insert(std::make_pair("boy","男孩"));
dict.insert(std::make_pair("goat","山羊"));
}
void Run()
{
std::cout<< "task is running "<<std::endl;
char buf[64];
ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
if(s > 0){
buf[s] = '\0';
//让他返回翻译好的单词意思,所以还需要定义一个字典
std::string key = buf;
//send发送的类型必须是以C语言的形式
send(sock,dict[key].c_str(),dict[key].size(),0);
}
else if(s == 0){
std::cout<<"Client quit ..."<<std::endl;
}
else{
std::cerr<<"recv error"<<std::endl;
}
}
~Task()
{
std::cout<<"server close sock"<<std::endl;
close(sock);
}
};
class ThreadPool
{
private:
std::queue<Task*> q; //线程池中需要一个任务队列,server端不断的发数据,线程池中的线程不断的处理数据,那么有可能你的Task越来越大,导致性能的损失,所以为了避免改用指针
int max_num;//你需要线程池里面有多少个线程
pthread_mutex_t lock;
pthread_cond_t cond; //你需要server和线程池中的线程保持同步的关系,当没有任务的时候,需要让线程等待,但是不可以让server端等待,如果它等待问题就大了
public:
void LockQueue()
{
pthread_mutex_lock(&lock);
}
void UnlockQueue()
{
pthread_mutex_unlock(&lock);
}
bool isEmpty()
{
return q.size() == 0;
}
void ThreadWait()
{
pthread_cond_wait(&cond,&lock);
}
void ThreadWakeUp()
{
pthread_cond_signal(&cond);
}
public:
ThreadPool(int _max = NUM):max_num(_max) 缺省
{}
//加了static 那么这个成员函数就属于类,只有一份,也就没有了this指针
//且static只能够访问static函数,没有的不能访问
static void *Routine(void *arg)// 此时他作为内部成员函数,第一个默认的参数是this指针,所以这里其实是2个参数 正常来说pthread_create的最后一个参数应该是一个,但是此时却变为了2个
{
ThreadPool* this_p = (ThreadPool*)arg;
//这是线程池里面的线程,要他们一直都存在,不销毁
while(true)
{
//线程池创造出来的目的就是去解决任务,但是又面临这同组线程之间的竞争以及在取任务的时候,server端放,所以需要一把锁
this_p->LockQueue();
while(this_p->isEmpty())//这里有可能是会被假唤醒的,所以需要循环的去判断
{
this_p->ThreadWait();
}
//如果任务队列中不是空,那就开始消化任务
Task *t;
this_p->Get(&t);
this_p->UnlockQueue();
t->Run();//这一步很厉害,我已经把任务从队列中拿出来了,就不需要在占有锁,在临界资源内处理了,可以先进行释放,让任务队列继续工作,我自己处理我拿出来的任务
delete t;
}
}
void ThreadPoolInit()
{
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&cond,nullptr);
pthread_t t[NUM];
for(int i = 0;i<max_num;++i)
{
pthread_create(t+i,nullptr,Routine,this);
}
}
//server
void Put(Task& in)引用
{
//要往任务队列中塞任务
LockQueue();
q.push(&in);地址
UnlockQueue();
//至少要唤醒一个线程
//pthread_cond_signal pthread_cond_broadcast()唤醒所有的线程
ThreadWakeUp();唤醒,去执行任务
}
void Get(Task **out)
{任务队列里:1级指针,用out把任务指针拿出来
Task *t = q.front();
q.pop();
*out = t;
}
~ThreadPool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
数据从套接字里取,
得到新链接,构造任务,把任务放到任务队列,多线程取任务,run函数处理任务,处理完任务之后,析构、关闭链接、delete任务,任务太多时,塞到任务队列里缓存,处理完之后再拿出来队列里的处理
服务器接收请求,
TCP:全双工、字节流、面向链接的通信方案
tcpServer.hpp
#pragma once
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>
#include"ThreadPool.hpp"
#define BACKLOG 5 //一般这个的值都设置的比较小,表示的意思是此时底层链接队列中等待链接的长度
class tcpServer{
private:
int port;
int lsock;//表示监听的套接字
ThreadPool *tp;
public:
tcpServer(int _port = 8080,int _lsock = -1)
:port(_port),lsock(_lsock)
{}
void initServer()
{
lsock = socket(AF_INET,SOCK_STREAM,0);
if(lsock < 0){
std::cerr<<"socket error"<<std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
std::cerr<<"bind error!"<<std::endl;
exit(3);
}
//走到这里说明bind成功了,但是tcp是面向链接的,所以还需要一个接口
if(listen(lsock,BACKLOG) < 0){
std::cout<<"listen error!"<<std::endl;
exit(4);
}
//tcp必须要将套接字设置为监听状态 这里可以想夜间你去吃饭,什么时候去都可以吃到,是为什么?是因为店里面一直有人在等待着你
//任意时间有客户端来连接我
//成功返回0,失败返回-1
tp = new ThreadPool();创建
tp->ThreadPoolInit();//初始化threadpool,创造出来一批线程
}
//echo服务器
static void service(int sock)
{
while(true){
//打开一个文件,也叫打开一个流,所以这里其实使用read和write也是可以的,但是这里是网络,最好使用tcp的recv和send
char buf[64];
ssize_t s = recv(sock,buf,sizeof(buf)-1,0);
if(s > 0){
buf[s] = '\0';
std::cout<<"Client# "<< buf <<std::endl;
send(sock,buf,strlen(buf),0);//此时的网络就是文件,你往文件中发送东西的格式,可不是以\0作为结束,加入\0会出现乱码的情况
}
else if(s == 0){
std::cout<<"Client quit ..."<<std::endl;
break;
}else{
std::cerr<<"recv error"<<std::endl;
break;
}
}
close(sock);
}
static void *serviceRoutine(void *arg)
{
pthread_detach(pthread_self());//采用线程分离,运行完了以后自动释放。
std::cout<<"create a new thread"<<std::endl;
int *p = (int*)arg;
int sock = *p;
service(sock);
delete p;
}
void start()
{
struct sockaddr_in endpoint;
while(true){
socklen_t len = sizeof(endpoint);
int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
if(sock < 0){
std::cerr<<"accept error!"<<std::endl;
continue; //相当于拉客失败了,我需要继续拉客
}
std::string cli_info = inet_ntoa(endpoint.sin_addr);
cli_info += ":";
cli_info += std::to_string(endpoint.sin_port); //这里获取的端口号是一个整数,需要把该整数转换为字符串,当然这里也可以使用stringstream 比如定义一个对象 , 此时有一个int变量,一个string 对象 你把int输入stringstream 然后再把stringstream在输入string的对象中,就进行了转换
std::cout<<"get a new link "<< cli_info<<" sock: "<<sock<<std::endl;
//当获得一个新链接的时候,就不要创建新线程等,只需要构建一个任务,然后把这个任务push到线程池中
//线程池里面的线程都是从任务队列中取的,所以接收到的连接应该打包成为一个Task任务包
Task *t = new Task(sock);栈上开任务
tp->Put(*t);解引用,开的任务放到任务队列
}
}
~tcpServer()
{}
};
tcpClient.hpp
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
class tcpClient{
private:
std::string serv_ip;
int serv_port;
int sock;
public:
tcpClient(std::string _ip = "127.0.0.1",int _port = 8080,int _sock = -1)
:serv_ip(_ip),serv_port(_port),sock(_sock)
{}
void initClient()
{
sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
std::cerr<<"socket error"<<std::endl;
exit(2);
}
struct sockaddr_in serv_point;
serv_point.sin_family = AF_INET;
serv_point.sin_port = htons(serv_port);
serv_point.sin_addr.s_addr = inet_addr(serv_ip.c_str());
//成功返回0,失败返回-1
if(connect(sock,(struct sockaddr*)&serv_point,sizeof(serv_point)) < 0){
std::cerr<<"connect error"<<std::endl;
}
}
void start()
{
char msg[64];
// while(true){
std::cout<<"Please Enter Massage# ";
//这里有一个用户层的c/c++的缓冲区,所以需要强制刷新,这样就不会出现第一行不打印这句的问题
fflush(stdout);
//对于Client端首先应该发送数据,然后在考虑接收
ssize_t s = read(0,msg,sizeof(msg)-1);//从标准输入中读取数据
//因为cin不会会把回车键给过滤掉,但是read又会获得回车键
if(s > 0){
msg[s-1] = '\0';//这一步可以把读到的回车键给过滤掉,这样显示的时候,就不会出现空行
send(sock,msg,strlen(msg),0);发到服务器
ssize_t ss = recv(sock,msg,sizeof(msg)-1,0);读取翻译之后的信息
if(ss > 0){
msg[ss] = '\0';
std::cout<<"Server echo # " << msg<<std::endl;
}
else if(ss == 0){
//break;
}else{
//break;
}
}
// }
}
~tcpClient()
{
close(sock);
}
};