---//tcp_server.hpp
#include
namespace ns_tcp_server
{
typedef void (*handler_t)(int);
void hand_add(int x)
{
++x;
std::cout<<x<<std::endl;
}
void hand_sub(int x)
{
--x;
std::cout<<x<<std::endl;
}
void loop(handler_t hand)
{
int a = 25;
hand(a);
}
}
---//main.cc
#include "tcp_server.hpp"
using namespace ns_tcp_server;
int main()
{
loop(hand_sub);
//loop(hand_sub);
return 0;
}
TCP 相比 UDP,流套接字(SOCK_STREAM)用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。
TCP服务端:首先需要创建一个监听套接字文件;接下来绑定IP地址和端口号到监听套接字文件;然后进行监听;最后获取连接,进行数据的收发和处理。
TCP客户端:首先创建套接字;不需要绑定;接着请求连接;然后完成数据的收发。
先创建的套接字一般叫做:监听套接字。相当于迎宾人员,真正进行通信的是获取连接后返回的套接字文件。
int socket(int domain, int type, int protocol);
,socket() 打开一个网络通讯端口,如果成功的话,就像 open ()一样返回一个文件描述符;
domain
:对于IPv4, family参数指定为 AF_INET
;type:
对于TCP协议,type参数指定为SOCK_STREAM
,表示面向流的传输协议#include
#include
listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0)
{
std::cout << "socket error" << std::endl;
exit(2);
}
绑定:服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后,就可以向服务器发起连接;;服务器需要调用bind绑定一个固定的网络地址和端口号。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
,bind() 成功返回0,失败返回-1。
sockfd
:上一步创建的返回值。const struct sockaddr *addr
:IPV4 就使用 struct sockaddr_in
;传参时强转成 struct sockaddr*
。
struct sockaddr_in.sin_addr.s_addr
,是用来绑定IP地址的,参数推荐设置成:INADDR_ANY
socklen_t addrlen
:上一个结构体的大小。INADDR_ANY
,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址,,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址。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);
}
listen()声明sockfd处于监听状态,,并且最多允许有backlog个客户端处于连接等待状态,,如果接收到更多的连接请求就忽略,,这里设置不会太大(一般是5)。
int listen(int sockfd, int backlog);
,listen()成功返回0,失败返回-1。const int backlog = 5;
if(listen(listen_sock,backlog)<0)
{
cerr << "listen error" << endl;
exit(3);
}
三次握手完成后,服务器调用 accept() 接受连接;如果服务器调用 accept() 时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
成功返回一个新的用于通信的套接字文件描述符;失败返回-1。
sockfd
:创建的监听套接字while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if(sock < 0)
{
std::cout << "warning: accept error" << std::endl;
continue;
}
}
客户端需要调用 connect() 连接服务器;connect和 bind 的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect()成功返回0,出错返回-1;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;
return;
}
read/write
在网络上收发数据。send()
和recv()
,
int send(SOCKET s,const char FAR *buf ,int len ,int flags);
int recv(SOCKET s ,char FAR * buf ,int len ,int flags);
sv.hpp头文件
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
using std::cout;
using std::endl;
using std::cin;
using std::cerr;
using std::string;
namespace ns_tcp_server
{
typedef void (*handler_t)(int);
const int backlog = 5;
class TcpSe
{
private:
uint16_t _port;
int listen_sock;
public:
TcpSe(uint16_t port)
:_port(port)
,listen_sock(-1)
{}
void InitSe()
{
listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
cerr << "socket error" << endl;
exit(1);
}
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)
{
cerr << "bind error" << endl;
exit(2);
}
//监听 正式传递数据之前,要先建立连接
if(listen(listen_sock,backlog)<0)
{
cerr << "listen error" << endl;
exit(3);
}
}
//循环获取连接,通信,处理数据
void Loop(handler_t hand)
{
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
if(sock < 0)
{
cout<< "warning : accept error" << endl;
continue;
}
uint16_t peer_port = ntohs(peer.sin_port);
string peer_ip = inet_ntoa(peer.sin_addr);//IP风格转换
cout << " debug: ip : "<< peer_ip <<" port: "<< peer_port<< endl;
hand(sock);
close(sock);//线程版本应该,让线程关闭
}
}
~TcpSe()
{
if(listen_sock >= 0)
{
close(listen_sock);
}
}
};
}
服务端sv.cc
#include "hd.hpp"
#include "sv.hpp"
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]);
// exit(1);
// }
uint16_t port = atoi("8081");
ns_tcp_server::TcpSe* svr = new ns_tcp_server::TcpSe(port);
svr->InitSe();
std::cout << "初始化完成"<<std::endl;
//svr->Loop(ns_handler::hand_commn);
//svr->Loop(ns_handler::hand_multiprocess);
//svr->Loop(ns_handler::hand_multithread);
svr->Loop(ns_handler::hand_tpool);
return 0;
}
hd.hpp
包含:单进程版本、多进程版本、多线程版本、线程池版本#include "sv.hpp"
#include "tpol.hpp"
#include
#include
#include
#include
#define SIZE 1024
namespace ns_handler
{
void hand_help(int sock)
{
cout << "回调函数被调用 debug: "<< sock << endl;
while(true)
{
char buff[SIZE];
ssize_t s = read(sock,buff,sizeof(buff)-1);
if(s>0)
{
buff[s]=0;
cout<< "client: "<<buff << endl;
string echo_message = buff;
if(echo_message == "quit")
{
break;
}
echo_message += "server says";
// cout<
send(sock,echo_message.c_str(),echo_message.size(),0);
//write(sock,echo_message.c_str(),echo_message.size());
}
else if (s==0)
{
cout <<sock << ": client quit----"<< endl;
break;
}
else{
cerr <<"read error"<< endl;
break;
}
}
}
void hand_commn(int sock)
{
hand_help(sock);
}
//让孙子进程去执行,主进程继续去获取连接;孙子进程继承父进程的fd,用完关闭即可。
void hand_multiprocess(int sock)
{
if(fork() == 0)
{
//child
if(fork()>0)
{
exit(0);
}
//grandson 孤儿进程 被os领养
hand_help(sock);
exit(0);
}
waitpid(-1,nullptr,0);
}
void* routine(void* args)
{
int sock = *(int*)args;
delete (int*)args;
cout<< "线程_sock:"<<sock<<endl;
pthread_detach(pthread_self());
hand_help(sock);
close(sock);
return nullptr;
}
void hand_multithread(int sock)
{
cout<<"sock: "<<sock<<endl;
pthread_t tid;
int* p = new int(sock);
pthread_create(&tid,nullptr,routine,p);
}
//线程池版本
class task
{
private:
int sock;
public:
task(){}
task(int sk):sock(sk){}
void operator()()
{
cout<<"当前处理客户数据的线程是"<<pthread_self()<<endl;
hand_help(sock);
close(sock);
}
~task(){}
};
void hand_tpool(int sock)
{
ThreadPool<task>::Get_inst(5)->pusht(task(sock));
}
}
tpol.hpp
#pragma once
#include
#include
#include
using std::queue;
template<class T>
class ThreadPool
{
private:
queue<T> q;
pthread_mutex_t lock;
pthread_cond_t cond;
public:
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>* inst;
public:
static ThreadPool<T>* Get_inst(int num)
{
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
if(nullptr == inst)
{
pthread_mutex_lock(&mtx);
if(nullptr == inst)
{
inst = new ThreadPool<T>();
inst->InTp(num);//线程池创建 num个 线程 申请及创建
}
pthread_mutex_unlock(&mtx);
}
return inst;
}
static void* Run(void* args)//类的内部所以要设置成静态的
{//线程执行函数只能由一个参数
pthread_detach(pthread_self());
ThreadPool* tp = (ThreadPool*)args;
while(true)
{
//上锁,线程间互斥,保证数据的安全一致性。
//条件变量,避免线程饥饿,保持线程间同步
//通过接口使用this属性的方法,美观环保。
tp->Lkq();
while(tp->Ispty())//while排除伪唤醒
{//任务队列没任务就阻塞等待。
tp->cnwait();
}
//从队列里取任务。
T t;
tp->popt(&t);
tp->ulkq();
t();//处理任务
}
}
void Lkq()
{
pthread_mutex_lock(&lock);
}
void ulkq()
{
pthread_mutex_unlock(&lock);
}
void cnwait()
{
pthread_cond_wait(&cond,&lock);
}
void cnwake()
{
pthread_cond_signal(&cond);
}
bool Ispty()
{
return q.size() == 0;
}
void popt(T* out)
{
*out= q.front();
q.pop();
}
void InTp(int num)//创建n个线程
{
for(auto i = 0; i < num;i++)
{
pthread_t tid;
pthread_create(&tid,nullptr,Run,this);
//传this 使用类中的属性以及方法
}
}
void pusht(const T& in)
{
//放任务,要么没放,要么放了,加锁保证数据的原子性
//然后唤醒消费者取任务
Lkq();
q.push(in);
cnwake();
ulkq();
}
~ThreadPool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
cl.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using std::cout;
using std::cin;
using std::string;
using std::cerr;
namespace ns_client
{
class Tcp_client
{
private://需要服务器的IP和端口
string desc_ip;
uint16_t desc_port;
int sock;
public:
Tcp_client( string _ip,uint16_t _port)
:desc_ip(_ip)
,desc_port(_port)
{}
void Init_Client()
{
//sock
sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
cerr << "socket error"<<std::endl;
exit(1);
}
//不需要绑定 不需要监听 不需要accept
}
//通信之前 需要先建立连接
void Start()
{
//填充对方的socket信息
struct sockaddr_in svr;
bzero(&svr,sizeof(svr));
svr.sin_family = AF_INET;
svr.sin_addr.s_addr = inet_addr(desc_ip.c_str());
svr.sin_port = htons(desc_port);
//2.发起连接请求
if(connect(sock,(struct sockaddr*)&svr,sizeof(svr))==0)
{
cout<< "connect succes"<<std::endl;
}
else{
cout<< "connect failed"<<std::endl;
return;
}
//3.完成业务逻辑
while(true)
{
char buff[1024]={0};
cout<<sock<<"please enter ";
fflush(stdout);
ssize_t s = read(0,buff,sizeof(buff)-1);
if(s>0)
{
buff[s-1]=0;
write(sock,buff,strlen(buff));
int rs = recv(sock,buff,sizeof(buff)-1,0);
if(rs>0){
buff[rs]=0;
cout<<buff<<std::endl;
}
else{
break;
}
// ssize_t rs = read(sock,buff,sizeof(buff)-1);
// if(rs>0)
// {
// buff[s]=0;
// cout<
// }
// else{
// cout<<"server close---"<
// break;
// }
}
}
}
~Tcp_client()
{
if(sock>=0)
{
close(sock);
}
}
};
}
cl.cc
#include "cl.hpp"
#include
void Usage( string proc)
{
cerr<<"Usage :"<<"\n\t"<<proc<<"_ip _port"<<std:: endl;
}
int main(int argc,char* argv[])
{
// if(argc!=3)
// {
// Usage(argv[0]);
// return 1;
// }
string ip = "127.0.0.1";
uint16_t port = atoi("8081");
ns_client::Tcp_client cli(ip,port);
cli.Init_Client();
cli.Start();
return 0;
}
netstat -ntlp
telnet ip 端口号
,进行测试;再按Ctrl ]
,即可进行通信;再按一次,输入 quit 退出。 客户端的connect
触发三次握手,底层由OS自动完成,建立连接;服务端的accept()
是三次握手完成后, 服务器调用 accept() 接受连接。由于TCP协议是全双工通信;所以两端都要关闭文件描述符,这就称之为四次挥手。服务端、客户端各自的close
执行挥手中的两次,也是有OS自动完成的。全双工通信:任何时刻,我可以给你发数据,你也可以给我发;半双工通信:任何时刻只能单向的发送数据。
建立连接的本质是,双方的操作系统,构建相应的内核数据结构(结构体),后续维护连接;因此建立连接也是有时间、空间成本的。计算机的名词对应着OS中的数据结构。