
socket网络编程之TCP
一、TCP实现回显服务器
1、服务端
(一)TcpServer.hpp
cpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <pthread.h>
#include <sys/wait.h>
const std::string defaultip = "0.0.0.0";
const int defaultfd = -1;
//枚举错误类型
enum
{
UsageError = 1,
SocketError,
BindError,
ListenError,
};
//封装客户端连接相关信息
class ThreadData
{
public:
ThreadData(int fd, const std::string &ip, const uint16_t &p) : sockfd(fd), clientip(ip), clientport(p)
{}
public:
int sockfd;
std::string clientip;
uint16_t clientport;
};
class TcpServer
{
public:
TcpServer(const uint16_t &port, const std::string &ip = defaultip) : listensock_(defaultfd), port_(port), ip_(ip)
{}
void InitServer()
{
//IPv4协议,TCP套接字
listensock_ = socket(AF_INET, SOCK_STREAM, 0);
if (listensock_ < 0)
{
exit(SocketError);
}
//local存储本地服务器地址信息并初始化为0
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
//IPv4协议,端口号,IP地址
local.sin_family = AF_INET;
local.sin_port = htons(port_);
inet_aton(ip_.c_str(), &(local.sin_addr));
//调用bind将套接字listensock_ 绑定到本地地址local
if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0)
{
exit(BindError);
}
//将套接字设置为监听状态,最多允许五个客户端连接请求排队等待处理
if (listen(listensock_, 5) < 0)
{
exit(ListenError);
}
}
void Start()
{
while (true)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
//新套接字sockfd用于与发起连接请求的客户端进行数据传输
//原来的listensock_继续监听
int sockfd = accept(listensock_, (struct sockaddr *)&client, &len);
if (sockfd < 0)
{
continue;
}
//将客户端端口号转换为主机字节序
uint16_t clientport = ntohs(client.sin_port);
//将客户端ip转换为点分十进制字符串
char clientip[32];
inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
//多进程,从这里开始的下面这段代码,可以用多线程以及线程池替代,在后面说说多线程
//这里很巧妙的设计,我们在后边与多线程一起解释
pid_t id = fork();
if (id == 0)
{
// 子进程关闭监听
close(listensock_);
//子进程创建"孙子"进程
if (fork() > 0)
exit(0);
Service(sockfd, clientip, clientport);
close(sockfd);
exit(0);
}
close(sockfd);
// 父进程等待
pid_t rid = waitpid(id, nullptr, 0);
(void)rid;
}
}
//处理发送来的内容,将数据整合打印到屏幕上
void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
{
char buffer[4096];
while (true)
{
ssize_t n = read(sockfd, buffer, sizeof(buffer));
if (n > 0)
{
buffer[n] = 0;
std::cout << "tcpclient say# " << buffer << std::endl;
std::string echo_string = "tcpserver echo# ";
echo_string += buffer;
write(sockfd, echo_string.c_str(), echo_string.size());
}
else
{
break;
}
}
}
~TcpServer() {}
private:
int listensock_;//监听套接字描述符
uint16_t port_;//端口号
std::string ip_;//ip地址
};
(二)main.cpp
cpp
#include "TcpServer.hpp"
#include <iostream>
#include <memory>
void Usage(std::string proc)
{
std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(UsageError);
}
uint16_t port = std::stoi(argv[1]);
//智能指针维护服务器
std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port));
tcp_svr->InitServer();
tcp_svr->Start();
return 0;
}
2、客户端
TcpClient.cpp
cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void Usage(const std::string &proc)
{
std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
<< std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
//创建TCP套接字描述符
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return 1;
}
//还是老套路,初始化服务器地址结构体结构体
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
//连接服务器
int n = connect(sockfd, (struct sockaddr*)&server, sizeof(server));
if(n < 0)
{
std::cerr << "connet error" << std::endl;
return 2;
}
//循环输出打印
std::string message;
while (true)
{
std::cout << "Please Enter# ";
std::getline(std::cin, message);
write(sockfd, message.c_str(), message.size());
char inbuffer[4096];
n = read(sockfd, inbuffer, sizeof(inbuffer));
if (n > 0)
{
inbuffer[n] = 0;
std::cout << inbuffer << std::endl;
}
}
close(sockfd);
return 0;
}

二、服务器Start函数
1、多进程版
cpp
//......
pid_t id = fork();
if (id == 0)
{
// 子进程关闭监听
close(listensock_);
//子进程创建"孙子"进程
if (fork() > 0)
exit(0);
Service(sockfd, clientip, clientport);
close(sockfd);
exit(0);
}
//父进程关闭sockfd描述符
close(sockfd);
// 父进程等待
pid_t rid = waitpid(id, nullptr, 0);
(void)rid;
//......
创建子进程后子进程关闭监听描述符,再创建一个"孙子"进程,然后子进程退出,此时孙子进程成为孤儿进程,被系统领养,再进行其他的工作,工作完成后关闭描述符,退出时由系统回收
父进程关闭新创建的描述符,然后父进程进入进程等待,这个进程等待的时间很短甚至没有,因为子进程在创建完"孙子"进程后就退出了,父进程就可以回收掉子进程继续下一轮的循环
整个过程不会担心父进程由于阻塞等待而造成的一系列问题,也不用修改为非阻塞轮询来消耗资源,被领养的孙子进程有系统回收资源,也不用担心它资源泄露
2、多线程版
cpp
//......
//声明
class TcpServer;
class ThreadData
{
public:
ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t) : sockfd(fd), clientip(ip), clientport(p), tsvr_(t)
{}
public:
int sockfd; //套接字描述符
std::string clientip; //ip地址
uint16_t clientport; //端口号
TcpServer *tsvr_; //指向TcpServer的指针
};
//......
//TcpServer结构体内
static void *Routine(void *args)
{
//将线程分离,结束后自动释放所占资源
pthread_detach(pthread_self());
//调用Service函数
ThreadData *td = static_cast<ThreadData *>(args);
td->tsvr->Service(td->sockfd, td->clientip, td->clientport);
delete td;
return nullptr;
}
//......
一样的效果
今日分享就到这了~
