当前位置: 首页 > news >正文

Linux——socket编程之tcp通信

前言

前面我们学习socket的udp通信,了解到了socket的概念与udp的实现方法,今天我们来学习一下面向连接的tcp通信。

一、tcp套接字创建

UDP和TCP都是通过套接字(socket)来实现通信的,因此TCP也得使用socket()接口创建套接字。

socket()    ->创建套接字

  • 参数domain:代表协议家族,输入AF_INET为IPv4协议,该参数告诉操作系统如何解释后面的type和protocol参数
  • 参数type:指定了套接字的类型,即指定了套接字的通信方式和数据传输方式,输入SOCK_STREAM面向字节流
  • 参数protocol:代表所使用的具体协议,输入0让系统自动选择合适的协议

二、填充网络信息并bind

创建好了基于tcp的IPv4套接字,我们还需要填充本地网络信息,知道了本地ip地址和端口号,和socket创建的sockfd进行绑定,后面就可以通过sockfd进行通信了

如下是 IPv4 地址的结构体sockaddr_in 。

struct sockaddr_in {short int sin_family;            // 地址族(Address Family),一般为 AF_INETunsigned short int sin_port;     // 端口号(Port),使用网络字节顺序(big-endian)struct in_addr sin_addr;         // IPv4 地址unsigned char sin_zero[8];       // 未使用的填充字段,通常设置为 0
};

填充完毕就可以开始bind了。

bind()   ->让socket信息与sockfd进行绑定

  • 参数sockfd:套接字文件描述符  传入之前创建的socket返回值
  • 参数addr:创建好的sockaddr结构体的地址
  • 参数addrlen:网络信息结构体的长度的长度

三、建立连接与监听

由于tcp是面向连接的,因此客户端和服务器双方要进行数据通信,必须要先建立连接。一般都是客户端去发起连接请求,想让服务端为他进行服务。比如你想刷抖音,那么你得打开抖音,申请去刷抖音,而不是说抖音舔着脸求你刷我,虽然他也想要日活,但是主动权还是在你手上的。

那服务端也需要去监听连接的到来,方便为客户端服务,因此有连接和监听两种状态。

connect()  建立连接

参数与bind一样

 listen()  监听连接

  • 参数sockfd:套接字描述符
  • 参数backlog:指定连接请求的最大排队数量,即在队列中等待接受连接的最大数量。他只是用于控制等待连接队列的长度,并不是服务器的最大并发连接数。
  • 返回值,成功返回0

listen返回值不为0证明listen失败。 

四、获取连接

客户端使用connect与服务端进行连接,服务端listen将自己设置为监听状态,代表能接受连接,但是你只是说自己能接受,并不会创建与客户端的连接。

正在的连接需要服务端既要listen进行监听,又要accept进行获取连接

accept()  从处于监听状态的套接字中接受一个传入的连接请求,并创建一个新的套接字来与客户端进行通信。

  • 参数sockfd:处于监听状态的套接字描述符
  • 参数addr:创建好的sockaddr结构体的地址
  • 参数addrlen:创建好的sockaddr结构体的长度的地址
  • 返回值:返回一个sockfd,使用返回的这个sockfd与客户端进行通信。

也就是说,在tcp通信中,是有两个sockfd的,之前我们socket、bind、listen、accept使用的都是同一个sockfd,但是链接已经完全建立好之后,我们要与客户端进行通信,需要使用accept返回的sockfd

这就类似于在街边拉客的美容店,张三在外面寻找客人,带到美容店去消费,进入美容店后张三就不再管你了,而是让美容店里的员工对你进行服务,张三转头又去拉客去了。

五、tcp通信的实现

有了这些预备知识,我们就可以编写代码了,具体代码如下,注释写的比较详细

Comm.hpp   (错误码)

#pragma once//错误码
enum{Usage_Err = 1,Socket_Err,Bind_Err,Listen_Err,Connect_Err,
};

 InetAddr.hpp  (封装sockaddr_in结构体)

#pragma once
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
class InetAddr
{
public:InetAddr(struct sockaddr_in& peer):_addr(peer){_port = ntohs(peer.sin_port); // ntohs网络转主机short// inet_ntoa 多线程下不安全 使用静态缓冲区来存储结果,并返回指向这个静态缓冲区的指针// 每次调用都会覆盖这个静态缓冲区的内容// _ip = inet_ntoa(peer.sin_addr); // inet_ntoa sin_addr转点分十进制字符串ip// 现在我们自己维护空间ipbuff,让inet_ntop把数据写到ipbuff里  这样线程安全char ipbuff[64]; inet_ntop(AF_INET,&peer.sin_addr,ipbuff,sizeof(ipbuff));_ip = ipbuff;}string GetIp(){return _ip;}uint16_t GetPort(){return _port;}struct sockaddr_in& GetAddr(){return _addr;}string PrintDebug(){string info = _ip;info+=":";info+=to_string(_port);return info;}~InetAddr(){}private:string _ip;uint16_t _port;struct sockaddr_in _addr;
};

