我们之前实现了UDP协议下的客户端与服务端的通信。
UDP(用户数据报协议)和TCP(传输控制协议)都是网络通信中常用的传输层协议,它们在数据传输的方式和特性上存在以下特点:
TCP
UDP
通俗理解的话:TCP的传输过程类似管道,数据从一端发送,然后在另一端按顺序接收。UDP传输数据的过程类似送快递,数据报文会一股脑包装在一起发送给接收者!
• socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描述符; • 应用程序可以像读写文件一样用 read / write 在网络上收发数据,通过流来进行读取写入! • 如果 socket()调用出错则返回-1; • 对于 IPv4, family 参数指定为 AF_INET; • 对于 TCP 协议,type 参数指定为 SOCK_STREAM, 表示面向流的传输协议 • protocol 参数的介绍从略,指定为 0 即可。
下面我们就来设计一下TCP协议下的服务器类:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include <string>
#include <cstring>
#include <iostream>
#include <functional>
#include <unistd.h>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace log_ns;
//基础信息
const int gport = 8888;
const int gblocklog = 8;
//错误码
enum
{
SOCKET_FD = 1,
SOCKET_BIND,
SOCKET_LISTNE
};
class TcpServer
{
public:
TcpServer(int port = gport) : _port(port),
_listensockfd(-1),
_isrunning(false)
{
}
// 进行初始化
void InitServer()
{
}
void Loop()
{
}
void Service(int sockfd, InetAddr addr)
{
}
~TcpServer()
{
}
private:
uint16_t _port; // 服务器端口
int _listensockfd; // 链接文件
bool _isrunning;
};
这就是基础的框架。
InitServer()
初始化接口进行的工作很好理解:
_listensockfd
_listensockfd
进行绑定_listensockfd
通过listen
函数进入监听状态。初始化任务就完成了
// 进行初始化
void InitServer()
{
// 创建socket文件 --- 字节流方式
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(FATAL, "socket error!!!\n");
exit(SOCKET_FD);
}
LOG(INFO, "socket create success!!! _listensockfd: %d\n", _listensockfd);
// 建立server结构体
struct sockaddr_in local;
memset(&local , 0 , sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY; // 服务器IP一般设置为0
local.sin_port = htons(_port); //一定注意主机序列转网络序列
// 进行绑定
if (::bind(_listensockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
{
LOG(FATAL, "bind error!!!\n");
exit(SOCKET_BIND);
}
LOG(INFO, "bind success!!!\n");
// 将_listensockfd文件转换为listening状态!!!
if (::listen(_listensockfd, gblocklog) < 0)
{
LOG(FATAL, "listen error!!!\n");
exit(SOCKET_LISTNE);
}
LOG(INFO, "listen success!!!\n");
}
Loop()
循环接收接口需要:
void Loop()
{
_isrunning = true;
while (_isrunning)
{
// accept接收sockfd
struct sockaddr_in client;
socklen_t len = sizeof(client);
int sockfd = ::accept(_listensockfd, (struct sockaddr *)&client, &len);
if(sockfd < 0)
{
LOG(WARNING, "accept error\n");
continue;
}
InetAddr addr(client);
// 读取数据
LOG(INFO, "get a new link, client info : %s, sockfd is : %d\n", addr.AddrStr().c_str(), sockfd);
// version 0 --- 不靠谱版本
Service(sockfd, addr);
}
_isrunning = false;
}
void Service(int sockfd, InetAddr addr)
{
LOG(INFO , "service start!!!\n");
while (true)
{
char buffer[1024];
ssize_t n = ::read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
LOG(INFO , "sockfd read success!!! buffer: %s\n" , buffer);
std::string str = "[server echo]#";
str += buffer;
write(sockfd, str.c_str(), str.size());
}
else if(n == 0)
{
LOG(INFO , "client %s quit!\n" , addr.AddrStr().c_str());
break;
}
else
{
LOG(ERROR, "read error: %s\n", addr.AddrStr().c_str());
break;
}
}
::close(sockfd);
}
这样基础的服务器的通信工作就写好了
接下来我们来完善一下服务端和客户端的通信逻辑,让他们可以通信起来
服务端简单的创建一个服务器类然后进行初始化和loop就可以了!!!
#include "TcpServer.hpp"
int main(int argc , char* argv[])
{
if(argc != 2)
{
std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;
exit(0);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);
tsvr->InitServer();
tsvr->Loop();
return 0;
}
客户端稍微复杂一些:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <cstring>
#include <iostream>
#include "Log.hpp"
using namespace log_ns;
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
exit(0);
}
std::string ip = argv[1];
uint16_t port = std::stoi(argv[2]);
// 创建socket文件
int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
LOG(FATAL, "sockfd create error!!!\n");
exit(1);
}
struct sockaddr_in server;
memset(&server, 0, sizeof(server)); // 数据归零
server.sin_family = AF_INET;
server.sin_port = htons(port); // 端口号 主机序列转网络序列!!!
::inet_pton(AF_INET, ip.c_str(), &server.sin_addr);//安全写入
// 进行发送数据
int n = ::connect(sockfd, (struct sockaddr *)&server, sizeof(server));
if (n < 0)
{
std::cerr << "connect socket error" << std::endl;
exit(2);
}
// 链接成功
while (true)
{
// 进行写入
std::string line;
std::cout << "Please Enter: ";
std::getline(std::cin, line);
::write(sockfd, line.c_str(), line.size());
LOG(DEBUG , "write success !!!\n");
// 读取数据
char buffer[1024];
int n = read(sockfd, buffer, sizeof(buffer));
if (n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl;
}
else
{
break;
}
}
::close(sockfd);
return 0;
}
我们来测试一下服务端和客户端是否可以做到通信:
很好,可以完美的进行通信!!!
之后我们就可以加入多线程,加入回调函数逻辑,就可以进行业务处理了!!!