Linux网络Socket编程TCP
相关接口
创建socket和udp的差不多,只不过type设为SOCK_STREAM,绑定方式一样,相比udp,要设置监听的状态,还要建立连接。
监听:
接收连接
成功了,返回一个文件描述符,将来通信就用这个fd,而之前创建socket的fd是用来获取新连接的。
建立连接
成功0,失败-1。
使用案例
回显服务器
//Log.hpp
#pragma once
#include<unistd.h>
#include<iostream>
#include<time.h>
#include<stdarg.h>
#include<fstream>
#include<string.h>
#include<pthread.h>
enum
{DEBUG = 1,INFO,WARNING,ERROR,FATAL
};
const std::string logfile = "log.txt";
pthread_mutex_t _mutex;
#define SCREEN_TYPE 1
#define FILE_TYPE 2
std::string LevelToString(int level)
{switch (level){case DEBUG:return "DEBUG";break;case INFO:return "INFO";break;case ERROR:return "ERROR";break;case WARNING:return "WARNING";break;case FATAL:return "FATAL";break;default:return "UNKONW";break;}
}
std::string GetTime()
{time_t now = time(nullptr);struct tm *curr = localtime(&now);char buf[128];snprintf(buf, sizeof(buf), "%d-%02d-%02d %02d:%02d:%02d",curr->tm_year+1900,curr->tm_mon+1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);return buf;
}
class LockGuard
{
public:LockGuard(pthread_mutex_t* td):_td(td){pthread_mutex_lock(_td);}~LockGuard(){pthread_mutex_unlock(_td);}private:pthread_mutex_t *_td;
};
class Logmessage
{
public:std::string _level;pid_t _id;std::string _filename;int _filenumber;//行号std::string _curr_time;std::string _message_info;
};
class Log
{
public:Log(const std::string& filename=logfile):_logfile(filename){}void Enable(int type){_type = type;}void FlushToScreen(Logmessage& lg){printf("[%s][%d][%s][%d][%s] %s", lg._level.c_str(), lg._id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str());}void FlushToFile(Logmessage& lg){std::ofstream t(_logfile,std::ios::app);if(!t.is_open())return;char logtxt[1024];snprintf(logtxt,sizeof(logtxt),"[%s][%d][%s][%d][%s] %s", lg._level.c_str(), lg._id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str());t.write(logtxt, strlen(logtxt));t.close();}void FlushLog(Logmessage& lg){LockGuard ld(&_mutex);// 此处可以加过滤,本代码没加if(_type==SCREEN_TYPE){FlushToScreen(lg);}else if(_type==FILE_TYPE){FlushToFile(lg);}}void logmessage(int level,std::string filename,int filenumber,const char* format,...){Logmessage lg;lg._level = LevelToString(level);lg._id = getpid();lg._filename = filename;lg._filenumber = filenumber;lg._curr_time = GetTime();va_list ap;va_start(ap, format);char info[512];vsnprintf(info, sizeof(info), format, ap);va_end(ap);lg._message_info = info;FlushLog(lg);}~Log(){}
private:int _type=SCREEN_TYPE;std::string _logfile;
};
Log lg;
#define LOG(level,format,...) do{lg.logmessage(level, __FILE__, __LINE__, format, ##__VA_ARGS__);}while (0)
#define EnableScreen() do{ lg.Enable(SCREEN_TYPE);} while (0)
#define EnableFILE() do{ lg.Enable(FILE_TYPE);} while (0)
//InetAddr.hpp
#pragma once
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<iostream>
using namespace std;
class InetAddr
{
private:void ToHost(){_port = ntohs(_addr.sin_port);_ip = inet_ntoa(_addr.sin_addr);}
public:InetAddr(const struct sockaddr_in& addr):_addr(addr){ToHost();}string Ip() { return _ip; }uint16_t Port() { return _port; }private:string _ip;uint16_t _port;struct sockaddr_in _addr;
};
//TcpServer.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include"Log.hpp"
#include<cstring>
#include"InetAddr.hpp"
#include<sys/wait.h>
using namespace std;
const static uint16_t gport = 6666;
const static int gsock = -1;
const static int gblcklog = 8;
enum
{SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR
};class TcpServer
{
public:TcpServer(uint16_t port=gport):_port(port),_listensockfd(gsock){}class ThreadDate{public:ThreadDate(int sockfd,TcpServer* self,const InetAddr& addr):_sockfd(sockfd),_self(self),_addr(addr){}int _sockfd;TcpServer *_self;InetAddr _addr;};void InitServer(){_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);if(_listensockfd<0){LOG(FATAL, "socket create error\n");exit(SOCKET_ERROR);}LOG(INFO, "socket create success\n");struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensockfd, (sockaddr *)&local, sizeof(local));if(n<0){LOG(FATAL, "bind error\n");exit(BIND_ERROR);}LOG(INFO, "bind success\n");//tcp面向连接,要求不断获取连接,因此设为listen状态if(::listen(_listensockfd,gblcklog)<0){LOG(FATAL, "listen error\n");exit(LISTEN_ERROR);}LOG(INFO, "listen success\n");}void Loop(){_isrunning = true;while(_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = ::accept(_listensockfd, (struct sockaddr *)&client, &len);if(sockfd<0){LOG(WARNING, "accept error\n");continue;}InetAddr addr(client);LOG(INFO, "new connect %s\n",addr.Ip().c_str());// //多进行版本// pid_t id=fork();// if(id==0)// {// ::close(_listensockfd);// //让孙子变成孤儿,然后执行任务// if(fork()>0)// exit(0);// Service(sockfd,addr);// exit(0);// }// ::close(sockfd);// int n = waitpid(id, nullptr, 0);// if(n>0)// {// LOG(INFO, "wait child success\n");// }//多线程版本pthread_t tid;ThreadDate *td = new ThreadDate(sockfd, this,addr);pthread_create(&tid, nullptr, Excute, td);}}static void* Excute(void* args){pthread_detach(pthread_self());ThreadDate *td = static_cast<ThreadDate *>(args);td->_self->Service(td->_sockfd,td->_addr);delete td;return nullptr;}void Service(int sockfd,InetAddr addr){while(1){char buf[512];ssize_t n = ::read(sockfd, buf, sizeof(buf) - 1);if(n>0){buf[n] = 0;string msg = "[" + addr.Ip() + "]:" + buf;::write(sockfd, msg.c_str(), msg.size());}else if(n==0){LOG(INFO, "client %s quit\n", addr.Ip().c_str());break;}else{LOG(ERROR, "read %s error\n", addr.Ip().c_str());break;}}::close(sockfd);}~TcpServer(){}private:uint16_t _port;int _listensockfd;bool _isrunning=false;};
//TcpServerMain.cpp
#include"TcpServer.hpp"
#include<memory>
int main()
{std::unique_ptr<TcpServer> server = std::make_unique<TcpServer>();server->InitServer();server->Loop();return 0;
}
//TcpClientMain.cpp
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<cstring>
#include"InetAddr.hpp"
using namespace std;
int main(int argc, char *argv[])
{if (argc != 3){cerr << "usage:" << argv[0] << "error" << endl;exit(0);}string serverip = argv[1];uint16_t serverport = htons(stoi(argv[2]));//创建int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if(sockfd<0){cout<<"create socket error"<<endl;exit(1);}//不需要显示绑定,会自己随机生产端口//建立连接struct sockaddr_in server;server.sin_port = serverport;server.sin_family = AF_INET;::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);int n = ::connect(sockfd, (struct sockaddr *)&server, sizeof(server));if(n<0){cerr << "connect error" << endl;exit(1);}while(1){string message;cout << "Enter:";getline(std::cin, message);write(sockfd, message.c_str(), message.size());char buf[512];int n=read(sockfd, buf, sizeof(buf) - 1);if(n>0){buf[n] = 0;cout << "server:" << buf << endl;}else {break;}}::close(sockfd);return 0;
}
多线程远程命令执行
创建管道,创建子进程然后子进程去执行command,把结果写到管道。
在上面的基础上把服务器改一改.
#pragma once
#include "Log.hpp"
#include "InetAddr.hpp"
#include <string>
#include<cstdio>
class Command
{
public:Command() {}string Excute(const string& cmdstr){string result;FILE *fp = popen(cmdstr.c_str(), "r");if(fp){char line[512];while(fgets(line,sizeof(line),fp)){result += line;}return result.empty()?"success":result;}else{return "execute error";}}void HanderCommand(int sockfd, InetAddr addr){while (1){char buf[512];ssize_t n = ::recv(sockfd, buf, sizeof(buf) - 1,0);if (n > 0){buf[n] = 0;string msg = "[" + addr.Ip() + "]:" + buf;LOG(INFO, "%s\n", msg.c_str());string result=Excute(buf);::send(sockfd, result.c_str(), result.size(), 0);}else if (n == 0){LOG(INFO, "client %s quit\n", addr.Ip().c_str());break;}else{LOG(ERROR, "read %s error\n", addr.Ip().c_str());break;}}::close(sockfd);}~Command() {}
};
//TcpServer.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include"Log.hpp"
#include<cstring>
#include"InetAddr.hpp"
#include<sys/wait.h>
#include<functional>
using namespace std;
using command_service_t = function<void(int, InetAddr)>;
const static uint16_t gport = 6666;
const static int gsock = -1;
const static int gblcklog = 8;
enum
{SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR
};class TcpServer
{
public:TcpServer(command_service_t service,uint16_t port=gport):_port(port),_listensockfd(gsock),_service(service){}class ThreadDate{public:ThreadDate(int sockfd,TcpServer* self,const InetAddr& addr):_sockfd(sockfd),_self(self),_addr(addr){}int _sockfd;TcpServer *_self;InetAddr _addr;};void InitServer(){_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);if(_listensockfd<0){LOG(FATAL, "socket create error\n");exit(SOCKET_ERROR);}LOG(INFO, "socket create success\n");struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensockfd, (sockaddr *)&local, sizeof(local));if(n<0){LOG(FATAL, "bind error\n");exit(BIND_ERROR);}LOG(INFO, "bind success\n");//tcp面向连接,要求不断获取连接,因此设为listen状态if(::listen(_listensockfd,gblcklog)<0){LOG(FATAL, "listen error\n");exit(LISTEN_ERROR);}LOG(INFO, "listen success\n");}void Loop(){_isrunning = true;while(_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = ::accept(_listensockfd, (struct sockaddr *)&client, &len);if(sockfd<0){LOG(WARNING, "accept error\n");continue;}InetAddr addr(client);LOG(INFO, "new connect %s\n",addr.Ip().c_str());// //多进行版本// pid_t id=fork();// if(id==0)// {// ::close(_listensockfd);// //让孙子变成孤儿,然后执行任务// if(fork()>0)// exit(0);// Service(sockfd,addr);// exit(0);// }// ::close(sockfd);// int n = waitpid(id, nullptr, 0);// if(n>0)// {// LOG(INFO, "wait child success\n");// }//多线程版本pthread_t tid;ThreadDate *td = new ThreadDate(sockfd, this,addr);pthread_create(&tid, nullptr, Excute, td);}}static void* Excute(void* args){pthread_detach(pthread_self());ThreadDate *td = static_cast<ThreadDate *>(args);td->_self->_service(td->_sockfd,td->_addr);delete td;return nullptr;}~TcpServer(){}private:uint16_t _port;int _listensockfd;bool _isrunning=false;command_service_t _service;
};
//TcpServer.cpp
#include"TcpServer.hpp"
#include"Command.hpp"
#include<memory>
int main()
{Command cmdservice;command_service_t func = std::bind(&Command::HanderCommand,&cmdservice, std::placeholders::_1, std::placeholders::_2);std::unique_ptr<TcpServer> server = std::make_unique<TcpServer>(func);server->InitServer();server->Loop();return 0;
}