 LockGuard.hpp  (锁的守护者)就是C++11里的lock_guard

#pragma once
#include <pthread.h>// 不定义锁,外部会传递锁
class Mutex
{
public:Mutex(pthread_mutex_t *lock): _lock(lock){}void Lock(){pthread_mutex_lock(_lock);}void UnLock(){pthread_mutex_unlock(_lock);}~Mutex(){}private:pthread_mutex_t *_lock;
};class LockGuard
{
public:LockGuard(pthread_mutex_t *lock): _mutex(lock){_mutex.Lock();}~LockGuard(){_mutex.UnLock();}
private:Mutex _mutex;
};

Log.hpp  (日志类) 

#pragma once#include<iostream>
#include<cstdarg>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
using namespace std;
enum{Debug = 0,Info,Warning,Error,Fatal
};enum{Screen = 10,OneFile,ClassFile
};string LevelToString(int level)
{switch (level){case Debug:return "Debug";case Info:return "Info";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "Unkonw"; }
}const int default_style = Screen;
const string default_filename = "Log.";
const string logdir = "log";class Log  
{
public:Log(int style = default_style,string filename = default_filename):_style(style),_filename(filename){if(_style != Screen)mkdir(logdir.c_str(),0775);}//更改打印方式void Enable(int style){_style = style;if(_style != Screen)mkdir(logdir.c_str(),0775);}//时间戳转化为年月日时分秒string GetTime(){time_t currtime = time(nullptr);struct tm* curr = localtime(&currtime);char time_buffer[128];snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",curr->tm_year+1900,curr->tm_mon+1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);return time_buffer;}//写入到文件中void WriteLogToOneFile(const string& logname,const string& message){FILE* fp = fopen(logname.c_str(),"a");if(fp==nullptr){perror("fopen filed");exit(-1);}fprintf(fp, "%s\n", message.c_str());fclose(fp);}//打印日志void WriteLogToClassFile(const string& levelstr,const string& message){string logname = logdir;logname+="/";logname+=_filename;logname+=levelstr;WriteLogToOneFile(logname,message);}void WriteLog(const string& levelstr,const string& message){switch (_style) {case Screen:cout<<message<<endl;//打印到屏幕中break;case OneFile:WriteLogToClassFile("all",message);//给定all,直接写到all里break;case ClassFile:WriteLogToClassFile(levelstr,message);//写入levelstr里break;default:break;}}//打印日志void LogMessage(int level,const char* format,...){char rightbuffer[1024];//处理消息va_list args;   //va_list 是指针va_start(args,format);//初始化va_list对象,format是最后一个确定的参数//现在args指向了可变参数部分vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中va_end(args);char leftbuffer[1024];//处理日志等级、pid、时间string levelstr = LevelToString(level);string currtime = GetTime();string idstr = to_string(getpid());snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str(),currtime.c_str(),idstr.c_str());string loginfo = leftbuffer;loginfo+=rightbuffer;WriteLog(levelstr,loginfo);}//提供接口给运算符重载使用void _LogMessage(int level,char* rightbuffer){char leftbuffer[1024];string levelstr = LevelToString(level);string currtime = GetTime();string idstr = to_string(getpid());snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str(),currtime.c_str(),idstr.c_str());string messages = leftbuffer;messages+=rightbuffer;WriteLog(levelstr,messages);}//运算符重载void operator()(int level,const char* format,...){char rightbuffer[1024];va_list args;   //va_list 是指针va_start(args,format);//初始化va_list对象,format是最后一个确定的参数vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中va_end(args);_LogMessage(level,rightbuffer);}~Log() {}
private:int _style;string _filename;
};Log lg;class Conf
{
public:Conf(){lg.Enable(Screen); }~Conf(){}
};Conf conf;

