在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址
源IP地址: 表示该条信息来源于哪个机器。
目的IP地址: 表示该条信息去往于哪个进程
我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这两者之间是怎样的关系?
端口号(port)是传输层协议的内容
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 “数据是谁发的, 要发给谁”
源端口号: 表示该条信息来源于哪个进程。
目的端口号: 表示该条信息去往于哪个机器。
此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识,后面再详细讨论
这里我们也先对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识;,后面再详细讨论
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换
#include
uint16_t htons(uint16_t hostshort)
uint32_t htonl(uint32_t hostlong)
uint16_t ntohs(uint16_t netshort)
uint32_t ntohl(uint32_t netlong)
这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节序转换为网络字节序,将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr * address,socklen_t address_len);*
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr*addr,socklen_t addrlen);
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、 IPv6,以及UNIX Domain Socket。然而, 各种网络协议的地址格式并不相同。
sockaddr 结构用于存储参与(IP)Windows/linux套接字通信的计算机上的一个internet协议(IP)地址。为了统一地址结构的表示方法 ,统一接口函数,使得不同的地址结构可以被bind()、connect()、recvfrom()、sendto()等函数调用。但一般的编程中并不直接对此数据结构进行操作,而使用另一个与之等价的数据结构sockaddr_in。这是由于Microsoft TCP/IP套接字开发人员的工具箱仅支持internet地址字段,而实际填充字段的每一部分则遵循sockaddr_in数据结构,两者大小都是16字节,所以二者之间可以进行切换。
sockaddr_in 结构
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。
in_addr结构
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。
下面介绍程序中用到的socket API,这些函数都在sys/socket.h中
socket()
bind()
我们的程序中对myaddr参数是这样初始化的
bzero ( &servaddr , sizeof ( servaddr ) ) ;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s _addr= hton1 (INADDR_ANY ) ;
servaddr.sin port = htons ( SERV_PORT);
listen()
accept()
服务器程序结构是这样的
while ( 1 ){
cliaddr_len = sizeof( cliaddr ) ;
connfd =accept ( listenfd,(struct sockaddr * ) &cliaddr , &cliaddr_len ) ;
n = read ( connfd, buf,MAXLINE);
...
close( connfd ) ;
}
connect
查看tcp相关信息可以用如下指令 netstat -nltp
下面实现一个简单的网络通信程序
客户端文件 udp_client.cc
#include
#include
#include
#include
#include
#include
#include
#include
// ./udp_client desc_ip desc_port
// ./udp_client 42.123.43.123 8080
void Usage(std::string proc)
{
std::cerr << "Usage: " << "\n\t" << proc << " desc_ip desc_port" << std::endl;
}
int main(int argc,char* argv[])
{
if( argc != 3 ){
Usage(argv[0]);
return 1;
}
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0){
std::cerr<<"socket error"<<std::endl;
return 2;
}
//bind,client端,不需要明确bind,原因??
//需不需要bind??需要
//不需要用户去主动bind,实际上,在sendto的时候,OS会自动随机给client bind端口号
char buffer[1024];
struct sockaddr_in desc;
memset(&desc,0,sizeof(desc));
desc.sin_family=AF_INET;
desc.sin_port = htons(atoi(argv[2]));
desc.sin_addr.s_addr = inet_addr(argv[1]);
for( ; ; ){
std::cout<<"Please Enter# "<< std::endl;
fflush(stdout);
buffer[0]=0;
ssize_t size=read(0,buffer,sizeof(buffer)-1);
if(size>0){
buffer[size-1]=0;
//std::cout<<"echo# "<
sendto(sock,buffer,strlen(buffer),0,(struct sockaddr*)&desc/*发送到哪里*/,sizeof(desc)/*长度*/);
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
ssize_t s=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);//peer,len 暂时不用
if(s>0){
buffer[s]=0;
std::cout<<"#echo "<<buffer<<std::endl;
}
}
}
close(sock);
return 0;
}
服务端文件 udp_server.cc
#include
#include
#include
#include
#include
#include
#include
#define PORT 8081
void Usage(std::string proc)
{
std::cerr << "Usage: " << "\n\t" << proc << " local_port" << std::endl;
}
// ./udp_server port
int main(int argc,char *argv[])
{
if(argc!=2){
Usage(argv[0]);
return 1;
}
int sock=socket(AF_INET, SOCK_DGRAM, 0);
if(sock<0){
std::cerr<<"socket error"<<std::endl;
return 2;
}
std::cout<<"sock: "<<sock<<std::endl;
//该结构是OS给你提供的一个结构体,用户层定义的,local是属于main函数内的一个临时变量
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1])); //后续网络端口,会以源端口的方式,发送给对面
//注意: 云服务器你要bind的时候的,一般不能直接绑定明确的IP,INADDR_ANY
//非常推荐使用INADDR_ANY, bind所有你的机器上面的ip
local.sin_addr.s_addr = htonl(INADDR_ANY); //一个IP本质,可以使用4个字节进行保存[0-255].[0-255].[0-255].[0-255], "42.192.83.143",点分十进制字符串风格IP
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){//就是将本主机相关的ip,端口,协议家族等信息写入到特定的fd标定的文件中
std::cerr<<"bind error"<<std::endl;
return 1;
}
char message[1024];
for( ; ; ){
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
ssize_t s=recvfrom(sock,message,sizeof(message)-1,0,(struct sockaddr*)&peer,&len);
if(s>0){
message[s]='\0';
std::cout << "client# " << message << std::endl;
std::string echo_message = message;
echo_message += "_server_";
echo_message += std::to_string((long long)time(nullptr));
//std::cout<<"client# " << message << std::endl;
sendto(sock,echo_message.c_str(),echo_message.size(),0,(struct sockaddr*)&peer,len);
}
else{
}
}
close(sock);
return 0;
}
Server端,为何要明确bind ?
client:server = n:1, server给别人提供服务,就需要自己尽可能的将自己暴露出去(IP(域名)+PORT(一般是被隐藏的)),必须是“稳定”(不能轻易改变,尤其是端口号)的。
client为何不需要明确bind?
如果client没有port,也变无法与server进行通信。
为何不需要我们给他bind呢?
如果你自己bind了,成功了还好,如果你的client端口被别的程序占用,你的client就无法启动,客户端不是一定要用哪一个端口,只需要有一个端口就可以。我们一般不自己bind,而是由OS随机帮我们查找端口.
下面编写TCP网络程序实现通信
handler.hpp文件
#pragma once
#include"tcp_server.hpp"
namespace ns_handler{
using namespace ns_tcpserver;
#define SIZE 1024
void HandlerHelper(int sock)
{
while(true){
char buffer[1024];
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0){
// read success
buffer[s]=0;
std::cout<<"clinet# "<<buffer<<std::endl;
std::string echo_string =buffer;
if(echo_string=="quit"){
break;
}
echo_string +="[server say]";
write(sock,echo_string.c_str(),echo_string.size());
}
else if(s==0){
//对端链接关闭
std::cout << sock << " : client quit ..." << std::endl;
break;
}
else{
// 读取失败
std::cerr << "read error" << std::endl;
break;
}
}
}
void HandlerSock_V1(int sock)
{
HandlerHelper(sock);
}
}
tcp_client.hpp文件
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ns_tcpclient{
class TcpClient{
private:
std::string desc_ip;// client要访问的对端服务器的IP地址
uint16_t desc_port;//client要访问的对端服务器的port端口号
int sock;
public:
TcpClient(std::string _ip,uint16_t _port):desc_ip(_ip),desc_port(_port),sock(-1)
{}
void InitTcpClient()
{
//创建socket
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0){
std::cerr<<"socket error"<<std::endl;
exit(2);
}
//2. client要不要bind??不要自己进行bind!在你发起链接的时候,OS会自动给你进行相关的绑定!
//3. client要不要listen?不需要!
//4. client要不要accept?不需要!
}
//tcp是面向连接的!client 要通信之前必须先连接
void Start()
{
//填充对方服务器的socket信息
struct sockaddr_in svr;
bzero(&svr,sizeof(svr));
svr.sin_family= AF_INET;
svr.sin_port = htons(desc_port);
svr.sin_addr.s_addr = inet_addr(desc_ip.c_str());
// 发起链接请求
if(connect(sock,(struct sockaddr*)&svr,sizeof(svr))==0){
std::cout<<"connect success..."<<std::endl;
}
else{
std::cout<<"connect failed..."<<std::endl;
}
// 完成业务逻辑
while(true){
char buffer[1024]={0};
std::cout << "请你输入# ";
fflush(stdout);
ssize_t s=read(0,buffer,sizeof(buffer)-1);
if(s>0){
buffer[s-1]=0;
write(sock,buffer,strlen(buffer));
size_t rs=read(sock,buffer,sizeof(buffer)-1);
if(rs>0){
buffer[rs]=0;
std::cout<<buffer<<std::endl;
}
else{
std::cout<<"server close..."<<std::endl;
}
}
}
}
~TcpClient()
{
if(sock>=0) close(sock);
}
};
}
client.cc
#include "tcp_client.hpp"
static void Usage(std::string proc)
{
std::cerr << "Usage: " << "\n\t" << proc << " svr_ip svr_port" << std::endl;
}
// ./tcp_client peer_ip peer_port
int main(int argc, char *argv[])
{
if(argc != 3){
Usage(argv[0]);
return 1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
ns_tcpclient::TcpClient cli(ip, port);
cli.InitTcpClient();
cli.Start();
return 0;
}
tcp_server.hpp文件
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
namespace ns_tcpserver{
typedef void(*handler_t)(int);//函数指针类型
const int backlog = 5;
class TcpServer{
private:
uint16_t port;
int listen_sock; //获取新链接
public:
TcpServer(int _port):port(_port), listen_sock(-1)
{}
void InitTcpServer()
{
listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0){
std::cout << "socket error" << std::endl;
exit(2);
}
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
std::cerr << "bind error" << std::endl;
exit(3);
}
//3. 监听,tcp协议是面向连接的,即如果要正式传递数据之前,需要先建立链接
//目的:允许client来连接server
if(listen(listen_sock, backlog) < 0){
std::cerr << "listen error" << std::endl;
exit(4);
}
}
// 自己设计的回调机制
void Loop(handler_t hander)
{
while(true){
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//1. 获取连接
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if(sock < 0){
std::cout << "warning: accept error" << std::endl;
continue;
//break;//不能
}
//debug,验证一下fd值
std::cout << "debug: sock->"<< sock << std::endl;
uint16_t peer_port = ntohs(peer.sin_port);
std::string peer_ip = inet_ntoa(peer.sin_addr); //4字节ip->点分十进制字符串风格的IP
//验证一下对端的socket信息中,ip,port
std::cout << "debug: " << peer_ip << ":" << peer_port << std::endl;
//使用sock进行通信
//处理链接
hander(sock);
//关闭链接
close(sock);
}
}
~TcpServer()
{
if(listen_sock >= 0) close(listen_sock);
}
};
}
server.cc
#include"tcp_server.hpp" //提供网络连接功能
#include"handler.hpp" //提供网络sock的处理功能
static void Usage(std::string proc)
{
std::cerr<<"Usage: "<<"\n\t"<<proc<<" prot"<<std::endl;
}
//./server port
int main(int argc,char *argv[])
{
// 这是一个简单的命令行参数的验证
if(argc!=2){
Usage(argv[0]);
return 0;
}
uint16_t port=atoi(argv[1]);
ns_tcpserver::TcpServer* svr=new ns_tcpserver::TcpServer(port);
svr->InitTcpServer();
svr->Loop(ns_handler::HandlerSock_V1);
return 0;
}
上面的程序一次只能有一个客户端和服务端通信,能不能让服务端一次和多个客户端通信呢?这里可以利用多进程
//肯定是进程要执行handler方法
void HandlerSock_V2(int sock)
{
//多进程
if(fork()==0){
//child
if(fork()>0){
//child
exit(0);
}
//孙子进程,孤儿进程,会被OS领养
HandlerHelper(sock);
exit(0);
}
//father
close(sock);
waitpid(-1,nullptr,0);
}
这里利用了孙子进程,避免了子进程需要父进程等待的问题
void* thread_routine(void* args)
{
int sock=*(int*)args;
delete (int*)args;
pthread_detach(pthread_self());
HandlerHelper(sock);//业务逻辑
//已经处理完毕,需要关闭不需要的fd,如果不关闭,就造成了文件描述符泄漏!
close(sock);
return nullptr;
}
void HandlerSock_V3(int sock)
{
// 多线程
pthread_t tid;
int* p=new int(sock);
pthread_create(&tid,nullptr,thread_routine,p);
}
创建进程和线程的优缺点
多进程:链接来了,才创建进程,而且没有上限, 优点: 稳定,进程是具独立性的。缺点:进程创建和交互的成本高,效率变低。
多线程:链接来了,才创建线程,而且没有上限, 优点: 轻量化。缺点:健壮性不足(一个线程崩掉整个进程都会崩掉)一旦系统中的进程或者线程极度增多,进程或者线程在系统内切换的成本增加,切换的周期变长了,甚至导致系统宕机。
tcp_server.hpp文件
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
namespace ns_tcpserver{
typedef void (*handler_t)(int); //函数指针类型
const int backlog = 5;
class TcpServer{
private:
uint16_t port;
int listen_sock; //获取新链接
public:
TcpServer(int _port):port(_port), listen_sock(-1)
{}
void InitTcpServer()
{
listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0){
std::cout << "socket error" << std::endl;
exit(2);
}
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
std::cerr << "bind error" << std::endl;
exit(3);
}
//3. 监听,tcp协议是面向连接的,即如果要正式传递数据之前,需要先建立链接
//目的:允许client来连接server
if(listen(listen_sock, backlog) < 0){
std::cerr << "listen error" << std::endl;
exit(4);
}
}
//自己设计的回调机制
void Loop(handler_t handler)
{
//listen_sock
//signal(SIGCHLD, SIG_IGN);
while(true){
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//4. 获取连接, sock
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if(sock < 0){
std::cout << "warning: accept error" << std::endl;
continue;
//break;//不能
}
//debug,验证一下fd值
std::cout << "debug: sock->"<< sock << std::endl;
uint16_t peer_port = ntohs(peer.sin_port);
std::string peer_ip = inet_ntoa(peer.sin_addr); //4字节ip->点分十进制字符串风格的IP
//验证一下对端的socket信息中,ip,port
std::cout << "debug: " << peer_ip << ":" << peer_port << std::endl;
//使用sock进行通信
//5. 处理链接
handler(sock);
//6. 关闭链接--暂时
//close(sock);
}
}
~TcpServer()
{
if(listen_sock >= 0) close(listen_sock);
}
};
}
server.cc文件
#include "tcp_server.hpp" //提供网络连接功能
#include "handler.hpp" //提供网络sock的处理功能
static void Usage(std::string proc)
{
std::cerr << "Usage:" << "\n\t" << proc << " port" << std::endl;
}
// ./server port
int main(int argc, char *argv[])
{
//这是一个简单的命令行参数的验证
if(argc != 2){
Usage(argv[0]);
return 0;
}
uint16_t port = atoi(argv[1]);
ns_tcpserver::TcpServer *svr = new ns_tcpserver::TcpServer(port);
svr->InitTcpServer();
//svr->Loop(ns_handler::HandlerSock_V1);
//svr->Loop(ns_handler::HandlerSock_V2);
//svr->Loop(ns_handler::HandlerSock_V3);
svr->Loop(ns_handler::HandlerSock_V4);
return 0;
}
tcp_cilent.hpp文件
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ns_tcpclient{
class TcpClient{
private:
std::string desc_ip; //client要访问的对端服务器的IP地址
uint16_t desc_port; //client要访问的对端服务器的port端口号
int sock;
public:
TcpClient(std::string _ip, uint16_t _port):desc_ip(_ip), desc_port(_port), sock(-1)
{}
void InitTcpClient()
{
//1. 创建socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
std::cerr << "socket error" << std::endl;
exit(2);
}
//2. client要不要bind??不要自己进行bind!在你发起链接的时候,OS会自动给你进行相关的绑定!
//3. client要不要listen?不需要!
//4. client要不要accept?不需要!
}
//tcp是面向连接的!client 要通信之前必须先连接
void Start()
{
//填充对方服务器的socket信息
struct sockaddr_in svr;
bzero(&svr, sizeof(svr));
svr.sin_family = AF_INET;
svr.sin_port = htons(desc_port);
svr.sin_addr.s_addr = inet_addr(desc_ip.c_str());
//2. 发起链接请求
if(connect(sock, (struct sockaddr*)&svr, sizeof(svr)) == 0){
std::cout << "connect success ..." << std::endl;
}
else{
std::cout << "connect failed ..." << std::endl;
return;
}
//3. 完成业务逻辑
while(true){
char buffer[1024] = {0};
std::cout << "请你输入# ";
fflush(stdout);
ssize_t s = read(0, buffer, sizeof(buffer)-1);
if(s > 0){
buffer[s-1] = 0;
write(sock, buffer, strlen(buffer));
ssize_t rs = read(sock, buffer, sizeof(buffer)-1);
if(rs > 0){
buffer[rs] = 0;
std::cout << buffer << std::endl;
}
else{
std::cout << "server close ..." << std::endl;
break;
}
}
}
}
~TcpClient()
{
if(sock >= 0) close(sock);
}
};
}
client.cc文件
#include "tcp_client.hpp"
static void Usage(std::string proc)
{
std::cerr << "Usage: " << "\n\t" << proc << " svr_ip svr_port" << std::endl;
}
// ./tcp_client peer_ip peer_port
int main(int argc, char *argv[])
{
if(argc != 3){
Usage(argv[0]);
return 1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
ns_tcpclient::TcpClient cli(ip, port);
cli.InitTcpClient();
cli.Start();
return 0;
}
thread_pool.hpp线程池文件
#pragma once
#include
#include
#include
template <class T>
class ThreadPool{
private:
std::queue<T> q; //给线程池派发任务的地点, 临界资源
pthread_mutex_t lock;
pthread_cond_t cond;
private:
ThreadPool()
{
pthread_mutex_init(&lock, nullptr);
pthread_cond_init(&cond, nullptr);
}
ThreadPool(const ThreadPool<T>&) = delete;
ThreadPool<T>& operator = (const ThreadPool<T>&) = delete;
static ThreadPool<T> *instance;
public:
static ThreadPool<T> *get_instance(int num)
{
//static std::mutex lock;
//lock.lock();
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
if(nullptr == instance){
pthread_mutex_lock(&mtx);
if(nullptr == instance){
instance = new ThreadPool<T>();
instance->InitThreadPool(num);
//instance->Init...
}
pthread_mutex_unlock(&mtx);
}
//lock.unlock();
return instance;
}
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);
}
void PopTask(T *out)
{
*out = q.front();
q.pop();
}
//Routinue是类中的一个成员方法!包含了一个隐士参数this!ThreadPool*
//实际上,这里是包含了两个参数的!
static void *Routinue(void *args/*,ThreadPool *this*/)
{
pthread_detach(pthread_self()); //线程分离
ThreadPool *tp = (ThreadPool*)args;
while(true){
tp->LockQueue();
//1. 检测是否有任务
//if -> while
while(tp->IsEmpty()){
//thread 应该等待,等待有任务
tp->ThreadWait(); //我们线程当前是在临界区内等待的!我是持有锁的!!!
}
//2. 取任务的过程
T t;
tp->PopTask(&t);
tp->UnlockQueue();
//3. 处理任务, 拿到任务之后,处理任务的时候,需要在临界区内处理吗?不需要
//在你的线程处理任务期间,其他线程是不是可以继续获取任务,处理任务
t();
}
}
void InitThreadPool(int num)
{
for(auto i = 0; i < num; i++){
pthread_t tid;
pthread_create(&tid, nullptr, Routinue, this);
}
}
void PushTask(const T &in)
{
//放任务
LockQueue();
q.push(in);
ThreadWakeup();
UnlockQueue();
}
~ThreadPool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
template<class T>
ThreadPool<T>* ThreadPool<T>::instance = nullptr;
handler.hpp文件
#pragma once
#include "thread_pool.hpp"
#include "tcp_server.hpp"
#include
#include
#include
#include
namespace ns_handler{
using namespace ns_tcpserver;
#define SIZE 1024
std::unordered_map<std::string, std::string> dict = {
{"apple" , "苹果"},
{"hello", "你好"},
{"bit", "比特"},
{"banana", "香蕉"},
};
void HandlerHelper(int sock)
{
char buffer[1024];
ssize_t s = read(sock, buffer, sizeof(buffer)-1);
if(s>0){
buffer[s] = 0;
std::cout << "client say# " << buffer <<std::endl;
std::string k = buffer;
std::string message = "我不知道";
auto iter = dict.find(k);
if(iter != dict.end()){
message = iter->second;
}
write(sock, message.c_str(), message.size());
std::cout << "server echo# " << message <<std::endl;
}
}
void HandlerSock_V1(int sock)
{
HandlerHelper(sock);
}
//肯定是进程要执行handler方法
void HandlerSock_V2(int sock)
{
//多进程
if(fork() == 0){
//child
if(fork() > 0){
//child
exit(0);
}
//孙子进程,孤儿进程,会被OS领养
HandlerHelper(sock);
exit(0);
}
//father
//close(sock);
waitpid(-1, nullptr, 0);
}
void *thread_routine(void *args)
{
//临时方案
int sock = *(int*)args;
delete (int*)args;
std::cout << "debug: " << sock << std::endl;
pthread_detach(pthread_self());
HandlerHelper(sock); //业务逻辑
//已经处理完毕,需要关闭不需要的fd,如果不关闭,就造成了文件描述符泄漏!
close(sock);
return nullptr;
}
void HandlerSock_V3(int sock)
{
//多线程版本
pthread_t tid;
int *p = new int(sock);
pthread_create(&tid, nullptr, thread_routine, p);
}
class task{
private:
int sock;
public:
task(){}
task(int _sock):sock(_sock)
{}
void operator()()
{
std::cout << "当前处理的线程是:" << pthread_self() << std::endl;
HandlerHelper(sock);
close(sock);
}
~task(){}
};
//线程池
void HandlerSock_V4(int sock)
{
ThreadPool<task>::get_instance(5)->PushTask(task(sock));
}
}
TCP协议通讯通讯的前提是先将客户端和服务端连接起来,这里的连接是指什么呢?
客户端连接服务器的时候,本质上是连接了服务器的操作系统(协议栈)模块。服务端和客户端是1:n的关系,所以一定会有多个客户端去连接一个服务器OS。
此时服务器OS上会有大量的客户端连接。服务器OS需要将所有连接管理起来(先描述,再组织)。
所谓的连接本质就是在双方OS内,维护对应的数据结构,建立了连接,后序也要维护连接,所以,建立连接是有成本的,消耗了时间和空间。
断开连接的本质:释放双方建立好的数据结构
下图是基于TCP协议的客户端/服务器程序的一般流程:
服务器初始化:
建立连接的过程:
这个建立连接的过程, 通常称为 三次握手
数据传输的过程
断开连接的过程:
这个断开连接的过程, 通常称为 四次挥手
在学习socket API时要注意应用程序和TCP协议层是如何交互的:
TCP是可靠传输,有连接,字节流
UDP是不可靠传输,无连接,数据报