第一:socket的基础知识
略,网上有很多这样的知识,我觉得他们应该讲的比我好。我是跟着韩国人尹圣雨写的《TCP/IP网络编程》这本书学的。
第二:使用的线程库
C++11 std::thread
在经过自己简单的封装
第三:声明
因为我也是初学,可能写的不好,封装的也不好,我写这篇文章,只是希望帮助很基础的初学者,慢慢的接触socket,也给自己记录一下学习的经过。
所以,如果错误的,或者不好的地方,望各位多多包涵,一起学习。
我是使用cMake构建项目的,我也只会很简单很简单的,这里就不贴出来了。如你不会的话,就直接用g++编译就好了。(可以百度一下g++多文件的编译)
这个我分为五篇文章,每篇文章是一个完整的项目
第一个是基础版的,也就是进行简单的封装,客户端连接服务端(仅限一个),做一个回声服务器。
第二个是使用多进程服务器,可进行多用户的连接。
第三个是使用select函数。
第四个是使用epoll。
第五个是多线程服务器。
第四:代码
客户端
/*================================================================
* 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>
#define 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(buf_len == 0){
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());
}
}
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>
#define BUFF_SZIE 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;//读取的字节长度
public:
int serv_sock;//服务器中服务端的socket描述符
int clnt_sock;//服务器中客户端的socket描述符
private:
public:
//构造函数
TcpServer();
//析构函数
~TcpServer();
/*
* 初始化服务器
* 服务端地址使用INADDR_ANY
* port:端口号
* */
bool InitServer(const string& port);
/*
* 阻塞接受客户端的连接
* */
bool Accept();
/**
* 关闭服务器中服务端的套接字
*/
void CloseServerSock();
/*
* 关闭服务器中客户端的套接字
* */
void CloseClientSock();
/*
* 读取客户端的数据
* */
int Read();
/**
* 回写客户端的数据
*/
void Write();
};
#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){
cout<<"TCP服务器的构造函数"<<endl;
}
TcpServer::~TcpServer(){
cout<<"TCP服务器的析构函数"<<endl;
}
bool TcpServer::InitServer(const string& port){
//先判断服务是否已经开启
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;
}
bool TcpServer::Accept(){
//判断服务端是否已经开启了socket
if(serv_sock == -1){
cout<<"服务器中未开启监听的socket!"<<endl;
return false;
}
//获取网络地址结构的长度
clnt_addr_len = sizeof(clnt_addr);
clnt_sock = accept(serv_sock , (struct sockaddr*)& clnt_addr , &clnt_addr_len);
if(clnt_sock == -1){
cout<<"服务器无法接受客户端的连接"<<endl;
return false;
}
return true;
}
void TcpServer::CloseServerSock(){
if(serv_sock > 0){
close(serv_sock);
serv_sock = -1;
}
}
void TcpServer::CloseClientSock(){
if(clnt_sock > 0){
close(clnt_sock);
serv_sock = -1;
}
}
int TcpServer::Read(){
if(clnt_sock == -1){
cout<<"未有客户端进行连接"<<endl;
return 0;
}
buf_len = read(clnt_sock , buf , BUFF_SZIE);
if(buf_len == 0){
//对端断开了连接,关闭对端
CloseClientSock();
return buf_len;
}
buf[buf_len] = 0;
cout<<"【"<<clnt_sock<<"】说:"<<buf<<endl;
return buf_len;
}
void TcpServer::Write(){
if(clnt_sock == -1){
cout<<"未有客户端进行连接,写入失败"<<endl;
}else{
write(clnt_sock , buf , buf_len);
}
}
/*================================================================
* 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;
}
TcpServer tcp_server;
//初始化服务器
bool init_res = tcp_server.InitServer(argv[1]);
if(!init_res){
cout<<"服务器初始化失败"<<endl;
return -1;
}
//接受客户端的连接,现在模拟只接受一个客户端的情况
bool accept_res = tcp_server.Accept();
if(!accept_res){
return -2;
}
//开始回声
while(1){
if(tcp_server.Read() == 0){
cout<<"客户端退出了"<<endl;
break;
}
tcp_server.Write();
}
tcp_server.CloseServerSock();
tcp_server.CloseClientSock();
return 0;
}
第五:结尾
我是把项目分成了客户端和服务端,如果初学者,可以直接复制进去,进行编译,就可以运行了,我代码必要的地方都会注释,希望能帮助到你。