 nocopy.hpp   (让服务器类继承nocopy,达到不可拷贝的作用)

#pragma onceclass nocopy
{
public:nocopy(){}nocopy(const nocopy& n) = delete;nocopy& operator=(const nocopy& n) = delete;~nocopy(){}
}; 

Thread.hpp    Thread库封装

#pragma once#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>// 设计方的视角
//typedef std::function<void()> func_t;
namespace kky
{template<class T>
using func_t = std::function<void(T&)>;template<class T>
class Thread
{
public:Thread(const std::string &threadname, func_t<T> func, T &data):_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data){}// 不加static会有this指针,无法调用pthread_creadtestatic void *ThreadRoutine(void *args) // 类内方法,{// (void)args; // 仅仅是为了防止编译器有告警Thread *ts = static_cast<Thread *>(args);ts->_func(ts->_data);return nullptr;}//运行线程bool Start(){int n = pthread_create(&_tid, nullptr, ThreadRoutine, this/*?*/);if(n == 0) {_isrunning = true;return true;}else return false;}//等待线程bool Join(){if(!_isrunning) return true;int n = pthread_join(_tid, nullptr);if(n == 0){_isrunning = false;return true;}return false;}std::string ThreadName(){return _threadname;}bool IsRunning(){return _isrunning; }~Thread(){}
private:pthread_t _tid;std::string _threadname;bool _isrunning;func_t<T> _func;T _data;
}; 
}

 Threadpool.hpp  (线程库的封装)

