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通信,了解到了socket的概念与udp的实现方法,今天我们来学习一下面向连接的tcp通信。 一、tcp套接字创建 UDP和TCP都是通过套接字(socket)来实现通信的,因此TCP也得使用socket()接口创建…...
HTTP协议介绍
文章目录 http协议http协议格式GET请求POST请求http客户端实现 http协议 http协议是应用层协议,一般建立在tcp协议的基础之上(当然你的实现非要基于udp也是可以的),也就是说http协议的数据收发是通过tcp协议的。 http协议也分为h…...
elasticsearch安装配置注意事项
安装Elasticsearch时,需要注意以下几个重要事项: 1、版本选择:选择与你系统和其他组件(如Logstash、Kibana)兼容的Elasticsearch版本。 2、Java环境:Elasticsearch是基于Java构建的,因此确保已…...
Istio 流量管理(请求路由、流量转移、请求重试、流量镜像、故障注入、熔断等)介绍及使用
一、Istio 流量管理 Istio是一个开源的服务网格,它为分布式微服务架构提供了网络层的抽象。它使得服务之间的通信变得更为可靠、安全,并且提供了细粒度的流量管理、监控和策略实施功能。Istio通过在服务之间插入一个透明的代理(Envoy&#x…...
Transformers中加载预训练模型的过程剖析
使用HuggingFace的Transformers库加载预训练模型来处理下游深度学习任务很是方便,然而加载预训练模型的方法多种多样且过程比较隐蔽,这在一定程度上会给人带来困惑。因此,本篇文章主要讲一下使用不同方法加载本地预训练模型的区别、加载预训练模型及其配置的过程,藉此做个记…...
使用MCU的 GPIO口 模拟SDIO时序读写TF/SD卡的可能性。
SD2.0协议详解:命令格式、初始化/读取/写入 - WangXuan的文章 - 知乎 https://zhuanlan.zhihu.com/p/610495260 参考上述与其它理解,若使用GPIO口模拟,重点与难点: 1. 时钟线问题: 在SD准备期间,需不间断…...
SSM【Spring SpringMVC Mybatis】——Mybatis(二)
如果对一些基础理论感兴趣可以看这一期👇 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参数传递【#与…...
在线教育系统在线网校报价,培训机构是怎样招聘老师的?流程是什么?
招生是培训机构的一大难题,但招不来老师,招不到好老师却也是培训机构面临的一个更为严峻的问题,没有老师,教学工作就展不开,没有老师,学生就留不住。培训学校的人员招聘不要等缺失时才去招聘,要…...
您的文件和驱动器上的“密码保护”有多安全?
某些行业(例如医疗保健、法律和公司)的人们在通过电子邮件发送文件时通常依赖密码保护,认为它可以提供足够的安全性来防止窥探。然而,对 PDF 或 Excel 文件进行简单的密码保护并不像看起来那样万无一失。 使用密码保护文件而不加…...
4000字超详解Linux权限
各位大佬好 ,这里是阿川的博客 , 祝您变得更强 个人主页:在线OJ的阿川 大佬的支持和鼓励,将是我成长路上最大的动力 阿川水平有限,如有错误,欢迎大佬指正 在Linux当中权限的体现主要有两种 普通用户 超…...
SearXNG - 一个注重隐私的互联网元搜索引擎
引言 在数字监控和数据货币化的时代,对于许多用户而言,在线保护个人信息变得至关重要。隐私问题特别突出的一个领域是搜索引擎,它们经常收集用户数据以定制个性化广告或构建用户档案。SearXNG 是一个开源的元搜索引擎,它汇集了各…...
(第15天)【leetcode题解】459、重复的子字符串
目录 459、重复的子字符串题目描述暴力匹配思路代码 字符串匹配思路代码与暴力匹配的不同 KMP解法思路代码KMP算法的核心和用途 459、重复的子字符串 题目描述 给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。 暴力匹配 思路 推理 如果…...
PostgreSQL的学习心得和知识总结(一百四十二)|深入理解PostgreSQL数据库数据库之 Continuous Integration
目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库…...
【外币兑换,简单贪心】
小明刚从美国回来,发现手上还有一些未用完的美金,于是想去银行兑换成人民币。可是听说最近人民币将会升值,并从金融机构得到了接下来十二个月可能的美元对人民币汇率,现在,小明想要在接下来一年中把美金都兑换成人民币…...
数据库入门(sql文档+命令行)
一.基础知识 1.SQL(Structured Query Language)结构化查询语言分类: DDL数据定义语言用来定义数据库对象:数据库、表、字段DML数据操作语言对数据库进行增删改查DQL数据查询语言查询数据库中表的信息DCL数据控制语言用来创建数据…...
【机器学习300问】84、AdaGrad算法是为了解决什么问题?
神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题,解决这个问题的过程称为最优化。因为参数空间非常复杂,无法轻易找到最优解,而且在深度神经网络中,参数的数量非常庞大,导致最优化问…...
Java算法-力扣leetcode-14. 最长公共前缀
14. 最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 ""。 示例 1: 输入: strs ["flower","flow","flight"] 输出: "fl"示…...
视频拼接融合产品的产品与架构设计(二)
视频拼接融合产品的产品与架构设计一 以上是第一期,以前思考的时候还是比较着急,现在思考的更多了,现实世界的拼接更加需要我们沉下心来做,尤其是对于更多画面,画面更加清晰怎么做 本篇章不在于其他功能,在…...
【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框架精品项目【用于个人学习】
源码获取:私聊回复【项目关键字】获取 更多选题参考: Java练手项目 & 个人学习等选题参考 推荐菜鸟教程Java学习、Javatpoint学习 前言 大家好,我是二哈喇子,此博文整理了各种项目需求 此文下的项目用于博主自己学习&#x…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...





