相比于UDP,TCP以字节流为传输模式,面向连接且更为可靠。就像打电话一样:
当确定有TCP通讯即将发生时,需要先将服务端设为监听(listen)状态告诉其准备干活接受客户端的连接;随后服务端会(accept)陷入阻塞状态,等待客户端(connect)发起连接。
这三个函数详情可见之前的文章,或者ai,找资料。
由于TCP的实现代码跟UDP的很像,仅需注意服务端监听、阻塞等待和客户端的过程,所以下面就直接放代码:
TCPServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include "InetAddr.hpp"
#include "Logger.hpp"
using namespace NS_LOG_MODULE;
enum
{
SUCCESS = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
};
static const int gbacklog = 16;
static const uint16_t gport = 8888;
class TcpServer
{
public:
TcpServer(uint16_t port = gport) : _port(port)
{
}
void InitServer()
{
signal(SIGPIPE, SIG_IGN);
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(_listensockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
if (listen(_listensockfd, gbacklog) < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::DEBUG) << "server init success";
}
void serviceIO(int sockfd, InetAddr address)
{
char inbuffer[1024] = {0};
while (true)
{
ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
if (n > 0)
{
inbuffer[n] = 0;
LOG(LogLevel::INFO) << address.ToString() << " say# " << inbuffer;
std::string echo = "server echo# ";
echo += inbuffer;
write(sockfd, echo.c_str(), echo.size());
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "client quit";
break;
}
else
{
LOG(LogLevel::ERROR) << "read error";
break;
}
}
close(sockfd);
}
void Start()
{
while (true)
{
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int sockfd = accept(_listensockfd, (struct sockaddr *)&clientaddr, &len);
if (sockfd < 0)
{
continue;
}
InetAddr addr(clientaddr);
serviceIO(sockfd, addr);
}
}
~TcpServer()
{
close(_listensockfd);
}
private:
uint16_t _port;
int _listensockfd;
};
TCPClient.cc
#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "InetAddr.hpp"
static void Usage(const std::string &name)
{
std::cerr << "Usage:\n\t";
std::cerr << name << " server_ip server_port" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
exit(2);
}
InetAddr serveraddress(server_port, server_ip);
int n = connect(sockfd, (struct sockaddr *)serveraddress.GetNetAddress(), serveraddress.Len());
if (n < 0)
{
std::cerr << "connect to " << serveraddress.ToString() << " failed!" << std::endl;
exit(3);
}
std::cerr << "connect to " << serveraddress.ToString() << " success!" << std::endl;
while (true)
{
std::string line;
std::cout << "please Enter# ";
std::getline(std::cin, line);
write(sockfd, line.c_str(), line.size());
char inbuffer[1024];
ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer));
if (n > 0)
{
inbuffer[n] = 0;
std::cout << inbuffer << std::endl;
}
else if (n == 0)
{
std::cout << "read enf of file!" << std::endl;
break;
}
else
{
std::cerr << "read error!" << std::endl;
break;
}
}
return 0;
}
ChatTCPMain.cc
#include "TCPServer.hpp"
#include <memory>
static void Usage(const std::string &process)
{
std::cerr << "Usage:\n\t";
std::cerr << process << " local_port" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
ENABLE_CONSOLE_LOG_STRATEGY();
uint16_t server_port = std::stoi(argv[1]);
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(server_port);
tsvr->InitServer();
tsvr->Start();
return 0;
}