#pragma once#include <pthread.h>
#include <vector>
#include <functional>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace std;namespace kky
{ 
static const int default_num = 5;class ThreadData
{
public:ThreadData(string name): thread_name(name){}string thread_name;
};template <class T>
class ThreadPool
{
private:ThreadPool(int thread_num = default_num): _thread_num(thread_num){pthread_mutex_init(&_mutex, nullptr); // 初始化pthread_cond_init(&_cond, nullptr);// 创建指定个数的线程for (int i = 0; i < _thread_num; i++){string thread_name = "thread_";thread_name += to_string(i + 1);ThreadData td(thread_name); // ThreadData为线程数据类型Thread<ThreadData> t(thread_name, bind(&ThreadPool<T>::ThreadRun, this, placeholders::_1), td);_threads.emplace_back(t);lg(Info, "%s 被创建...", thread_name.c_str()); // 写入}}ThreadPool(const ThreadPool<T> &tp) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> &tp) = delete;public:static ThreadPool<T> *GetInstance(){if(instance == nullptr) {LockGuard lockguard(&sig_lock);if (instance == nullptr){instance = new ThreadPool<T>();lg.LogMessage(Info, "创建单例成功...");}}return instance;}// 线程运行bool Start(){for (auto &thread : _threads){thread.Start();lg.LogMessage(Info, "%s 正在运行!", thread.ThreadName().c_str());}}// 线程条件变量等待void ThreadWait(ThreadData &td){lg.LogMessage(Debug, "没有任务,%s休眠了", td.thread_name.c_str());pthread_cond_wait(&_cond, &_mutex);}// 线程条件变量唤醒void ThreadWakeUp(){pthread_cond_signal(&_cond);}// 执行任务void ThreadRun(ThreadData &td){while (1){T t;// 取出任务{LockGuard lockguard(&_mutex); // 代码块中自动加锁与解锁while (_q.empty()){ThreadWait(td);lg.LogMessage(Debug, "有任务了,%s去执行任务了", td.thread_name.c_str());}t = _q.front();_q.pop();}t();// 处理任务 我们通过打印消息来模拟任务//  cout<<t<<endl;// lg.LogMessage(Debug, "%s 计算结果为:%d", td.thread_name.c_str(), t);}}// 将任务放到队列中void Push(const T &in){{LockGuard lockguard(&_mutex);_q.push(in);}lg.LogMessage(Debug, "任务push成功");ThreadWakeUp();}~ThreadPool(){pthread_mutex_destroy(&_mutex); // 销毁pthread_cond_destroy(&_cond);}// 进程等待void Wait(){for (auto &thread : _threads){thread.Join();}}private:queue<T> _q;vector<Thread<ThreadData>> _threads;int _thread_num;pthread_mutex_t _mutex;pthread_cond_t _cond;static ThreadPool<T> *instance;static pthread_mutex_t sig_lock;
};template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;template<class T>
pthread_mutex_t ThreadPool<T>::sig_lock = PTHREAD_MUTEX_INITIALIZER;
}

 TcpServer.hpp   (服务端的封装)

#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unordered_map>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <thread>
#include "Log.hpp"
#include "Comm.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Threadpool.hpp"class ThreadData;
static const int default_backlog = 5;
using func_t = function<void(ThreadData *)>;
using task_t = function<void()>;
using callback_t = function<void(int sockfd, InetAddr &addr)>;class ThreadData
{
public:ThreadData(int sock, struct sockaddr_in &peer): _sockfd(sock), _addr(peer){}~ThreadData(){close(_sockfd);}public:int _sockfd;InetAddr _addr;
};class TcpServer : public nocopy
{
public:TcpServer(uint16_t port): _port(port), _isrunning(false){}void HandlerRequest(ThreadData *td){Service1(td->_sockfd);delete td;}void Init(){// 1.创建套接字  得到文件描述符_listen_sockfd = socket(AF_INET, SOCK_STREAM, 0); // SOCK_STREAM 代表TCP字节流if (_listen_sockfd < 0){lg.LogMessage(Fatal, "create socket error, \errno code: %d,error string: %s",errno, strerror(errno));exit(Socket_Err);}lg.LogMessage(Debug, "create socket success, sockfd: %d", _listen_sockfd);// 固定写法,解决一些少量bind失败的问题int opt = 1;setsockopt(_listen_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));// 2.填充网络信息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;// 3.bindint n = bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local));if (n != 0){lg.LogMessage(Fatal, "bind socket error, \errno code: %d,error string: %s",errno, strerror(errno));exit(Bind_Err);}lg.LogMessage(Debug, "bind socket success, sockfd: %d", _listen_sockfd);// 4.设置监听状态,TCP特有的if (listen(_listen_sockfd, default_backlog) != 0){lg.LogMessage(Fatal, "listen socket error, \errno code: %d,error string: %s",errno, strerror(errno));exit(Listen_Err);}lg.LogMessage(Debug, "listen socket success, sockfd: %d", _listen_sockfd);kky::ThreadPool<task_t>::GetInstance()->Start();funcs.insert(make_pair("defaultService",std::bind(&TcpServer::DefaultService,this,placeholders::_1,placeholders::_2)));}// accept获取的sockfd是全双工的,因此我们read和write都可以用这个sockfdvoid Service1(int sockfd){char buff[1024];while (true){ssize_t n = read(sockfd, buff, sizeof(buff) - 1);if (n > 0){buff[n] = 0;cout << "client say# " << buff << endl;string echo_string = "server echo# ";echo_string += buff;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0) // 表示对面关闭了连接{lg.LogMessage(Info, "client quit...");break;}else{lg.LogMessage(Error, "read socket error, \errno code: %d,error string: %s",errno, strerror(errno));break;}}}void Service2(int sockfd, InetAddr addr){char buff[1024];while (true){ssize_t n = read(sockfd, buff, sizeof(buff) - 1);if (n > 0){buff[n] = 0;cout << "[" << addr.PrintDebug() << "]#" << buff << endl;string echo_string = "server echo# ";echo_string += buff;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0) // 表示对面关闭了连接{lg.LogMessage(Info, "client quit...");break;}else{lg.LogMessage(Error, "read socket error, \errno code: %d,error string: %s",errno, strerror(errno));break;}}}void Start(){_isrunning = true;// 忽略SIGCHLD信号,那么子进程退出时内核会自动回收其资源,并且不会产生僵尸进程// 与下面v3版本多线程一起使用,如果不是V3版本,这句代码无用signal(SIGCHLD, SIG_IGN);while (_isrunning){struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);// 5.获取连接int sockfd = accept(_listen_sockfd, (struct sockaddr *)&peer, &len);if (sockfd < 0){lg.LogMessage(Warning, "accept socket error");continue;}lg.LogMessage(Debug, "accept socket success,get a new sockfd: %d", sockfd);// 6.提供服务// 6.v1版本 只能一个为一个进程服务//  Service1(sockfd);//  close(sockfd);// v2  多进程 fork()孙子进程版本//  pid_t id = fork();//  if(id<0)//  {//      close(sockfd);//      continue;//  }//  else if(id ==0)//  {//      //子进程不用关心_listen_sockfd,因为父进程在listen。子进程只服务就好//      close(_listen_sockfd);//      if(fork()>0)//          exit(0);//     //孙子进程 父进程退出了,他变成孤儿进程,//     //被操作系统领养 死亡自动回收,不需要wait了//     Service1(sockfd);//     close(sockfd);//     exit(0);// }// else// {//     //父进程//     close(sockfd); //sockfd交给子进程去服务了//     //nullptr代表不需要子进程的退出信息,0代表阻塞等待//     pid_t rid = waitpid(id,nullptr,0);//     if(rid == id)//     {//         //等待成功  //去干你想干的事情//     }// }// v3 多进程 信号版本  //与104行signal(SIGCHLD,SIG_IGN);一起使用// 通过信号忽略的方式让父进程不再管子进程// pid_t id = fork();// if(id < 0)// {//     close(sockfd);//     continue;// }// else if(id == 0)// {//     //子进程//     close(_listen_sockfd);//     Service1(sockfd);//     close(sockfd);//     exit(0);// }// else// {//     //父进程//     close(sockfd);// }// v4 多线程版本// ThreadData *td = new ThreadData(sockfd,peer);// //不直接传sockfd,是担心主线程while循坏去将sockfd覆盖,因此传递对象指针// func_t f = bind(&TcpServer::HandlerRequest,this,placeholders::_1);// thread t1(f,td);// t1.detach();// v5 线程池版本task_t f = bind(&TcpServer::Routine, this, sockfd, peer);kky::ThreadPool<task_t>::GetInstance()->Push(f);}}//服务器去读取客户端的输入信息,依据输入提供服务string Read(int sockfd){char buff[1024];ssize_t n = read(sockfd, buff, sizeof(buff) - 1);if (n > 0){buff[n] = 0;}else if (n == 0) // 表示对面关闭了连接{lg.LogMessage(Info, "client quit...");}else{lg.LogMessage(Error, "read socket error, \errno code: %d,error string: %s",errno, strerror(errno));}return buff;}void Routine(int sockfd,InetAddr addr){funcs["defaultService"](sockfd,addr);string type = Read(sockfd);lg.LogMessage(Debug,"%s select %s",addr.PrintDebug().c_str(),type.c_str());if(type == "ping")funcs[type](sockfd,addr);else if(type == "translate")funcs[type](sockfd,addr);else funcs["defaultService"](sockfd,addr);close(sockfd);}void DefaultService(int sockfd,InetAddr& addr){std::string service_list = " | ";for(auto& func : funcs){service_list += func.first; service_list +=" | ";}write(sockfd,service_list.c_str(),service_list.size());}// 添加string->callback_t 方法的映射void RegisterFunc(const string &name, callback_t func){funcs[name] = func;}~TcpServer(){}private:uint16_t _port;int _listen_sockfd;bool _isrunning;// 输入"xxx" 就去执行 xxx 任务unordered_map<string, callback_t> funcs;
};

 Main.cc   (服务端的入口)

#include "TcpServer.hpp"#include <memory>using namespace std;string unknown = "unknown";
class Dictionary
{
public:Dictionary(){dict.insert(make_pair<string,string>("banana","香蕉"));dict.insert(make_pair<string,string>("apple","苹果"));dict.insert(make_pair<string,string>("monkey","猴子"));dict.insert(make_pair<string,string>("love","爱"));}string Excute(const string& word){if(dict.find(word)!=dict.end())return dict[word];return unknown;}~Dictionary(){}
private:unordered_map<string,string> dict;
};Dictionary ts;void Usage(string proc)
{cout << "Usage \n\t" << proc << "local_port\n"<< endl;
}//判断服务器是否正常运行————>心跳机制  客户ping,就会获得pong 证明服务端没问题
void Ping(int sockfd, InetAddr addr)
{lg.LogMessage(Debug, "%s select %s success, fd : %d", addr.PrintDebug().c_str(), "ping", sockfd);char buffer[1024];ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0)buffer[n] = 0;string echo_string = "Pong";write(sockfd,echo_string.c_str(),echo_string.size());
}void Translate(int sockfd, InetAddr addr)
{lg.LogMessage(Debug, "%s select %s success, fd : %d", addr.PrintDebug().c_str(), "translate", sockfd);char wordbuf[128];ssize_t n = read(sockfd, wordbuf, sizeof(wordbuf) - 1);if (n > 0)wordbuf[n] = 0;string chinese = ts.Excute(wordbuf);write(sockfd,chinese.c_str(),chinese.size());lg.LogMessage(Debug,"%s Translate service,%s->%s",addr.PrintDebug().c_str(),wordbuf,chinese.c_str());
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);return Usage_Err;}uint16_t port = stoi(argv[1]);unique_ptr<TcpServer> tsvr(new TcpServer(port));tsvr->RegisterFunc("ping", Ping);tsvr->RegisterFunc("translate", Translate);tsvr->Init();tsvr->Start();
}

 TcpClient.cc   (客户端的入口)

