TCP依旧使用代码来熟悉对应的套接字,很多接口都是在udp中使用过的
所以就不会单独把他们拿出来作为标题了,只会把第一次出现的接口作为标题
通过TCP的套接字 ,来把数据交付给对方的应用层,完成双方进程的通信
在 tcpServer.hpp 中,创建一个命名空间 yzq 用于封装
在命名空间中,定义一个类 TcpServer
该类中包含 构造 析构 初始化(initServer) 启动(start)
设置监听端口号(后面会解释) ,需要端口号标识进程的唯一性
在类外设置一个默认端口号8888作为构造函数参数port的缺省值
创建套接字
输入 man socket
第一个参数 domain ,用于区分 进行网络通信还是 本地通信
若想为网络通信,则使用 AF_INET
若想为本地通信,则使用 AF_UNIX
第二个参数 type, 套接字对应的服务类型
SOCK_STREAM 流式套接
SOCK_DGRAM 无连接不可靠的通信(用户数据报)
第三个参数 protocol ,表示想用那种协议,协议默认为0
若为 流式套接,则系统会认为是TCP协议 ,若为用户数据报,则系统会认为是UDP协议
套接字的返回值:若成功则返回文件描述符,若失败则返回 -1
说明进行网络通信,流式套接,同时系统认为是TCP协议
创建err.hpp 用于存储错误信息的枚举
如果创建失败,则终止程序
输入 man 2 bind ,查看绑定
给一个套接字绑定一个名字
第一个参数 sockfd 为 套接字
第二个参数 addr 为 通用结构体类型
第三个参数 addrlen 为 第二个参数的实际长度大小
bind返回值:若成功,则返回0,若失败,返回 -1
使用bind,是需要借助一个通用结构体来实现的
所以定义一个 网络通信类型的结构体 local
在上一篇博客中,详细讲述了 sockaddr_in 结构体的内部组成
不懂可以去看看:struct sockaddr_in 的理解
输入 man htons ,表示短整数的主机转网络序列
所以需要将主机的port_进行转化 ,然后再交给 local的sin_port (端口号)
INADDR_ANY 表示bind的任意IP
如果绑定失败返回-1
输入 man 2 listen
设置当前套接字状态为 监听状态
第一个参数 sockfd 为 套接字
第二个参数 暂不做解释,一般设为整数
若成功则返回0,若失败返回-1
监听失败 返回-1,并终止程序
在类外设置一个 默认整数 为32
设置一个布尔变量 quit_,若为true则表示 服务器启动, 若为false,则表示 服务器没有启动
如果服务器没有启动,则进入while循环
输入 man 2 accept
需要知道谁连的你,所以要获取到客户端的相关信息
第一个参数 sockfd 为套接字
第二个参数 addr 为通用结构体类型的 结构体 这个结构体是用来记录客户端内的port号以及IP地址 、16位地址类型等信息
第三个参数 addrlen 为 结构体的大小
返回值:
若成功,则返回一个合法的整数 即文件描述符
若失败,返回-1并且设置错误码
如:有一个鱼庄,生意不太好,所以在外面站着一个人叫张三,进行揽客
有一天你和你的朋友在外面遇见张三,张三就向你们说他们鱼庄有多少,推荐去他们哪里吃鱼
正好你们俩也饿了,所以就跟张三去鱼庄吃鱼,但是只有你们进入鱼庄了,张三并没有进去
张三只是向里面喊了一声,来客人了,然后继续找人去了
这个时候来了一个服务员李四,向你们询问要吃什么,并向你们提供各种服务
每一次张三把客人招呼到鱼庄时,都会有一名服务员给客人提供服务
当张三做完自己的工作后,立马返回自己的工作岗位,继续招揽客人
张三不给用户提供具体的服务,只负责把客人从路上拉到店里去吃饭 进行消费
李四来给客人提供服务
鱼庄 可以看作是 整个服务器
像张三这样把客人从外部 拉到餐厅里的 称为 监听套接字 即accept的第一个参数 sockfd
像李四这样作的动作,相当于accept会返回一个文件描述符,这个文件描述符 是真正给用户提供IO服务的
若张三继续拉客,在路上碰见一个人,问他要不要去鱼庄吃饭,但那个人摇了摇头,表示没有意愿去鱼庄吃饭,
此时张三就被拒绝了,但这并不影响张三继续拉客去鱼庄
所以 accept 获取失败,只需继续 执行即可
提供一个service的函数 ,参数为新的文件描述符sock
用于实现基本的读写服务 即 客户端发消息,需要把消息转回去
TCP 是一种流式服务
输入 man 2 read
从文件描述符fd中将我们想要的数据,按照数据块的方式读取出来
返回值代表多少字节,读取到文件结尾为0,失败为-1
将sock中的数据读取到buffer缓冲区中
若读取成功,则将最后一位的下一位赋值为0
若read的返回值为0,则对方将连接关闭了,所以sock也可以关闭
若返回值小于0,则读取失败,返回错误码
收到消息,需要把消息做某种处理后,再把消息转回去
所以使用 包装器 functional处理
在类外设置一个函数类型,返回值为string,参数为 string 的包装器
用该函数类型定义为一个私有变量func
将处理完的消息进行返回
输入 man 2 write
向一个文件中写入信息
fd代表文件描述符
buf代表 缓冲区
count代表 缓冲区大小
write将缓冲区的count大小的数据写入 fd中
将res中的数据 写入 sock文件描述符中
想要只输入 ./tcp_server 加 端口号
所以在main函数中添加命令行参数
main函数的两个参数,char* argv[] 为指针数组 ,argv为一张表,包含一个个指针,指针指向字符串
int argc,argc为数组的元素个数
当参数输入不为2时,就会终止程序,同时打印出对应的输入参数
通过构造函数了解, 想要使用 new TcpServer 需要传入回调和端口号
为了使用客户端,所以要输入对应的 可执行程序 serverip serverport
所以在main函数需要使用 命令行参数
若输入的参数少于3个,则终止程序,并打印出对应输入的参数
将输入的第二个参数的IP地址 赋值给 serverip
将输入的第三个参数的端口号,使用atoi将字符串转化为整数 ,再赋值给serverport
网络通信,并为流式套接,默认为0,因为流式所以为TCP协议
若创建套接字失败,则终止程序
输入 man accept
客户端 通过套接字sockfd,向特定的服务器发起链接请求
sockfd:套接字
addr:公共类型的结构体 内部包含 服务器的IP地址和的端口号
addrlen:结构体的大小
返回值:若成功,则返回0,若失败,返回-1和错误码
首次发起链接时,操作系统会给客户端自动进行绑定端口
所以需要先定义一个结构体server
借助htons 将上述的主机序列端口号serverport 转化为网络序列端口号
输入man inet_addr
第一个参数为 字符串风格的IP地址
第二个参数 为 网络序列的IP地址
将 字符串风格的IP地址 转为 网络序列的IP地址
再将主机序列的IP地址serverip,转化为网络序列的IP地址
cnt表示重连次数
设置while循环,当不等于0链接失败时,cnt值减1,并重新链接,若cnt值为0,则break终止循环
若出了while循环,cont小于等于0,则终止程序
创建一个string类型的line,将输入的参数传入line中
使用write,将line的内容传入文件描述符中
使用read,将sock的数据传入buffer中
通过read的返回值来判断,若返回值大于0则,输出其中内容
若返回值等于0,则说明链接关闭,则退出while循环
若返回值小于,则说明创建失败,返回错误码
#pragma once
enum
{
USAGE_ERR=1,
SOCKET_ERR,//2
BIND_ERR,//3
LISTEN_ERR//4
};
.PHONY:all
all: tcp_client tcp_server
tcp_client:tcpClient.cc
g++ -o $@ $^ -std=c++11 -lpthread
tcp_server:tcpServer.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f tcp_client tcp_server
#pragma once
#include
#include
#include
#include
#include"err.hpp"
#include
#include
#include
#include
#include
namespace yzq
{
static uint16_t defaultport=8888;//默认端口号
static const int backlog=32;//默认整数为32
using func_t=std::function<std::string(const std::string&)>;
class TcpServer;
class ThreadData//该类用于存放客户端的IP port 套接字
{
public:
ThreadData(int fd,const std::string&ip,const uint16_t &port,TcpServer*ts)//构造
:sock(fd),clientip(ip),clientport(port),current(ts)
{}
public:
int sock;//套接字
std::string clientip;//客户端IP
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)//创建失败
{
std::cout<<" create socket errno"<<std::endl;
exit(SOCKET_ERR);//终止程序
}
//2. bind 绑定
struct sockaddr_in local;//网络通信类型
//清空
memset(&local,'\0',sizeof(local));
local.sin_family=AF_INET;//网络通信
//htons 主机转网络
local.sin_port=htons(port_);//端口号
local.sin_addr.s_addr=INADDR_ANY ; //IP地址
if(bind(listensock_,(struct sockaddr*)&local,sizeof(local))<0)
//失败返回-1
{
std::cout<<" bind socket errno"<<std::endl;
exit(BIND_ERR);//终止程序
}
// 3.监听
if(listen(listensock_,backlog)<0)
{
//监听失败返回-1
std::cout<<" listen socket errno"<<std::endl;
exit(LISTEN_ERR);//终止程序
}
}
void start()//启动
{
quit_=false;//服务器没有启动
while(!quit_)
{
//4.获取连接,accept
struct sockaddr_in client;//网络通信类型
socklen_t len=sizeof(client);//结构体大小
int sock=accept(listensock_,(struct sockaddr*)&client,&len);
if(sock<0)
{
//获取失败
std::cout<<" accept errno"<<std::endl;
continue;//继续执行
}
//提取客户端信息
std::string clientip=inet_ntoa(client.sin_addr);//客户端ip
uint16_t clientport=ntohs(client.sin_port);//客户端端口号
//5.获取新连接成功,开始进行业务处理
std::cout<<"获取新连接成功: "<<sock<<"from "<<listensock_<<std::endl;
//service(sock);//多线程版本没有调用函数
//多线程版本
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=(ThreadData*)args;
td->current->service(td->sock);
delete td;
return nullptr;
}
void service(int sock)
{
char buffer[1024];
while(true)
{
//将sock中的数据读取到buffer中
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
//读取成功
buffer[s]=0;
//使用func 进行回调
std::string res=func_(buffer);
std::cout<<res<<std::endl;
//将res中的数据写给sock中
write(sock,res.c_str(),res.size());
}
else if(s==0)
{
//说明对方将连接关闭了
close(sock);
std::cout<<"client quit,me too"<<std::endl;
break;
}
else
{
//读取失败返回-1
std::cout<<"read errno"<<strerror(errno)<<std::endl;
break;
}
}
}
~TcpServer()
{}
private:
func_t func_;//函数类型
int listensock_;//监听套接字
bool quit_;//表示服务器是否启动
uint16_t port_;//端口号
};
}
#include"tcpServer.hpp"
#include //智能指针
using namespace std;
using namespace yzq;
static void usage(string proc)
{
std::cout<<"usage:\n\t"<<proc<<"port\n"<<std::endl;
}
std::string echo(const std::string&message)
{
return message;
}
// ./tcp_server port
int main(int argc,char*argv[])
{
//输入两个参数 所以不等于2
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;
}
#include
#include
#include
#include
#include
#include
#include
#include"err.hpp"
using namespace std;
static void usage(string proc)
{
std::cout<<"usage:\n\t"<<proc<<"port\n"<<std::endl;
}
//./tcp_client serverip serverport
int main(int argc,char*argv[])
{
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);//终止程序
}
std::string serverip=argv[1];//IP地址
uint16_t serverport=atoi(argv[2]);//端口号
//1.创建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
//创建失败
cout<<"socket errnr:"<<strerror(errno)<<endl;
exit(SOCKET_ERR);//终止程序
}
//2.发起链接
struct sockaddr_in server;
memset(&server,0,sizeof(server));//清空
server.sin_family=AF_INET;//网络通信类型
//htons 主机序列转为网络序列
server.sin_port=htons(serverport);//网络端口号
inet_aton(serverip.c_str(),&server.sin_addr);//网络IP地址
int cnt=5;//重连次数
while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)
{
//不等于0则链接失败
sleep(1);
cout<<"正在尝试重连,重连次数还有:"<<cnt--<<endl;
if(cnt<=0)
{
//没有重连次数
break;
}
}
if(cnt<=0)
{
//链接失败
cout<<"链接失败.."<<endl;
exit(SOCKET_ERR);//终止程序
}
char buffer[1024];
//3.链接成功
while(true)
{
string line;
cout<<"enter>>";
getline(cin,line);//从cin中获取内容 写入line中
write(sock,line.c_str(),line.size());//将line中的内容写入到sock文件描述符中
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
cout<<"server echo"<<buffer<<endl;
}
else if(s==0)
{
cout<<"server quit"<<endl;
break;
}
else
{
cout<<"read errno"<<strerror(errno)<<endl;
break;
}
}
close(sock);
return 0;
}