网络编程套接字(3): 简单的TCP网络程序
文章目录
- 网络编程套接字(3)
- 4. 简单的TCP网络程序
- 4.1 服务端创建
- (1) 创建套接字
- (2) 绑定端口
- (3) 监听
- (4) 获取新连接
- (5) 处理读取与写入
- 4.2 客户端创建
- (1)连接服务器
- 4.3 代码编写
- (1) v1__简单发送消息
- (2) v2_多进程版本
- (3) v3_多线程版本
- (4) v4_线程池版本
网络编程套接字(3)
4. 简单的TCP网络程序
4.1 服务端创建
(1) 创建套接字
还是之前udp部分的socket函数,这里只是简单说明一下与udp的差异
int socket(int domain, int type, int protocol);
只需将第二个参数type换成:
SOCK_STREAM: 基于TCP的网络通信,流式套接字,提供的是流式服务(对应TCP的特点:面向字节流)
(2) 绑定端口
还是和之前一样的接口
(3) 监听
UDP服务器的初始化操作只有2步,第一步:创建套接字,第二步:是绑定。但是TCP服务器是面向连接的,客户端在正式向TCP服务器发送数据之前,需要先与TCP服务器建立连接,然后才能与服务器进行通信。
因此TCP服务器需要时刻注意是否有客户端发来连接请求,此时就需要将TCP服务器创建的套接字设置为监听状态
listen for connections on a socket: 监听套接字上的连接
头文件:#include <sys/types.h> #include <sys/socket.h>函数原型:int listen(int sockfd, int backlog);参数说明:第一个参数sockfd: 需要设置为监听状态的套接字对应的文件描述符第二个参数backlog: 这里当成一个整数,后续详细解释返回值:监听成功: 返回0监听失败: 失败返回-1,并设置错误码
(4) 获取新连接
客户端有新链接到来,服务端可以获取到新链接,这一步需要死循环获取客户端新链接。
accept a connection on a socket: 接收套接字上的连接
头文件:#include <sys/types.h> #include <sys/socket.h>函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);参数说明:第一个参数sockfd: 监听套接字第二个参数addr: 获取对方一端网络相关的属性信息第三个参数addrlen: addr的长度返回值:连接成功: 返回接收到的套接字的文件描述符连接失败: 失败返回-1,并设置错误码
关于accept的返回值: 也是一个文件描述符
为什么又返回一个新的文件描述符??返回的这个新的文件描述符跟旧的文件描述符_sockfd有什么关系?
感性理解:
对比listen监听套接字与accept函数返回的套接字
- listen监听套接字:用于获取客户端发来的连接请求。accept函数会不断从监听套接字当中获取新连接
- accept函数返回的套接字:用于为本次accept获取到的连接提供服务。
- 而listen监听套接字的任务只是不断获取新连接,而真正为这些连接提供服务的套接字是accept函数返回的套接字,而不是监听套接字。
(5) 处理读取与写入
因为TC 提供的是流式服务,所以这里利用read和write来实现读取与写入
4.2 客户端创建
4步:创建套接字,客户端向服务器发起连接请求,bind(不需要自己绑定,由OS自动分配),处理数据读取与写入
(1)连接服务器
initiate a connection on a socket: 在套接字上发起连接
头文件:#include <sys/types.h>#include <sys/socket.h>函数原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数说明:第一个参数sockfd: 表示通过该套接字发起连接请求第二个参数addr: 对方一端网络相关的属性信息第三个参数addrlen: addr的长度返回值:连接成功: 返回0连接失败: 失败返回-1,并设置错误码
4.3 代码编写
这里一共提供4个版本的tcp代码
err.hpp
:这个代码是公用的后续不在给出
#pragma onceenum
{USAGE_ERR=1,SOCKET_ERR,BIND_ERR,LISTEN_ERR,CONNECT_ERR,
};
(1) v1__简单发送消息
客户端向服务端发送消息,服务端收到后再把消息发回给客户端
tcpServer.hpp
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include"err.hpp"
using namespace std;// 问题: 目前的服务器, 无法处理多个client的问题, 为什么?
// 单进程服务, 当服务端向客户端提供业务处理服务时, 没有办法accet, 不能处理连接namespace ns_server
{static const uint16_t defaultport=8081;static int backlog=32;using func_t=function<string(const string&)>; // 回调函数,一种处理逻辑class TcpServer{public:TcpServer(func_t func, uint16_t port=defaultport):func_(func),port_(port),quit_(true){}void InitServer(){// 1. 创建socket文件listensock_=socket(AF_INET,SOCK_STREAM,0);if(listensock_<0){cerr<<"create socket error"<<endl;exit(SOCKET_ERR);}// 2. bindstruct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_port=htons(port_);local.sin_family=AF_INET;local.sin_addr.s_addr=INADDR_ANY;int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));if(n<0){cerr<<"bind socket error"<<endl;exit(BIND_ERR);}// 3. 监听int m=listen(listensock_,backlog);if(m<0){cerr<<"listen socket error"<<endl;exit(LISTEN_ERR);}}void Start(){quit_=false;while(!quit_){struct sockaddr_in client;socklen_t len=sizeof(client);// 4. 获取连接, acceptint sock=accept(listensock_,(struct sockaddr*)&client,&len);if (sock < 0) // accept失败并不会终止进程, 只要获取下一个连接{cerr << "accept error" << endl;continue;}// 提取client信息 --- debugstring clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格uint16_t clientport=ntohs(client.sin_port); // 网络序列转主机序列// 5. 获取新连接成功后, 开始进行业务处理cout<<"获取新连接成功: "<<sock<<" from "<<listensock_<<", "<< clientip << "- " <<clientport<<endl;// v1service(sock,clientip,clientport);}}// 流式 - 利用read和writevoid service(int sock, const string&clientip,const uint16_t clientport){string who=clientip + "-" + to_string(clientport);char buffer[1024];while(true){ssize_t s=read(sock,buffer,sizeof(buffer)-1);if(s>0){buffer[s]=0;string res=func_(buffer); // 进行回调cout<<who<< ">>> " <<res<<endl;// 把收到的消息返回(写给客户端)write(sock,res.c_str(),res.size());}else if(s==0){// 对方将连接关闭了close(sock);cout<< who <<" quit, me too"<<endl;break;}else{close(sock);cerr<<"read error: "<<strerror(errno)<<endl;break;}}}~TcpServer(){}private:uint16_t port_;int listensock_;bool quit_; // 标志服务器是否运行字段func_t func_;};
}
tcpServer.cc
#include"tcpServer.hpp"
using namespace ns_server;// ./tcp_server port// 使用手册
static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}string echo(const string&message)
{return message;
}int main(int argc,char*argv[])
{if(argc!=2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);unique_ptr<TcpServer> tsvr(new TcpServer(echo,port));tsvr->InitServer();tsvr->Start();return 0;
}
tcpClient.cc
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include"err.hpp"
using namespace std;static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{// 准备工作if(argc!=3){usage(argv[0]);exit(USAGE_ERR);}string serverip=argv[1];uint16_t serverport=atoi(argv[2]);// 1.创建套接字int sock=socket(AF_INET,SOCK_STREAM,0);if (sock < 0){cerr << "create socket error: " << strerror(errno) << endl;exit(SOCKET_ERR);}// (2) 客户端要不要bind呢? 要// 要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind// (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接// 2. connect 客户端向服务器发起连接请求struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_port=htons(serverport);server.sin_family=AF_INET;// server.sin_addr.s_addr=INADDR_ANY; 绝对不是inet_aton(serverip.c_str(),&(server.sin_addr)); // 字符串风格ip转成点分十进制int cnt=5;while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0) // 连接失败{sleep(1);cout<<"正在给你重连, 重连次数还有: "<<cnt--<<endl;if(cnt<=0)break;}if(cnt<=0){cerr<<"连接失败"<<endl;exit(CONNECT_ERR);}char buffer[1024];// 3. 连接成功while(true){string line;cout<<"Enter>> ";getline(cin,line);write(sock,line.c_str(),line.size());ssize_t s = read(sock, buffer, sizeof(buffer)-1);if (s > 0){buffer[s] = 0;cout<<"server echo >>>"<<buffer<<endl;}else if (s == 0){cerr << "server quit" << endl;break;}else{cerr << "read error: " << strerror(errno) << endl;break;}}close(sock);return 0;
}
运行结果:
(2) v2_多进程版本
v2版本是把单执行流服务器改成多进程版的服务器
-
在accept获取新连接成功后,fork创建创建子进程,此时子进程对外提供服务, 父进程只进行accept
-
父进程的文件描述符会被子进程继承,但并不是父子共用同一张文件描述符表,因为子进程会拷贝继承父进程的文件描述符表
-
对于套接字文件也是相同的,父进程创建的子进程也会继承父进程的套接字文件,此时子进程就能够对特定的套接字文件进行读写操作,进而完成对对应客户端的服务
关于阻塞等待与非阻塞等待
- 若采用阻塞式等待,那么服务端还是需要等待服务完当前客户端,才能继续获取下一个连接请求,此时服务端仍然是以一种串行的方式为客户端提供服务
- 若采用非阻塞式等待,虽然在子进程为客户端提供服务期间服务端可以继续获取新连接,但此时服务端就需要将所有子进程的PID保存下来,并且需要不断花费时间检测子进程是否退出
- 由此可见两种都有缺陷,所以我们可以考虑让服务端不等待子进程退出
常见的方式有两种:
- 捕捉SIGCHLD信号,将其处理动作设置为忽略。
- 让父进程创建子进程,子进程再创建孙子进程,子进程退出,让孙子进程为客户端提供服务,孙进程的回收工作由OS来承担
下面是创建孙进程的方案:
tcpServer.hpp
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include<sys/wait.h>
#include"err.hpp"
using namespace std;namespace ns_server
{static const uint16_t defaultport=8081;static int backlog=32;using func_t=function<string(const string&)>; // 回调函数,一种处理逻辑class TcpServer{public:TcpServer(func_t func, uint16_t port=defaultport):func_(func),port_(port),quit_(true){}void InitServer(){// 1. 创建socket文件listensock_=socket(AF_INET,SOCK_STREAM,0);if(listensock_<0){cerr<<"create socket error"<<endl;exit(SOCKET_ERR);}// 2. bindstruct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_port=htons(port_);local.sin_family=AF_INET;local.sin_addr.s_addr=INADDR_ANY;int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));if(n<0){cerr<<"bind socket error"<<endl;exit(BIND_ERR);}// 3. 监听int m=listen(listensock_,backlog);if(m<0){cerr<<"listen socket error"<<endl;exit(LISTEN_ERR);}}void Start(){// signal(SIGCHLD,SIG_IGN); // ok, 最推荐// signal(SIGCHLD,handler); // 回收子进程, 不太推荐quit_=false;while(!quit_){struct sockaddr_in client;socklen_t len=sizeof(client);// 4. 获取连接, acceptint sock=accept(listensock_,(struct sockaddr*)&client,&len);if (sock < 0) // accept失败并不会终止进程, 只要获取下一个连接{cerr << "accept error" << endl;continue;}// 提取client信息 --- debugstring clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格uint16_t clientport=ntohs(client.sin_port); // 网络序列转主机序列// 5. 获取新连接成功后, 开始进行业务处理cout<<"获取新连接成功: "<<sock<<" from "<<listensock_<<", "<< clientip << "- " <<clientport<<endl;// v2: 多进程版本// 子进程对外提供服务, 父进程只进行acceptpid_t id=fork();if(id<0){close(sock);continue;}else if(id==0) // child, 父进程的fd会被子进程继承吗? 会; 父子会用同一张文件描述符表吗?不会, 子进程会拷贝继承父进程的fd table{// 建议关闭掉不需要的fdclose(listensock_);if(fork()>0) exit(0); // 就这一行代码// 子进程已经退了(则下面的wait立马返回, 回收子进程资源), 孙子进程在运行(无父进程, 变成孤儿进程, 被系统领养),提供服务// 孙子进程的回收工作由系统来承担service(sock,clientip,clientport);exit(0);}// 父进程, 一定要关闭不需要的fd(否则会导致父进程的文件描述符变少, 即父进程文件描述符资源的浪费[文件描述符泄露])close(sock);// 不等待子进程, 会导致子进程僵尸之后无法回收, 近而导致内存泄漏pid_t ret=waitpid(id,nullptr,0); // 父进程默认是阻塞的, waitpid(id,nullptr,WNOHANG);不推荐if(ret==id)cout<< "wait child "<<id<< " success" <<endl;}}// 流式 - 利用read和writevoid service(int sock, const string&clientip,const uint16_t clientport){string who=clientip + "-" + to_string(clientport);char buffer[1024];while(true){ssize_t s=read(sock,buffer,sizeof(buffer)-1);if(s>0){buffer[s]=0;string res=func_(buffer); // 进行回调cout<<who<< ">>> " <<res<<endl;// 把收到的消息返回(写给客户端)write(sock,res.c_str(),res.size());}else if(s==0){// 对方将连接关闭了close(sock);cout<< who <<" quit, me too"<<endl;break;}else{close(sock);cerr<<"read error: "<<strerror(errno)<<endl;break;}}}~TcpServer(){}private:uint16_t port_;int listensock_;bool quit_; // 标志服务器是否运行字段func_t func_;};
}
tcpServer.cc
#include"tcpServer.hpp"
using namespace ns_server;// ./tcp_server port// 使用手册
static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}string echo(const string&message)
{return message;
}int main(int argc,char*argv[])
{if(argc!=2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);unique_ptr<TcpServer> tsvr(new TcpServer(echo,port));tsvr->InitServer();tsvr->Start();return 0;
}
tcpClient.cc
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include"err.hpp"
using namespace std;static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{// 准备工作if(argc!=3){usage(argv[0]);exit(USAGE_ERR);}string serverip=argv[1];uint16_t serverport=atoi(argv[2]);// 1.创建套接字int sock=socket(AF_INET,SOCK_STREAM,0);if (sock < 0){cerr << "create socket error: " << strerror(errno) << endl;exit(SOCKET_ERR);}// (2) 客户端要不要bind呢? 要// 要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind// (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接// 2. connect 客户端向服务器发起连接请求struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_port=htons(serverport);server.sin_family=AF_INET;// server.sin_addr.s_addr=INADDR_ANY; 绝对不是inet_aton(serverip.c_str(),&(server.sin_addr)); // 字符串风格ip转成点分十进制int cnt=5;while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0) // 连接失败{sleep(1);cout<<"正在给你重连, 重连次数还有: "<<cnt--<<endl;if(cnt<=0)break;}if(cnt<=0){cerr<<"连接失败"<<endl;exit(CONNECT_ERR);}char buffer[1024];// 3. 连接成功while(true){string line;cout<<"Enter>> ";getline(cin,line);write(sock,line.c_str(),line.size());ssize_t s = read(sock, buffer, sizeof(buffer)-1);if (s > 0){buffer[s] = 0;cout<<"server echo >>>"<<buffer<<endl;}else if (s == 0){cerr << "server quit" << endl;break;}else{cerr << "read error: " << strerror(errno) << endl;break;}}close(sock);return 0;
}
运行结果:
(3) v3_多线程版本
频繁的创建进程会给OS带来巨大的负担,并且创建线程的成本比创建线程高得多。因此在实现多执行流的服务器时最好采用多线程进行实现。
主线程创建出新线程后,也是需要等待新线程退出的,否则也会造成类似于僵尸进程这样的问题。但对于线程来说,如果不想让主线程等待新线程退出,直接线程分离即可,当这个线程退出时系统会自动回收该线程所对应的资源。
各个线程共享是同一张文件描述符表,也就是说服务进程(主线程)调用accept函数获取到一个文件描述符后,其他创建的新线程是能够直接访问这个文件描述符的。
所以不能关闭不要的套接字文件描述符,该文件描述符的关闭操作应该又新线程来执行。因为是新线程为客户端提供服务的,只有当新线程为客户端提供的服务结束后才能将该文件描述符关闭。
tcpServer.hpp
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include<sys/wait.h>
#include<pthread.h>
#include"err.hpp"
using namespace std;namespace ns_server
{static const uint16_t defaultport=8081;static int backlog=32;using func_t=function<string(const string&)>; // 回调函数,一种处理逻辑class TcpServer;class ThreadData{public:ThreadData(int fd, const string&ip,const uint16_t&port,TcpServer*ts):sock(fd),clientip(ip),clientport(port),current(ts){}public:int sock;string clientip;uint16_t clientport;TcpServer*current;};class TcpServer{public:TcpServer(func_t func, uint16_t port=defaultport):func_(func),port_(port),quit_(true){}void InitServer(){// 1. 创建socket文件listensock_=socket(AF_INET,SOCK_STREAM,0);if(listensock_<0){cerr<<"create socket error"<<endl;exit(SOCKET_ERR);}// 2. bindstruct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_port=htons(port_);local.sin_family=AF_INET;local.sin_addr.s_addr=INADDR_ANY;int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));if(n<0){cerr<<"bind socket error"<<endl;exit(BIND_ERR);}// 3. 监听int m=listen(listensock_,backlog);if(m<0){cerr<<"listen socket error"<<endl;exit(LISTEN_ERR);}}void Start(){// signal(SIGCHLD,SIG_IGN); // ok, 最推荐// signal(SIGCHLD,handler); // 回收子进程, 不太推荐quit_=false;while(!quit_){struct sockaddr_in client;socklen_t len=sizeof(client);// 4. 获取连接, acceptint sock=accept(listensock_,(struct sockaddr*)&client,&len);if (sock < 0) // accept失败并不会终止进程, 只要获取下一个连接{cerr << "accept error" << endl;continue;}// 提取client信息 --- debugstring clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格uint16_t clientport=ntohs(client.sin_port); // 网络序列转主机序列// 5. 获取新连接成功后, 开始进行业务处理cout<<"获取新连接成功: "<<sock<<" from "<<listensock_<<", "<< clientip << "- " <<clientport<<endl;// v3: 多线程版本 --- 原生多线程// 1. 要不要关闭不要的socket? 绝对不能,一个进程的文件描述符表共享, 关了影响其他线程// 2. 要不要回收线程?要;如何回收?会不会阻塞pthread_t tid;ThreadData*td=new ThreadData(sock,clientip,clientport,this); // 要开出一块独立的空间pthread_create(&tid,nullptr,threadRoutine,td);}}static void*threadRoutine(void*args){pthread_detach(pthread_self());ThreadData*td=static_cast<ThreadData*>(args);td->current->service(td->sock,td->clientip,td->clientport);delete td;}// 流式 - 利用read和writevoid service(int sock, const string&clientip,const uint16_t clientport){string who=clientip + "-" + to_string(clientport);char buffer[1024];while(true){ssize_t s=read(sock,buffer,sizeof(buffer)-1);if(s>0){buffer[s]=0;string res=func_(buffer); // 进行回调cout<<who<< ">>> " <<res<<endl;// 把收到的消息返回(写给客户端)write(sock,res.c_str(),res.size());}else if(s==0){// 对方将连接关闭了close(sock);cout<< who <<" quit, me too"<<endl;break;}else{close(sock);cerr<<"read error: "<<strerror(errno)<<endl;break;}}}~TcpServer(){}private:uint16_t port_;int listensock_;bool quit_; // 标志服务器是否运行字段func_t func_;};
}
tcpServer.cc
#include"tcpServer.hpp"
using namespace ns_server;// ./tcp_server port// 使用手册
static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}string echo(const string&message)
{return message;
}int main(int argc,char*argv[])
{if(argc!=2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);unique_ptr<TcpServer> tsvr(new TcpServer(echo,port));tsvr->InitServer();tsvr->Start();return 0;
}
tcpClient.cc
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include"err.hpp"
using namespace std;static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{// 准备工作if(argc!=3){usage(argv[0]);exit(USAGE_ERR);}string serverip=argv[1];uint16_t serverport=atoi(argv[2]);// 1.创建套接字int sock=socket(AF_INET,SOCK_STREAM,0);if (sock < 0){cerr << "create socket error: " << strerror(errno) << endl;exit(SOCKET_ERR);}// (2) 客户端要不要bind呢? 要// 要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind// (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接// 2. connect 客户端向服务器发起连接请求struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_port=htons(serverport);server.sin_family=AF_INET;// server.sin_addr.s_addr=INADDR_ANY; 绝对不是inet_aton(serverip.c_str(),&(server.sin_addr)); // 字符串风格ip转成点分十进制int cnt=5;while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0) // 连接失败{sleep(1);cout<<"正在给你重连, 重连次数还有: "<<cnt--<<endl;if(cnt<=0)break;}if(cnt<=0){cerr<<"连接失败"<<endl;exit(CONNECT_ERR);}char buffer[1024];// 3. 连接成功while(true){string line;cout<<"Enter>> ";getline(cin,line);write(sock,line.c_str(),line.size());ssize_t s = read(sock, buffer, sizeof(buffer)-1);if (s > 0){buffer[s] = 0;cout<<"server echo >>>"<<buffer<<endl;}else if (s == 0){cerr << "server quit" << endl;break;}else{cerr << "read error: " << strerror(errno) << endl;break;}}close(sock);return 0;
}
运行结果:
(4) v4_线程池版本
多线程版的问题:
- 每当有新连接到来时,服务端的主线程都会为该客户端创建提供服务的新线程,当服务结束时就会将新线程销毁,这样做既麻烦又效率低下,每当有新连接到来才开始创建提供服务的新线程
- 若有大量的客户端请求,此时服务端要为每一个客户端创建对应的服务线程。计算机中的线程越多,CPU的压力越大
线程池
- 在服务端预先创建一批线程,当有客户端请求连接时就让这些线程为客户端提供服务,此时客户端一来就有线程为其提供服务,而不是当客户端来了才创建对应的服务线程(减少了频繁创建线程的开销)
- 当某个线程为客户端提供完服务后,不要让该线程退出,而是让该线程继续为下一个客户端提供服务,如果当前没有客户端连接请求,则可以让该线程先进入休眠状态,当有客户端连接到来时再将该线程唤醒。
- 服务端创建的这一批线程的数量不能太多,此时CPU的压力也就不会太大
task.hpp
#pragma once
#include<iostream>
#include<string>
#include<unistd.h>
#include<string.h>
#include<functional>
using namespace std;using cb_t=function<void(int sock, const string&,const uint16_t&)>;class Task
{
public:Task(){}Task(int sock, const string& ip,const uint16_t&port,cb_t cb):_sock(sock),_ip(ip),_port(port),_cb(cb){}void operator()(){_cb(_sock,_ip,_port);}~Task(){}private:int _sock;string _ip;uint16_t _port;cb_t _cb;
};
LockGuard.hpp
#include<iostream>
#include<pthread.h>
using namespace std;class Mutex //自己不维护锁,由外部传入
{
public:Mutex(pthread_mutex_t* mutex):_pmutex(mutex){}void lock(){pthread_mutex_lock(_pmutex);}void unlock(){pthread_mutex_unlock(_pmutex);}~Mutex(){}private:pthread_mutex_t* _pmutex; //锁的指针
};class LockGuard //自己不维护锁,由外部传入
{
public:LockGuard(pthread_mutex_t* mutex):_mutex(mutex){_mutex.lock();}~LockGuard(){_mutex.unlock();}private:Mutex _mutex; //锁的指针
};
thread.hpp
#include<iostream>
#include<string>
using namespace std;class Thread
{
public:typedef enum{NEW=0,RUNNING,EXITED}ThreadStatus;typedef void (*func_t)(void*); //函数指针, 参数是void*Thread(int num, func_t func, void*args):_tid(0),_status(NEW),_func(func),_args(args){char name[128];snprintf(name,sizeof(name),"thread-%d",num);_name=name;}int status() {return _status;}string threadname() {return _name;}pthread_t thread_id(){if(_status==RUNNING)return _tid;elsereturn 0;}// runHelper是不是类的成员函数, 而类的成员函数, 具有默认参数this, 需要static// void*runHelper(Thread*this, void*args) , 而pthread_create要求传的参数必须是: void*的, 即参数不匹配// 但是static会有新的问题: static成员函数, 无法直接访问类属性和其他成员函数static void*runHelper(void*args){Thread*ts=(Thread*)args; //就拿到了当前对象// _func(_args);(*ts)();}//仿函数void operator()(){_func(_args);}void run(){int n=pthread_create(&_tid,nullptr,runHelper,this); //this: 是当前线程对象Threadif(n!=0) exit(-1);_status=RUNNING;}void join(){int n=pthread_join(_tid,nullptr);if(n!=0){cerr<<" main thread join thread "<< _name << " error "<<endl;}_status=EXITED;}~Thread(){}private:pthread_t _tid;string _name;func_t _func; //线程未来要执行的回调void*_args; //调用回调函数时的参数ThreadStatus _status;
};
threadPool_v4.hpp
#include<iostream>
#include<memory>
#include<vector>
#include<queue>
#include<unistd.h>
#include"thread.hpp"
#include"lockGuard.hpp"
using namespace std;const static int N=5;template<class T>
class threadPool
{
public:pthread_mutex_t* getlock(){return &_lock;}void threadWait(){pthread_cond_wait(&_cond,&_lock);}void threadWakeup(){pthread_cond_signal(&_cond); // 唤醒在条件变量下等待的线程}bool isEmpty(){return _tasks.empty();}T popTask(){T t=_tasks.front();_tasks.pop();return t;}static void threadRoutine(void*args) {threadPool<T>*tp=static_cast<threadPool<T>*>(args);while(true){// 1. 检测有没有任务 --- 本质是看队列是否为空// --- 本质就是在访问共享资源 --- 必定加锁// 2. 有: 处理// 3. 无: 等待// 细节: 必定加锁T t;{LockGuard lockguard(tp->getlock());while (tp->isEmpty()){// 等待, 在条件变量下等待tp->threadWait();}t = tp->popTask(); // 把任务从公共区域拿到私有区域}// for test// 处理任务应不应该在临界区中处理, 不应该, 这是线程自己私有的事情t(); }}static threadPool<T> * getinstance(){if (instance == nullptr) // 为什么要这样? 提高效率, 减少加锁的次数{LockGuard lockguard(&instance_lock);if (instance == nullptr){cout<<"线程池单例形成"<<endl;instance = new threadPool<T>();instance->init();instance->start();}}return instance;}void init(){for(int i=0;i<_num;++i){_threads.push_back(Thread(i,threadRoutine,this));cout<<i<<" thread running"<<endl;}}void check(){for(auto&t:_threads){cout<<t.threadname()<<" running..."<<endl;}}void start(){for(auto&t:_threads){t.run();}}void pushTask(const T&t){LockGuard lockguard(&_lock);_tasks.push(t);threadWakeup();}~threadPool(){for(auto&t:_threads){t.join();}pthread_mutex_destroy(&_lock);pthread_cond_destroy(&_cond);}private:threadPool(int num=N):_num(num){pthread_mutex_init(&_lock,nullptr);pthread_cond_init(&_cond,nullptr);}threadPool(const threadPool<T>&tp)=delete;void operator=(const threadPool<T>&tp)=delete;private:vector<Thread> _threads; int _num; queue<T> _tasks; pthread_mutex_t _lock;pthread_cond_t _cond;static threadPool<T>*instance;static pthread_mutex_t instance_lock;
};template<class T>
threadPool<T> * threadPool<T>::instance=nullptr;template<class T>
pthread_mutex_t threadPool<T>::instance_lock=PTHREAD_MUTEX_INITIALIZER;
tcpServer.hpp
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include<sys/wait.h>
#include<pthread.h>
#include"err.hpp"
#include"threadPool_v4.hpp"
#include"task.hpp"
using namespace std;namespace ns_server
{static const uint16_t defaultport=8081;static int backlog=32;using func_t=function<string(const string&)>; // 回调函数,一种处理逻辑class TcpServer;class ThreadData{public:ThreadData(int fd, const string&ip,const uint16_t&port,TcpServer*ts):sock(fd),clientip(ip),clientport(port),current(ts){}public:int sock;string clientip;uint16_t clientport;TcpServer*current;};class TcpServer{public:TcpServer(func_t func, uint16_t port=defaultport):func_(func),port_(port),quit_(true){}void InitServer(){// 1. 创建socket文件listensock_=socket(AF_INET,SOCK_STREAM,0);if(listensock_<0){cerr<<"create socket error"<<endl;exit(SOCKET_ERR);}// 2. bindstruct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_port=htons(port_);local.sin_family=AF_INET;local.sin_addr.s_addr=INADDR_ANY;int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));if(n<0){cerr<<"bind socket error"<<endl;exit(BIND_ERR);}// 3. 监听int m=listen(listensock_,backlog);if(m<0){cerr<<"listen socket error"<<endl;exit(LISTEN_ERR);}}void Start(){quit_=false;while(!quit_){struct sockaddr_in client;socklen_t len=sizeof(client);// 4. 获取连接, acceptint sock=accept(listensock_,(struct sockaddr*)&client,&len);if (sock < 0) // accept失败并不会终止进程, 只要获取下一个连接{cerr << "accept error" << endl;continue;}// 提取client信息 --- debugstring clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格uint16_t clientport=ntohs(client.sin_port); // 网络序列转主机序列// 5. 获取新连接成功后, 开始进行业务处理cout<<"获取新连接成功: "<<sock<<" from "<<listensock_<<", "<< clientip << "- " <<clientport<<endl;// v4: 线程池版本 // 一旦用户来了,你才创建线程, 线程池吗// 使用线程池的时候, 一定是有限的线程个数, 一定要处理短任务Task t(sock,clientip,clientport, bind(&TcpServer::service, this,placeholders::_1,placeholders::_2,placeholders::_3));threadPool<Task>::getinstance()->pushTask(t);}}static void*threadRoutine(void*args){pthread_detach(pthread_self());ThreadData*td=static_cast<ThreadData*>(args);td->current->service(td->sock,td->clientip,td->clientport);delete td;}// 流式 - 利用read和writevoid service(int sock, const string&clientip,const uint16_t clientport){string who=clientip + "-" + to_string(clientport);char buffer[1024];ssize_t s = read(sock, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;string res = func_(buffer); // 进行回调cout << who << ">>> " << res << endl;// 把收到的消息返回(写给客户端)write(sock, res.c_str(), res.size());}else if (s == 0){cout << who << " quit, me too" << endl;}else{close(sock);cerr << "read error: " << strerror(errno) << endl;}close(sock);}~TcpServer(){}private:uint16_t port_;int listensock_;bool quit_; // 标志服务器是否运行字段func_t func_;};
}
tcpServer.cc
#include"tcpServer.hpp"
using namespace ns_server;// ./tcp_server port// 使用手册
static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}string echo(const string&message)
{return message;
}int main(int argc,char*argv[])
{if(argc!=2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);unique_ptr<TcpServer> tsvr(new TcpServer(echo,port));tsvr->InitServer();tsvr->Start();return 0;
}
tcpClient.cc
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include"err.hpp"
using namespace std;static void usage(string proc)
{cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{// 准备工作if(argc!=3){usage(argv[0]);exit(USAGE_ERR);}string serverip=argv[1];uint16_t serverport=atoi(argv[2]);// 1.创建套接字int sock=socket(AF_INET,SOCK_STREAM,0);if (sock < 0){cerr << "create socket error: " << strerror(errno) << endl;exit(SOCKET_ERR);}// (2) 客户端要不要bind呢? 要// 要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind// (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接// 2. connect 客户端向服务器发起连接请求struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_port=htons(serverport);server.sin_family=AF_INET;// server.sin_addr.s_addr=INADDR_ANY; 绝对不是inet_aton(serverip.c_str(),&(server.sin_addr)); // 字符串风格ip转成点分十进制int cnt=5;while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0) // 连接失败{sleep(1);cout<<"正在给你重连, 重连次数还有: "<<cnt--<<endl;if(cnt<=0)break;}if(cnt<=0){cerr<<"连接失败"<<endl;exit(CONNECT_ERR);}char buffer[1024];// 3. 连接成功while(true){string line;cout<<"Enter>> ";getline(cin,line);write(sock,line.c_str(),line.size());ssize_t s = read(sock, buffer, sizeof(buffer)-1);if (s > 0){buffer[s] = 0;cout<<"server echo >>>"<<buffer<<endl;}else if (s == 0){cerr << "server quit" << endl;break;}else{cerr << "read error: " << strerror(errno) << endl;break;}}close(sock);return 0;
}
运行结果:
ip serverport\n" <<endl;
}
// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
// 准备工作
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
string serverip=argv[1];
uint16_t serverport=atoi(argv[2]);// 1.创建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if (sock < 0)
{cerr << "create socket error: " << strerror(errno) << endl;exit(SOCKET_ERR);
}// (2) 客户端要不要bind呢? 要
// 要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind
// (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接// 2. connect 客户端向服务器发起连接请求
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_port=htons(serverport);
server.sin_family=AF_INET;
// server.sin_addr.s_addr=INADDR_ANY; 绝对不是
inet_aton(serverip.c_str(),&(server.sin_addr)); // 字符串风格ip转成点分十进制int cnt=5;while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0) // 连接失败
{sleep(1);cout<<"正在给你重连, 重连次数还有: "<<cnt--<<endl;if(cnt<=0)break;
}
if(cnt<=0)
{cerr<<"连接失败"<<endl;exit(CONNECT_ERR);
}char buffer[1024];
// 3. 连接成功
while(true)
{string line;cout<<"Enter>> ";getline(cin,line);write(sock,line.c_str(),line.size());ssize_t s = read(sock, buffer, sizeof(buffer)-1);if (s > 0){buffer[s] = 0;cout<<"server echo >>>"<<buffer<<endl;}else if (s == 0){cerr << "server quit" << endl;break;}else{cerr << "read error: " << strerror(errno) << endl;break;}
}close(sock);
return 0;
}
运行结果:
相关文章:

网络编程套接字(3): 简单的TCP网络程序
文章目录 网络编程套接字(3)4. 简单的TCP网络程序4.1 服务端创建(1) 创建套接字(2) 绑定端口(3) 监听(4) 获取新连接(5) 处理读取与写入 4.2 客户端创建(1)连接服务器 4.3 代码编写(1) v1__简单发送消息(2) v2_多进程版本(3) v3_多线程版本(4) v4_线程池版本 网络编程套接字(3)…...

springMVC之拦截器
文章目录 前言一、拦截器的配置二、拦截器的三个抽象方法三、多个拦截器的执行顺序总结 前言 拦截器 一、拦截器的配置 SpringMVC中的拦截器用于拦截控制器方法的执行 SpringMVC中的拦截器需要实现HandlerInterceptor SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置&…...

docker搭建个人网盘和私有仓库Harbor
目录 1、使用mysql:5.7和 owncloud 镜像,构建一个个人网盘 2、安装搭建私有仓库 Harbor 1、使用mysql:5.7和owncloud,构建一个个人网盘 1.拉取mysql:5.6镜像,并且运行mysql容器 [rootnode8 ~]# docker pull mysql:5.7 [rootnode8 ~]# doc…...

智慧排水监测系统,科技助力城市排水治理
城市里,人们每天通过道路通行,人多,路窄,都会拥堵。同样,下雨天,雨水通过雨篦汇集、管道输送,最终排出去,当雨水过大,或者管道过窄,或者管道不通畅࿰…...