#include <iostream>
#include <string>
#include <cstring>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include "Comm.hpp"
using namespace std;#define Retry_Count 5void Usage(string proc)
{cout << "Usage \n\t" << proc << " local_ip local_port\n"<< endl;
}bool VisitServer(const string &ip, const uint16_t port, int &cnt)
{int n = 0;string inbuffer;char service_list[1024];ssize_t w = 0; // write 返回值,由于goto原因  需要放在前面ssize_t r = 0; // read  返回值,由于goto原因  需要放在前面bool ret = true;// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){cerr << "socket error" << endl;ret = false;goto END;}// 2.填写sockaddr_in信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(port);// inet_pton 类似于 inet_addr 都是让点分十进制 IP 转网络字节序的二进制序列inet_pton(AF_INET, ip.c_str(), &server.sin_addr.s_addr);// 3.connect进行连接,他会自动绑定,不需要手动绑定n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){cerr << "connect error" << endl;ret = false;goto END;}//先读取操作列表r = read(sockfd, service_list, sizeof(service_list) - 1);if (r > 0){service_list[r] = 0;cout << "服务器提供的服务列表是:" << service_list << endl;}cnt = 1; // 让cnt又从1-5再次链接cout << "Please Select Service# ";getline(cin, inbuffer);// connect并没有产生新的sockfd,只用这一个sockfd进行通信即可。w = write(sockfd, inbuffer.c_str(), inbuffer.size());if (w > 0){char buffer[1024];cout << "Please Enter# ";getline(cin, inbuffer);write(sockfd, inbuffer.c_str(), inbuffer.size());r = read(sockfd, buffer, sizeof(buffer) - 1);if (r > 0){buffer[r] = 0;cout << buffer << endl;}else if (r == 0) // read返回值为0,代表读端关闭,算是正常结束{goto END;}else{ret = false;goto END;}}else if (w == 0){cout << "你并没有输入" << endl;}else{cout << "write时服务器关闭" << endl;ret = false;}
END:close(sockfd);return ret;
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(Usage_Err);}signal(SIGPIPE, SIG_IGN);string ip = argv[1];uint16_t port = stoi(argv[2]);// 断线重连int cnt = 1;while (cnt <= Retry_Count){int result = VisitServer(ip, port, cnt);if (result)break;else{sleep(1);cout << "server offline, retrying..., count: " << cnt++ << endl;}}if (cnt > Retry_Count){cout << "server offline" << endl;}
}

 Makefile (一键构建代码)

