第一:声明
不再追溯,可以先看看(基础版本)再看这个,谢谢。
第二:代码
工具类
/*================================================================
* Copyright (C) 2021 ymbLite. All rights reserved.
*
* 文件名称:BaseUtil.h
* 创 建 者:ymbLite
* 创建日期:2021年11月04日
* 描 述:工具类
*
================================================================*/
#ifndef _BASEUTIL_H
#define _BASEUTIL_H
#include <iostream>
#include <arpa/inet.h>
#include <cstring>
#include <sys/wait.h>
using std::cout;
using std::endl;
/*
* describe :读取已经准好的socket中的数据
* 在需要读取的时候,需要传入真正读取的字节数,可以实现安全读取,可以避免粘包的问题
*
* sockfd :读取的socket描述符
* buffer :保存数据的缓冲地址
* n :读取数据的字节数
*
* return :成功接收到n字节的数据后返回true,socket连接不可用返回false
* */
bool BaseRead(const int& sockfd , char* buffer , const size_t n);
/*
* describe :把数据写入已准备好的socket中
*
* sockfd :写入数据的socket描述符
* buffer :待发送数据缓冲区的地址
* n :待发送数据的字节数
*
* return :成功发送完n字节数的数据后返回true,socket连接不可用返回false
* */
bool BaseWrite(const int& sockfd , const char* buffer , const size_t n);
/*
* describe :接受socket的对端发送过来的数据
*
* sockfd :读取数据的socket描述符
* buffer :存放读取数据的缓冲区
* ibuflen :本次成功接受数据的字节数
* itimeout :读取超时时间的设置,默认为0-暂时不使用
*
* return :成功返回true,失败(连接超时或者socket不可用)返回false
* */
bool TcpBaseRead(const int& sockfd , char* buffer , int* ibuflen , const int itimeout = 0);
/*
* describe :向socket对端发送已准备好的数据
*
* sockfd :写入数据的socket描述符
* buffer :待发送数据的数据缓冲区
* ibuflen :待发送数据的字节数
*
* return :成功返回true,失败(socket连接不可用)返回false;
* */
bool TcpBaseWrite(const int& sockfd , const char* buffer , const int ibuflen = 0 );
/*
* 子进程结束之后,信号调用的函数
* */
void read_childproc(int aig);
#endif //BASEUTIL_H
/*================================================================
* Copyright (C) 2021 ymbLite. All rights reserved.
*
* 文件名称:BaseUtil.cpp
* 创 建 者:ymbLite
* 创建日期:2021年11月04日
* 描 述:
*
================================================================*/
#include "BaseUtil.h"
bool BaseRead(const int& sockfd , char* buffer , const int n){
int _total_size = n;//总共读取的字节数
int _every_size = 0;//每次读取的字节数
int _already_size = 0;//已经读取的字节数
//循环读取数据,到需要读取的数据为空为止
while(_total_size > 0){
if((_every_size = recv(sockfd , buffer + _already_size , _total_size,0)) <= 0)
return false;
_already_size += _every_size;
_total_size -= _every_size;
}
return true;
}
bool BaseWrite(const int& sockfd , const char* buffer , const size_t n){
int _total_size = n;
int _every_size = 0;//每次写入的数据
int _alerdy_size = 0;//已经写出的数据
while(_total_size > 0){
if((_every_size = send(sockfd , buffer+_alerdy_size , _total_size , 0)) <= 0){
return false;
}
_alerdy_size += _every_size;
_total_size -= _every_size;
}
return true;
}
bool TcpBaseRead(const int& sockfd , char* buffer , int* ibuflen , const int itimeout){
//首先判断socket描述符是否可以使用
if(sockfd == -1){
return false;
}
//超时设置,暂时不处理超时
//然后读取缓冲区里面前4个字节,因为有粘包的情况出现,所以在发送数据之前没客户端会首先在数据前面的四个字节放置数据的大小
*ibuflen = 0;
if(!BaseRead(sockfd , (char*)ibuflen , 4)) return false ;
//最后才是读取正真的字节数
*ibuflen = ntohl(*ibuflen);//把网络字节序转换成主机字节序
if(!BaseRead(sockfd , buffer , (*ibuflen))) return false;
return true;
}
bool TcpBaseWrite(const int& sockfd , const char* buffer , const int ibuflen){
if(sockfd == -1) return false;
int ilen = 0;
//如果传输的是字符串,那么就采用字符串的长度
if(ibuflen == 0)
ilen = strlen(buffer);
else
ilen = ibuflen;
int ilen_n = htonl(ilen);
char strBuffer[ilen + 4];
memset(strBuffer , 0 , sizeof(strBuffer));
memcpy(strBuffer , &ilen_n , 4);
memcpy(strBuffer+4 , buffer , ilen);
if(!BaseWrite(sockfd , strBuffer , ilen+4)) return false;
return true;
}
void read_childproc(int sig){
pid_t pid;
int status;
pid = waitpid(-1 , &status , WNOHANG);
if(WIFEXITED(status)){
cout<<"子进程返回的状态码:"<<WEXITSTATUS(status)<<endl;
cout<<"移除子进程,他的PID:"<<pid<<endl;
}
}
客户端
/*================================================================
* Copyright (C) 2021 ymbLite. All rights reserved.
*
* 文件名称:TcpClient.h
* 创 建 者:ymbLite
* 创建日期:2021年11月03日
* 描 述:
*
================================================================*/
#ifndef _TCPCLIENT_H
#define _TCPCLIENT_H
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>
#include <unistd.h>
#include <netdb.h>
#include "BaseUtil.h"
//#define BUFF_SIZE 1024
const int BUFF_SIZE = 1024;
using std::cout;
using std::cin;
using std::endl;
using std::string;
class TcpClient{
private:
int clnt_sock; //客户端的socket
struct sockaddr_in serv_addr; //客户端的连接服务器的网络地址结构
char buf[BUFF_SIZE]; //客户端的读写缓冲区
int buf_len; //客户端的读写长度
public:
TcpClient();//构造函数
~TcpClient();//析构函数
/**
* 初始化客户端
* ip_addr: ipv4的地址
* port: 通信的端口号
*/
bool InitClient();
/*
* 连接服务器
* */
bool Connect(const string& ip_addr , const string& post);
/*
* 读取缓冲区的数据
* */
int Read();
/*
* 写入数据
* str:需要发送的数据
* */
void Write(const string& str);
/*
* 关闭socket
* */
void CloseClientSock();
};
#endif //TCPCLIENT_H
/*================================================================
* Copyright (C) 2021 ymbLite. All rights reserved.
*
* 文件名称:TcpClient.cpp
* 创 建 者:ymbLite
* 创建日期:2021年11月03日
* 描 述:
*
================================================================*/
#include "TcpClient.h"
in_addr GetHostByName(const string& ip_addr){
struct hostent* host;
host = gethostbyname(ip_addr.c_str());
struct in_addr _sin_addr;
memset(&_sin_addr , 0 , sizeof(_sin_addr));
if(!host){
return _sin_addr;
}
_sin_addr = (*(struct in_addr*)host->h_addr_list[0]);
return _sin_addr;
}
TcpClient::TcpClient():clnt_sock(-1),buf_len(0){
}
TcpClient::~TcpClient(){
}
bool TcpClient::InitClient(){
if(clnt_sock != -1){
CloseClientSock();
}
clnt_sock = socket(PF_INET , SOCK_STREAM , 0);
if(clnt_sock == -1){
cout<<"客户端创建socket失败,socket() error!"<<endl;
return false;
}
cout<<"客户端创建socket成功!"<<endl;
return true;
}
bool TcpClient::Connect(const string& ip_addr , const string& port){
if(clnt_sock == -1){
cout<<"客户端没有创建socket,无法连接服务器!"<<endl;
return false;
}
//开始连接服务器
memset(&serv_addr , 0 , sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr = GetHostByName(ip_addr);
serv_addr.sin_port = htons(atoi(port.c_str()));
if(connect(clnt_sock , (struct sockaddr*)& serv_addr , sizeof(serv_addr)) == -1){
CloseClientSock();
cout<<"连接服务器失败"<<endl;
return false;
}
cout<<"连接服务器成功..."<<endl;
return true;
}
int TcpClient::Read(){
if(clnt_sock < 0){
cout<<"客户端未开启socket,无法读取"<<endl;
return -1;
}
//buf_len = read(clnt_sock , buf , BUFF_SIZE);
if(!TcpBaseRead(clnt_sock , buf , &buf_len)){
cout<<"对端已关闭socket,无法继续读取"<<endl;
CloseClientSock();
return buf_len;
}
buf[buf_len] = 0;
cout<<"服务器说:"<<buf<<endl;
return buf_len;
}
void TcpClient::Write(const string& str){
if(clnt_sock < 0){
cout<<"客户端未开启socket,写入失败!"<<endl;
}else{
//write(clnt_sock , str.c_str() , str.size());
TcpBaseWrite(clnt_sock , str.c_str());
}
}
void TcpClient::CloseClientSock(){
if(clnt_sock > 0){
close(clnt_sock);
}
clnt_sock = -1;
}
/*================================================================
* Copyright (C) 2021 ymbLite. All rights reserved.
*
* 文件名称:main.cpp
* 创 建 者:ymbLite
* 创建日期:2021年11月03日
* 描 述:
*
================================================================*/
#include "TcpClient.h"
int main(int argc , char* argv[]){
TcpClient tcp_client;
bool ope_res = tcp_client.InitClient();
if(!ope_res){
return -1;
}
ope_res = tcp_client.Connect(argv[1] , argv[2]);
if(!ope_res){
return -2;
}
cout<<"开始发送数据(输入q退出)"<<endl;
while(1){
string str;
cin>>str;
if(!strcmp(str.c_str() , "q")){
break;
}
tcp_client.Write(str);
if(tcp_client.Read() <= 0){
break;
}
}
tcp_client.CloseClientSock();
return 0;
}
服务端
/*================================================================
* Copyright (C) 2021 ymbLite. All rights reserved.
*
* 文件名称:TcpServer.h
* 创 建 者:ymbLite
* 创建日期:2021年11月03日
* 描 述:socket编程中,简单的TCP服务端
*
================================================================*/
#ifndef _TCPSERVER_H
#define _TCPSERVER_H
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <cstring>
#include <unistd.h>
#include <signal.h>//注册信号的函数和结构体
#include "BaseUtil.h"
const int BUFF_SIZE = 1024;
using std::cout;
using std::endl;
using std::cin;
using std::string;
class TcpServer{
private:
struct sockaddr_in serv_addr;//服务器中服务端的网络地址结构
struct sockaddr_in clnt_addr;//服务端中客户端的网络地址结构
socklen_t clnt_addr_len;//服务器中客户端的网络地址结构的长度
//char buf[BUFF_SZIE];//读写缓冲区
//int buf_len;//读取的字节长度
pid_t pid ;//多进程的id
struct sigaction act;
int act_state;//信号注册的结果
public:
int serv_sock;//服务器中服务端的socket描述符
int clnt_sock;//服务器中客户端的socket描述符
public:
/*
* 阻塞接受客户端的连接
* return:
* -1:客户端连接失败
* 0:服务端未开启socket服务
* >0:成功接受一个客户端的连接,返回的是客户在服务端中的socket描述符
* */
int Accept();
public:
//构造函数
TcpServer();
//析构函数
~TcpServer();
/*
* 初始化服务器
* 服务端地址使用INADDR_ANY
* port:端口号
* */
bool InitServer(const string& port);
/*
* 开启多线程,完成服务端为多个客户提供连接的服务
* */
int StartFork();
/**
* 关闭服务器中服务端的套接字
*/
void CloseServerSock();
/*
* 关闭服务器中客户端的套接字
* */
void CloseClientSock();
};
#endif //TCPSERVER_H
/*================================================================
* Copyright (C) 2021 ymbLite. All rights reserved.
*
* 文件名称:TcpServer.cpp
* 创 建 者:ymbLite
* 创建日期:2021年11月03日
* 描 述:
*
================================================================*/
#include "TcpServer.h"
TcpServer::TcpServer():serv_sock(-1),clnt_sock(-1){
}
TcpServer::~TcpServer(){
CloseServerSock();
}
bool TcpServer::InitServer(const string& port){
//开始注册信号
act.sa_handler = read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act_state = sigaction(SIGCHLD , &act , 0);
//先判断服务是否已经开启
if(serv_sock > 0){
CloseServerSock();
serv_sock = -1;
}
//创建服务器中的服务端socket
serv_sock = socket(PF_INET , SOCK_STREAM , 0);
if(serv_sock == -1){
cout<<"服务器创建监听套接字失败,socket() error!"<<endl;
return false;
}
//绑定服务端的socket到相应的网络地址结构中
memset(&serv_addr , 0 , sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(port.c_str()));
if(bind(serv_sock , (struct sockaddr*)& serv_addr , sizeof(serv_addr)) == -1){
cout<<"服务器绑定套接字失败,bind() error!"<<endl;
//关闭服务端的socket
CloseServerSock();
return false;
}
//开始监听服务端的套接字
if(listen(serv_sock , 5) == -1){
cout<<"服务端监听套接字失败,listen() error!"<<endl;
//关闭服务端的socket
CloseServerSock();
return false;
}
cout<<"服务端初始化完成,开始接受客户端的连接..."<<endl;
return true;
}
int TcpServer::Accept(){
//判断服务端是否已经开启了socket
if(serv_sock == -1){
cout<<"服务器中未开启监听的socket,Accept() error!"<<endl;
return 0;
}
clnt_addr_len = sizeof(clnt_addr);
clnt_sock = accept(serv_sock , (struct sockaddr*)& clnt_addr , &clnt_addr_len);
return clnt_sock;
}
void TcpServer::CloseServerSock(){
if(serv_sock > 0){
cout<<"关闭了监听socket"<<endl;
close(serv_sock);
serv_sock = -1;
}
}
void TcpServer::CloseClientSock(){
if(clnt_sock > 0){
cout<<"关闭了连接socket"<<endl;
close(clnt_sock);
serv_sock = -1;
}
}
int TcpServer::StartFork(){
while(1){
//首先调用阻塞接受客户端连接的函数
int accept_res = Accept();
if(accept_res == -1){
cout<<"继续等待用户连接..."<<endl;
continue;
}
//客户端连接成功
cout<<"【"<<accept_res<<"】连接成功..."<<endl;
//开启一个进程
pid = fork();
//为了节约资源,在子进程和父进程中,都关闭掉彼此的socket
if(pid == 0){
cout<<"子进程开启,将对连接【"<<accept_res<<"】提供通信服务"<<endl;
//子进程的操作区域
//关闭父进程中服务端的socket
close(serv_sock);
//开始对连接的socket进行读写操作
int str_len;
char buf[BUFF_SIZE];
while(TcpBaseRead(accept_res , buf , &str_len)){
buf[str_len] = 0;
cout<<"【"<<accept_res<<"】说:"<<buf<<endl;
if(!TcpBaseWrite(accept_res , buf)){
cout<<"发送消息失败,对端关闭了socket"<<endl;
break;
}
}
cout<<"【"<<accept_res<<"】断开了连接"<<endl;
//对端断开了连接
close(accept_res);
return 10;
}else{
//父进程的操作区域
//关闭父进程中客户端的socket
close(accept_res);
}
}
CloseServerSock();
return 0;
}
/*================================================================
* Copyright (C) 2021 ymbLite. All rights reserved.
*
* 文件名称:main.cpp
* 创 建 者:ymbLite
* 创建日期:2021年11月03日
* 描 述:
*
================================================================*/
#include "TcpServer.h"
int main(int argc ,char* argv[]){
if(argc != 2){
cout<<"Usage : "<<argv[0]<<" <PORT>"<<endl;
return -1;
}
//创建服务端的socket
TcpServer tcp_server;
//初始化服务器
bool init_res = tcp_server.InitServer(argv[1]);
if(!init_res){
cout<<"服务器初始化失败"<<endl;
return -1;
}
//开启服务端的监听功能
int fork_res = tcp_server.StartFork();
cout<<"fork_res="<<fork_res<<endl;
if(fork_res != 0){
return fork_res;
}
//开始创建多进程
//while(1){
// int accept_res = tcp_server.Accept();
// if(accept_res == -1){
// cout<<"继续等待用户的连接"<<endl;
// continue;
// }
// cout<<"【"<<accept_res<<"】连接成功..."<<endl;
// pid_t pid;
// pid = fork();//创建一个子进程
// if(pid == 0){
// cout<<"子进程开启,将对socket【"<<accept_res<<"】提供通信服务。"<<endl;
// //在子进程中,首先关闭父进程的socket
// tcp_server.CloseServerSock();
// int buf_len;
// char buf[BUFF_SIZE];
// while(TcpBaseRead(accept_res , buf , &buf_len)){
// buf[buf_len] = 0 ;
// cout<<"【"<<accept_res<<"】说:"<<buf<<endl;
// if(!TcpBaseWrite(accept_res , buf)){
// cout<<"发送消息失败,对端关闭"<<endl;
// break;
// }
// }
// cout<<"【"<<accept_res<<"】断开了连接"<<endl;
// //对端断开了连接
// close(accept_res);
// cout<<"展示"<<endl;
// return 10;
// cout<<"退出..."<<endl;
// }else{
// close(accept_res);
// }
//}
//tcp_server.CloseServerSock();
return 0;
}
第三:结尾
在这里,写了一个工具类,照搬了的是B栈的freecplus写的,主要是解决TCP传输中的粘包和拆包的问题。具体可以百度,我就不再赘述。
项目依然是封装成两个程序,客户端和服务端,在客户端和服务端都添加工具类,在进行编译就可以了,注意运行的参数即可,我一般使用的是12345。
还是那句话,如果有错误的,希望多多包涵,写错了的或者设计不合理的,可以讨论,我也只是初学者,谢谢。
希望可以帮助到你。