部署java程序的服务器cpu过高如何排查和解决
1.top命令找到占用CPU高的Java进程PID 2.根据进程ID找到占用CPU高的线程 ps -mp pid -o THREAD,tid | sort -r ps -mp 124682 -o THREAD,tid | sort -r 3.将指定的线程ID输出为16进制格式 printf “%x\n” tid printf "%x\n" 6384 18f0 4.jstack pid |…...

合宙Air724UG LuatOS-Air LVGL API控件--按钮 (Button)
按钮 (Button) 按钮控件,这个就不用多说了,界面的基础控件之一。 示例代码 – 按键回调函数 event_handler function(obj, event) if event lvgl.EVENT_CLICKED then print(“Clicked\n”) elseif event lvgl.EVENT_VALUE_CHANGED then print(“To…...

new/delete与malloc/free的区别
new/delete与malloc/free的区别 new、delete是C中的操作符,而malloc、free是标准库函数。 new 和 delete 是类型安全的,它们能够根据要分配的对象类型进行内存分配和释放,并调用相应的构造函数和析构函数。而 malloc 和 free 则是无类型的&am…...

QT listWidget 中实现元素的自由拖拽
QListWIdget中拖拽元素移动 setMovement(QListView::Movement::Free);setDragEnabled(true); setDragDropMode(DragDropMode::DragDrop); setDefaultDropAction(Qt::DropAction::MoveAction);...

ChatGPT AIGC 完成二八分析柏拉图的制作案例
我们先让ChatGPT来总结一下二八分析柏拉图的好处与优点 同样ChatGPT 也可以帮我们来实现柏拉图的制作。 效果如下: 这样的按年份进行选择的柏拉图使用前端可视化的技术就可以实现。 如HTML,JS,Echarts等,但是代码可以让ChatGPT来做,生成。 在ChatGPT中给它一个Prompt …...

Python 分析HTTP的可靠性
在这篇文章中,我们将介绍如何使用 Python 来分析代理服务提供商的可靠性。代理服务在许多场景中都非常有用,例如突破地理限制、保护隐私和提高网络安全性。然而,并非所有的代理服务提供商都是可靠的。因此,我们将使用 Python 来测…...

数据库连接报错CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
数据库连接报错CannotGetJdbcConnectionException: Failed to obtain JDBC Connection 报错信息 [Namecom.primeton.esb.online.restaurant.ms.online.mediaService.mediaService.biz][activity nameJDBC调用][activity idinvokePojo9] throw an exception:java.lang.Excepti…...

【Linux系列】vmware虚拟机网络配置详解
非原创 原文地址[1] 首发博客地址[2] 系列文章地址[3] vmware 为我们提供了三种网络工作模式,它们分别是:Bridged(桥接模式)、NAT(网络地址转换模式)、Host-Only(仅主机模式)。 打开…...

AUTOSAR规范与ECU软件开发(实践篇)7.8 MCAL模块配置方法及常用接口函数介绍之Icu的配置
目录 1、前言 2 、Icu模块 (1) Icu General配置 (2) IcuConfigSet配置 (3) IcuConfigSet配置 1、前言 本例程的硬件平台为MPC5744P开发板&...

2023-9-2 Prim算法求最小生成树
题目链接:Prim算法求最小生成树 #include <iostream> #include <cstring> #include <algorithm>using namespace std;const int N 510, INF 0x3f3f3f3f;int n, m; int g[N][N]; int dist[N]; bool st[N];int prim() {memset(dist, 0x3f, size…...

骨传导耳机会影响听力吗?这是真的吗?
首先正常的使用骨传导耳机并不会影响我们的听力!那是为什么呢?? 因为骨传导是一种声音传导方式,可以通过人的颅骨、骨迷路、内耳淋巴液传递、螺旋器、听神经、听觉中枢来传递声波。 相对于通过耳道声波的经典声音传导方式&#x…...

【华为OD机试python】 阿里巴巴找黄金宝箱(Ⅱ)【2023 B卷|100分】
题目描述 一贫如洗的樵夫阿里巴巴在去砍柴的路上,无意中发现了强盗集团的藏宝地, 藏宝地有编号从0-N的箱子,每个箱子上面贴有箱子中藏有金币的数量。 从金币数量中选出一个数字集合,并销毁贴有这些数字的每个箱子, 如果能销毁一半及以上的箱子,则返回这个数字集合的最小大…...

9.6 【C语言】使用枚举类型
如果一个变量只有几种可能的值,则可以定义为枚举类型,所谓“枚举”就是指把可能的值一一列举出来,变量的值只限于列举出来的值的范围内。 声明枚举类型用enum开头,例如: enum Weekday{sun,mon,tue,wed,thu,fri,sar};…...

一文了解tcp/ip协议的运行原理
接触代理ip的人都了解https/sock5等ip协议,那么TCP/IP 协议又是什么? 一、什么是TCP/IP 协议? TCP/IP 协议实际上是一系列网络通信协议的一个统称,他负责具体的数据传输工作,核心的两个协议包括TCP以及IP,…...

spring cloud alibaba
项目依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.5.RELEASE</version></parent><properties><springcloud.alibaba.version>2.1…...

K 次取反后最大化的数组和【贪心算法】
1005 . K 次取反后最大化的数组和 给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组: 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后,返回数组 可能…...

pulsar集群搭建_亲测成功
pulsar集群搭建_亲测成功 单机运行请看: Linux MacBook单机部署Pulsar并开启认证功能 集群组成 搭建 Pulsar 集群至少需要 3 个组件:ZooKeeper 集群、BookKeeper 集群和 broker 集群(Broker 是 Pulsar 的自身实例)。这三个集群组件如下: …...

笔记:linux中LED驱动设备树配置和用法
设备树中节点配置 设备树中的LED驱动一般是这样写,LED驱动可以控制GPIO的电平变化,生成文件节点很方便 leds: leds {compatible "gpio-leds";gpio_demo: gpio_demo {label "gpio_demo";gpios <&gpio0 RK_PC0 GPIO_ACTIV…...

Linux网络编程 网络基础知识
目录 1.网络的历史和协议的分成 2.网络互联促成了TCP/IP协议的产生 3.网络的体系结构 4.TCP/IP协议族体系 5.网络各层的协议解释 6.网络的封包和拆包 7.网络预备知识 1.网络的历史和协议的分成 Internet-"冷战"的产物 1957年十月和十一月,前苏…...

盘点狼人杀中的强神与弱神 并评价操作体验
最初 强神是大家对猎人的称呼,但随着板子的增加 强神渐渐变成了强神神牌的统称。 狼人杀发展至今板子已经非常多了,而每个板子都会有不同的角色。 相同的是 大部分都会希望拿到一张强力神牌,这样能大大提高我们玩家的游戏体验,但其…...

数据结构与算法学习(day1)
前言 (1)我是一个大三的学生(准确来说应该是准大三,因为明天才报名哈哈哈)。 (2)最近就想每天闲着没事也刷些C语言习题来锻炼下编程水平,也一直在思考企业对应届大学生能力的要求,所以经常会想到关于面试的事情。由于我也没实习过,所以我对面试没有一个具象化的概念。…...

递归寻找第n位数字
编写递归函数digit(n,j),返回整数n的从右边开始的第j位数字 首先来看非递归法,只需用n/(10^(j-1))%10即可 #include<stdio.h> //编写递归函数digit(n,j),返回整数n的从右边开始的第j位数字 int digit(int n,i…...

[国产MCU]-W801开发实例-WiFi热点模式创建
WiFi热点模式创建 文章目录 WiFi热点模式创建1、创建WiFi热点相关API介绍2、热点创建实例W801的WiFi支持热点模式。本文将详细介绍如何创建热点模式。 1、创建WiFi热点相关API介绍 int tls_wifi_softap_create(struct tls_softap_info_t apinfo,struct tls_ip_info_t ipinfo)**…...

云原生Kubernetes:二进制部署K8S单Master架构(二)
目录 一、理论 1.K8S单Master架构 2.部署 master 组件 3.部署 Woker Node 组件 4.在master1节点上操作 5.在 node01 节点上操作 6.在 master01 节点上操作 7.在 node01 节点上操作 8.node02 节点部署(方法一) 二、实验 1.环境 2.部署 master …...

spring高级源码50讲-43-50(spring续)
其它 43) FactoryBean 演示 - FactoryBean 代码参考 package com.itheima.a43;import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan;ComponentScan public class A43 {publi…...

FTP文件传输服务器
目录 一、FTP协议两种工作模式 二、FTP数据两种传输模式 三、FTP用户分类 四、VSFTP配置案例 4.1匿名开放模式 4.2本地用户模式 4.3虚拟用户模式 五、实验总结 一、FTP协议两种工作模式 主动模式: 1、客户端主动向ftp服务器发送控制连接,三次握手控制连接…...