.PHONY:all
all:tcp_server tcp_client
tcp_server:Main.ccg++ -o $@ $^ -std=c++11 -lpthread
tcp_client:TcpClient.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f tcp_server tcp_client

 代码链接

运行结果如下

相关文章:

Linux——socket编程之tcp通信

前言 前面我们学习socket的udp通信&#xff0c;了解到了socket的概念与udp的实现方法&#xff0c;今天我们来学习一下面向连接的tcp通信。 一、tcp套接字创建 UDP和TCP都是通过套接字&#xff08;socket&#xff09;来实现通信的&#xff0c;因此TCP也得使用socket()接口创建…...

HTTP协议介绍

文章目录 http协议http协议格式GET请求POST请求http客户端实现 http协议 http协议是应用层协议&#xff0c;一般建立在tcp协议的基础之上&#xff08;当然你的实现非要基于udp也是可以的&#xff09;&#xff0c;也就是说http协议的数据收发是通过tcp协议的。 http协议也分为h…...

elasticsearch安装配置注意事项

安装Elasticsearch时&#xff0c;需要注意以下几个重要事项&#xff1a; 1、版本选择&#xff1a;选择与你系统和其他组件&#xff08;如Logstash、Kibana&#xff09;兼容的Elasticsearch版本。 2、Java环境&#xff1a;Elasticsearch是基于Java构建的&#xff0c;因此确保已…...

Istio 流量管理(请求路由、流量转移、请求重试、流量镜像、故障注入、熔断等)介绍及使用

一、Istio 流量管理 Istio是一个开源的服务网格&#xff0c;它为分布式微服务架构提供了网络层的抽象。它使得服务之间的通信变得更为可靠、安全&#xff0c;并且提供了细粒度的流量管理、监控和策略实施功能。Istio通过在服务之间插入一个透明的代理&#xff08;Envoy&#x…...

Transformers中加载预训练模型的过程剖析

使用HuggingFace的Transformers库加载预训练模型来处理下游深度学习任务很是方便,然而加载预训练模型的方法多种多样且过程比较隐蔽,这在一定程度上会给人带来困惑。因此,本篇文章主要讲一下使用不同方法加载本地预训练模型的区别、加载预训练模型及其配置的过程,藉此做个记…...

使用MCU的 GPIO口 模拟SDIO时序读写TF/SD卡的可能性。

SD2.0协议详解&#xff1a;命令格式、初始化/读取/写入 - WangXuan的文章 - 知乎 https://zhuanlan.zhihu.com/p/610495260 参考上述与其它理解&#xff0c;若使用GPIO口模拟&#xff0c;重点与难点&#xff1a; 1. 时钟线问题&#xff1a; 在SD准备期间&#xff0c;需不间断…...

SSM【Spring SpringMVC Mybatis】——Mybatis(二)

如果对一些基础理论感兴趣可以看这一期&#x1f447; SSM【Spring SpringMVC Mybatis】——Mybatis 目录 1、Mybatis中参数传递问题 1.1 单个普通参数 1.2 多个普通参数 1.3 命名参数 1.4 POJO参数 1.5 Map参数 1.6 Collection|List|Array等参数 2、Mybatis参数传递【#与…...

在线教育系统在线网校报价,培训机构是怎样招聘老师的?流程是什么?

招生是培训机构的一大难题&#xff0c;但招不来老师&#xff0c;招不到好老师却也是培训机构面临的一个更为严峻的问题&#xff0c;没有老师&#xff0c;教学工作就展不开&#xff0c;没有老师&#xff0c;学生就留不住。培训学校的人员招聘不要等缺失时才去招聘&#xff0c;要…...

您的文件和驱动器上的“密码保护”有多安全?

某些行业&#xff08;例如医疗保健、法律和公司&#xff09;的人们在通过电子邮件发送文件时通常依赖密码保护&#xff0c;认为它可以提供足够的安全性来防止窥探。然而&#xff0c;对 PDF 或 Excel 文件进行简单的密码保护并不像看起来那样万无一失。 使用密码保护文件而不加…...

4000字超详解Linux权限

各位大佬好 &#xff0c;这里是阿川的博客 &#xff0c; 祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 在Linux当中权限的体现主要有两种 普通用户 超…...

SearXNG - 一个注重隐私的互联网元搜索引擎

引言 在数字监控和数据货币化的时代&#xff0c;对于许多用户而言&#xff0c;在线保护个人信息变得至关重要。隐私问题特别突出的一个领域是搜索引擎&#xff0c;它们经常收集用户数据以定制个性化广告或构建用户档案。SearXNG 是一个开源的元搜索引擎&#xff0c;它汇集了各…...

(第15天)【leetcode题解】459、重复的子字符串

目录 459、重复的子字符串题目描述暴力匹配思路代码 字符串匹配思路代码与暴力匹配的不同 KMP解法思路代码KMP算法的核心和用途 459、重复的子字符串 题目描述 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 暴力匹配 思路 推理 如果…...

PostgreSQL的学习心得和知识总结(一百四十二)|深入理解PostgreSQL数据库数据库之 Continuous Integration

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…...

【外币兑换,简单贪心】

小明刚从美国回来&#xff0c;发现手上还有一些未用完的美金&#xff0c;于是想去银行兑换成人民币。可是听说最近人民币将会升值&#xff0c;并从金融机构得到了接下来十二个月可能的美元对人民币汇率&#xff0c;现在&#xff0c;小明想要在接下来一年中把美金都兑换成人民币…...

数据库入门(sql文档+命令行)

一.基础知识 1.SQL&#xff08;Structured Query Language&#xff09;结构化查询语言分类&#xff1a; DDL数据定义语言用来定义数据库对象&#xff1a;数据库、表、字段DML数据操作语言对数据库进行增删改查DQL数据查询语言查询数据库中表的信息DCL数据控制语言用来创建数据…...

【机器学习300问】84、AdaGrad算法是为了解决什么问题?

神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题&#xff0c;解决这个问题的过程称为最优化。因为参数空间非常复杂&#xff0c;无法轻易找到最优解&#xff0c;而且在深度神经网络中&#xff0c;参数的数量非常庞大&#xff0c;导致最优化问…...

Java算法-力扣leetcode-14. 最长公共前缀

14. 最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 ""。 示例 1&#xff1a; 输入&#xff1a; strs ["flower","flow","flight"] 输出&#xff1a; "fl"示…...

视频拼接融合产品的产品与架构设计(二)

视频拼接融合产品的产品与架构设计一 以上是第一期&#xff0c;以前思考的时候还是比较着急&#xff0c;现在思考的更多了&#xff0c;现实世界的拼接更加需要我们沉下心来做&#xff0c;尤其是对于更多画面&#xff0c;画面更加清晰怎么做 本篇章不在于其他功能&#xff0c;在…...

【docker 】push 镜像到私服

查看镜像 docker images把这个hello-world 推送到私服 docker push hello-world:latest 报错了。不能推送。需要标记镜像 标记Docker镜像 docker tag hello-world:latest 192.168.2.1:5000/hello-world:latest 将Docker镜像推送到私服 docker push 192.168.2.1:5000/hello…...

Java框架精品项目【用于个人学习】

源码获取&#xff1a;私聊回复【项目关键字】获取 更多选题参考&#xff1a; Java练手项目 & 个人学习等选题参考 推荐菜鸟教程Java学习、Javatpoint学习 前言 大家好&#xff0c;我是二哈喇子&#xff0c;此博文整理了各种项目需求 此文下的项目用于博主自己学习&#x…...

每周一算法:无向图的最小环

题目链接 观光之旅 题目描述 给定一张无向图&#xff0c;求图中一个至少包含 3 3 3 个点的环&#xff0c;环上的节点不重复&#xff0c;并且环上的边的长度之和最小。 该问题称为无向图的最小环问题。 你需要输出最小环的方案&#xff0c;若最小环不唯一&#xff0c;输出…...

分布式websocket IM即时通讯聊天开源项目如何启动

前言 自己之前分享了分布式websocket的视频有同学去fork项目了&#xff0c;自己启动一下更方便理解项目嘛。然后把项目启动需要的东西全部梳理出来。支持群聊单聊,表情包以及发送图片。 支持消息可靠&#xff0c;消息防重&#xff0c;消息有序。同时基础架构有分布式权限&…...

tensorflow学习笔记(1)环境准备写个简单例子(小白手册)-20240506

一、安装python、tensorflow 1、Mac上默认python已经安装,自带pip 2、pip3 install tensorflow 如果报错,提示pip3版本较低,可以根据提示来更新pip3:/Library/Developer/CommandLineTools/usr/bin/python3 -m pip install --upgrade pip 3、然后再使用pip3来安装tensor…...

kubernate 基本概念

一 K8S 是什么&#xff1f; K8S 全称&#xff1a;Kubernetes 1 kubernate基本概念 作用&#xff1a; 用于自动部署、扩展和管理“容器化&#xff08;containerized&#xff09;应用程序”的开源系统。 可以理解成 K8S 是负责自动化运维管理多个容器化程序&#xff08;比如…...

【系统架构师】-选择题(十二)计算机网络

1、网闸的作用&#xff1a;实现内网与互联网通信&#xff0c;但内网与互联网不是直连的 2、管理距离是指一种路由协议的路由可信度。15表示该路由信息比较可靠 管理距离越小&#xff0c;它的优先级就越高&#xff0c;也就是可信度越高。 0是最可信赖的&#xff0c;而255则意味…...

代码随想录|总结篇

完结篇&#xff1a; 60天&#xff0c;还是坚持了下来&#xff0c;达成算法路上的一个小目标。 加入代码随想录训练营之前&#xff0c;也断断续续刷到了树那一章节&#xff0c;但后面因为导师项目等种种情况&#xff0c;一直耽搁到年后。年后打算重新开始刷题时&#xff0c;正好…...

网络编程套接字和传输层tcp,udp协议

认识端口号 我们知道在网络数据传输的时候&#xff0c;在IP数据包头部有两个IP地址&#xff0c;分别叫做源IP地址和目的IP地址。IP地址是帮助我们在网络中确定最终发送的主机&#xff0c;但是实际上数据应该发送到主机上指定的进程上的&#xff0c;所以我们不仅要确定主机&…...

通过wget下载ftp文件

通过wget下载ftp文件 基础用法带密码的http文件带密码的ftp文件补充 基础用法 在下载的过程中会显示进度条&#xff0c;包含百分比&#xff0c;已下载字节&#xff0c;下载速度&#xff0c;剩余时间。 # 下载单个文件 wget [url_file]# 下载目录全部文件 wget [url_dir/*] wg…...

Acrobat Pro DC 2023 for Mac:PDF处理的终极解决方案

Acrobat Pro DC 2023 for Mac为Mac用户提供了PDF处理的终极解决方案。它具备强大的文档处理能力&#xff0c;无论是查看、编辑还是创建PDF文件&#xff0c;都能轻松胜任。在编辑功能方面&#xff0c;Acrobat Pro DC 2023支持对文本、图像进行精准的修改和调整&#xff0c;还能添…...

map容器

目录 map构造和赋值 map大小和交换 map插入和删除 map查找和统计 map排序 map构造和赋值 map中所有元素都是pair&#xff08;即一对&#xff09; pair中第一个元素为key&#xff08;键值&#xff09;&#xff0c;起到索引作用&#xff0c;第二个元素为value&#xff08;…...