Linux之网络套接字

Linux之网络套接字

  • 一.IP地址和端口号
  • 二.TCP和UDP协议
    • 2.1网络字节序
  • 三.socket编程的常见API
  • 四.模拟实现UDP服务器和客户端
  • 五.模拟实现TCP服务器和客户端

一.IP地址和端口号

在了解了网络相关的基础知识之后我们知道了数据在计算机中传输的流程并且发现IP地址在其中占据了确定目标主机的功能,但是在计算机中我们网络通信的行为其实本质都是进程间进行通信。那么在一个主机中有那么多个进程我们要如何准确定位到某个进程呢?
那就要利用端口号了,所谓的端口,就好像是门牌号一样,客户端可以通过ip地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号。
所以我们使用IP地址+端口号的方法就可以找到互联网中唯一的一个进程,其中IP地址用来标记互联网中唯一的一台主机,端口号用来标记主机中唯一的一个进程。但是对于端口号有些地方是和IP地址相同的,一个主机既可以只有一个IP地址也可以有多个IP地址所以一个进程既可以有一个端口号也可以有多个端口号。同时多个主机不可以有一个IP地址多个进程也不可以有一个端口号。
在了解到其中的奥秘之后人们为了更好的称呼这两个概念的组合就又创造了一个概念叫套接字(socket),socket的英译其实是插座的意思从它的英译中我们也可以大概理解它的作用。插座是为了连接两端的物品所以将其具体到网络中socket的作用就是充当应用层和传输层之间的一个抽象层,它将TCP/IP传输层中的一些复杂的操作简单化接口化进而提供给应用层进行调用从而实现进程间的网络通信。

如果端口号port是用来确定计算机中的唯一的一个进程的话,那么可能有些人就有疑问了我们在操作系统的学习的时候使用过进程的pid当时说pid的作用也是确定唯一的一个进程。那么这两者之间有什么联系或者是有什么区别吗?
答案是没有什么区别,它两都可以用来确定主机中唯一的一个进程但是pid是在进行进程管理的时候使用的而端口号是在进行网络通信的时候使用的,这么做的原因其实也很简单就是将进程管理和网络通信进行解耦,pid就专职于进程管理port就专职于网络通信两个人谁都不打扰谁。

二.TCP和UDP协议

之前我们一直在谈协议的分层协议的意义等等但是我们一直没有见过真正的协议如今我们就先学习一下传输层中的两个协议:TCP协议和UDP协议
TCP协议

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

UDP协议

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

我们先大致了解一下这两个协议的基本特质其中具体的内容我们等之后再谈,在这里我要强调一个概念:可不可靠传输是协议的特性,这两个都是一个中性词没有好坏之分。就像函数的可复用性一样,可靠传输不代表就比不可靠传输好。可靠传输说明传输的过程更复杂更安全,不可靠传输说明传输的过程比较简单适合一些对数据可靠性不高的场景。

2.1网络字节序

在我们最开始学习C语言的时候我们在介绍数据的存储方式时提到过不同的机器存储方式不同其中分为大端机和小端机。大端模式也就是将低位数据存储到高地址中高位数据存储到低地址中,小端模式是将低位数据存储到低地址里高位数据存储到高地址里。大端模式和小端模式没有明显的优劣之差,如果非要说哪个优一点大端机器的可读性比小端机器好一点。

对于内存的中的数据存储方式有大小端之分那么对于网络传输的数据流是否也要有大小端之分呢?我们又该选择哪个呢?
虽然内存地址有大小端之分但是我们要知道计算机对于数据的读取和写入是从低地址到高地址的这也就是导致为什么大端模式比小端模式可读性更高的原因。
在这里插入图片描述
所以在数据从一个主机发送到另外一个主机读取的过程中发送和读取都是从低地址到高地址的所以网络数据流的数据的存储方法也就是数据的地址是按照大端模式的也就是低地址高字节。
要注意这是一个规定不是你先用哪个方式发送就是哪个方式的,所有的机器在进行TCP/IP通信的时候发送数据的时候都需要将其转换为大端模式。而这个转换为大端模式后的数据存储方式叫做网络字节序。
而为了方便用户将各种整型变量网络字节序化系统提供了几个库函数
在这里插入图片描述

三.socket编程的常见API

  1. 创建socket文件描述符(TCP/UDP, 客户端 + 服务器)
    在这里插入图片描述

对于这三个参数和返回值大家应该有不少的疑惑:为什么返回值是一个文件描述符?socket类型是什么?SOCK_STREAM和SOCK_DGRAM又分别是什么意思?我一个一个的给大家解释一下。
对于返回值是文件描述符的问题我们需要知道网络传输的原理是什么,简单来说两个主机互相传输数据其实就是在服务器和客户端中分别创建一个文档然后服务器会向自己新创建的文档中写入和读取数据,而写入的数据又会通过网络拷贝到客户端新创建的文档中而客户端亦是如此。所以在创建套接字的时候会返回一个文件描述符。
在进行网络编程的时候socket是有很多类型的有些是由于传输的环境不同有些是由于协议的不同,例如unix_socket:域间socket,在同一台主机中进行通信,网络socket:使用ip+port的方式运用于网络通信,原始socket:编写一些网络工具。
SOCK_STREAM和SOCK_DGRAM分别是流格式套接字和数据报格式套接字,这两个概念在我们介绍TCP和UDP协议的提到过这也就是为什么这两个socket类型是和TCP和UDP协议组合在一起的原因。

  1. 绑定端口号 (TCP/UDP, 服务器)
    在这里插入图片描述
    对于第二个参数我需要给大家讲解一下,sockaddr是一个结构体代表的是一个通用的地址类型它其中存储了地址族和套接字中的目标地址信息和端口号。
    在这里插入图片描述
    但是这个结构体是有漏洞的它将目标的地址信息和端口号混在了一起。所以还设计了另外两个结构体sockaddr_in。
    在这里插入图片描述
    而sockaddr和sockaddr_in的就像我们在C++中学习的多态的基类和子类一样,我们在socket编程中使用的一般都是sockaddr_in但是由于某些函数的参数是sockaddr所以我们可以将sockaddr_in强转为sockaddr。

  2. 开始监听socket (TCP, 服务器)
    在这里插入图片描述
    listen函数的作用是将套接字文件描述符从主动转为被动文件描述符,然后用于被动监听客户端的连接,具体的会在讲解协议的时候再谈。如今我们只需要知道怎么用它即可。

  3. 接收请求 (TCP, 服务器)
    在这里插入图片描述
    为什么还要创建一个新文件呢不是已经创建了一个文件了吗为什么不用那个文件描述符呢?
    这个问题其实在使用listen的时候就已经有答案了,大家可以仔细看看listen函数的作用:套接字文件描述符从主动转为被动文件描述符,然后用于被动监听客户端的连接。socket返回的文件描述符已经是被动模式那么肯定需要我们再创建一个新的文件来对客户端进行服务。就好像我们在路上遇到的拉客的人,在我们被他拉进去吃饭之后服务我们的是另外的服务员并不是那个拉我们进来的人。使用套接字文件描述符就是那个拉客的人而这个新的文件描述符才是服务我们的人。

  4. 建立连接 (TCP, 客户端)
    在这里插入图片描述

在大概了解了模拟实现UDP和TCP服务器和客户端时会出现的新函数后我们就可以来分别模拟实现UDP服务器和客户端以及TCP服务器和客户端了。
这两个协议的不同所以模拟实现的过程也不同并且它两服务器和客户端的运行逻辑也不同,到了模拟实现的时候我会具体来说。

四.模拟实现UDP服务器和客户端

我们先来看一下UDP服务器和客户端的运行逻辑
在这里插入图片描述

#Makefile
.PHONY:all
all:udp_server udp_clientudp_server:Main.ccg++ -o $@ $^ -std=c++14
udp_client:udp_client.ccg++ -o $@ $^ -std=c++14.PHONY:clean
clean:rm -f udp_server udp_client
//log.hpp
#pragma once
#include <iostream>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>enum
{Debug = 0,Info,Warning,Error,Fatal
};// 分为打印到屏幕上,打印到一个文件中,打印到不同的等级的文件中
enum
{Screen = 10,Onefile,Classfile
};int defaultstyle = Screen;
std::string default_filename = "log.";
std::string logdir = "log";class Log
{
public:Log(int style = defaultstyle, std::string file = default_filename): _style(style), _file(file){// mkdir不止是指令,还是一个函数可以用来调用然后创建目录mkdir(logdir.c_str(), 0775);}// 更改模式void Enable(int sty){std::cout << "Enable success" << std::endl;_style = sty;}std::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";}return "null";}// 返回本地时间std::string TimeChangeLocal(){time_t currtime = time(nullptr);// 我们利用time返回的时间是以时间戳的形式返回的不太好看// 所以我们可以利用localtime将其转化为一个类struct tm *local = localtime(&currtime);char time_buffer[128];// 利用snprintf以年-月-日 小时-分钟-秒的形式输入到tim_buffer中// 注意:tm类的年是以1900年开始的的snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d-%d-%d",local->tm_year + 1900, local->tm_mon, local->tm_mday,local->tm_hour, local->tm_min, local->tm_sec);return time_buffer;}// 打印到具体的某个文件中void WriteLogToOneFile(const std::string &logname, const std::string &message){umask(0);int fd = open(logname.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);write(fd, message.c_str(), message.size());close(fd);}// 打印到不同等级的文件中void WriteLogToClassFile(const std::string &leverstr, const std::string &message){std::string logname = logdir;logname += "/";logname += _file;logname += leverstr;WriteLogToOneFile(logname, message);}void Writelog(std::string &str, std::string &message){switch (_style){case Screen:std::cout << message;break;case Onefile:WriteLogToClassFile("all", message);break;case Classfile:WriteLogToClassFile(str, message);break;default:break;}}// 我们想要日志像printf一样打印所以要使用可变参数// 但是我们可以利用va_list参数的变量将可变参数存储在变量中void LogMessage(int level, const char *format, ...){// 左边信息为:[消息等级][时间][进程编号]char leftmessage[1024];std::string leverstr = LevelToString(level);std::string currtime = TimeChangeLocal();std::string idstr = std::to_string(getpid());// 右边信息为:想输出的内容char rightmessage[1024];va_list args;va_start(args, format);                                      // 将args指向可变参数部分vsnprintf(rightmessage, sizeof(rightmessage), format, args); // 将可变参数写入到rightmessage中va_end(args);                                                // args == nullptrsnprintf(leftmessage, sizeof(leftmessage), "[%s][%s][%s]", leverstr.c_str(), currtime.c_str(), idstr.c_str());std::string loginfo = leftmessage;loginfo += rightmessage;Writelog(leverstr, loginfo);}~Log(){}private:int _style;std::string _file;
};Log lg;
//nocopy.hpp
#pragma onceclass NoCopy
{
public:NoCopy(){}~NoCopy(){}NoCopy(const NoCopy &) = delete;const NoCopy &operator=(const NoCopy &) = delete;
};
//Main.cc
#include "udp_server.hpp"
#include <iostream>
#include <memory>void Usage(std::string process)
{std::cout << "Usage:\n" << process << "local_ip local_port\n" << std::endl;
}
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);return -1;}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);std::unique_ptr<UdpServer> up_udp = std::make_unique<UdpServer>(ip,port);up_udp->Init();up_udp->Start();return 0;
}
//udp_client.hpp
#include <iostream>
#include <memory>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>const static int defaultsize = 1024;void Usage(std::string process)
{std::cout << "Usage:\n"<< process << "server_ip server_port\n"<< std::endl;
}
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);return -1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);// 1.创建socketint socketfd = socket(AF_INET, SOCK_DGRAM, 0);if (socketfd < 0){std::cout << "client socket error" << errno << " " << strerror(errno) << std::endl;return -1;}std::cout << "socket create success socket:" << socketfd << std::endl;// 2.绑定网络信息// client需要绑定网络信息但是不需要显式绑定,在client发送第一条消息时系统会随机给他绑定一个端口号// 为什么client需要绑定网络信息是因为client会有很多如果我们显式绑定很容易重复// 收集server的信息等到发送时使用struct sockaddr_in servermessage;memset(&servermessage, 0, sizeof(servermessage));servermessage.sin_family = AF_INET;servermessage.sin_port = htons(server_port);servermessage.sin_addr.s_addr = inet_addr(server_ip.c_str());// 3.发送消息// 服务器是个死循环,客户端同理while (1){std::string inbuffer;std::cout << "Please Enter:";std::getline(std::cin, inbuffer);// 发送消息ssize_t n = sendto(socketfd, inbuffer.c_str(), inbuffer.size(),0,(struct sockaddr *)&servermessage, sizeof(servermessage));if (n > 0){// 接收消息char outbuffer[defaultsize];struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t m = recvfrom(socketfd, &outbuffer, sizeof(outbuffer) - 1,0,(struct sockaddr*)&temp, &len);if (m > 0){outbuffer[m] = 0;std::cout << "server echo:" << outbuffer << std::endl;}else{break;}}else{break;}}close(socketfd);return 0;
}
//udp_server.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "log.hpp"
#include "nocopy.hpp"const static uint16_t defaultport = 8888;
const static int defaultsocketfd = -1;
const static int defaultsize = 1024;class UdpServer : public NoCopy
{
public:UdpServer(std::string ip, uint16_t port = defaultport, int socketfd = defaultsocketfd): _ip(ip), _port(port), _socketfd(socketfd){}~UdpServer(){}void Init(){// 1.创建socket,创建文件信息_socketfd = socket(AF_INET, SOCK_DGRAM, 0);if (_socketfd < 0){lg.LogMessage(Fatal, "socket error, %d : %s\n", errno, strerror(errno));exit(-1);}lg.LogMessage(Info, "socket create success, socketfd:%d\n", _socketfd);// 2.绑定网络信息// 保存本地的信息struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;// 在保存本地信息时要将ip和端口号port转换为网络序列// ip同时要改为四字节的local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());// 本地信息的结构体填完了后要将其加载到内核中int n = bind(_socketfd, (struct sockaddr *)&local, sizeof(local));if (n != 0){lg.LogMessage(Fatal, "bind error, %d : %s\n", errno, strerror(errno));exit(-1);}}void Start(){char buffer[defaultsize];// 服务器是一个死循环while (1){// 保存client客户端的信息struct sockaddr_in peer;socklen_t len = sizeof(peer);// 接收消息ssize_t n = recvfrom(_socketfd, &buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0){buffer[n] = 0;std::cout << "client say :" << buffer << std::endl;// 发送消息sendto(_socketfd, &buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, len);}}}private:std::string _ip;uint16_t _port;int _socketfd;
};

这些代码只能完成最简单的UDP的服务器和客户端仅仅只能发消息,大家还可以利用我们之前学习的程序替换来完成发送并且执行指令的功能以及利用多线程来完成一个聊天室的功能。
这里的代码我就不贴出来了大家需要的话可以私聊我。

五.模拟实现TCP服务器和客户端

在这里插入图片描述

在TCP编程中我们就不需要使用sendto和recvform这两个函数了我们可以直接用write和read来完成文件的读写操作。
下面我也是直接贴出代码,这次的代码就是最终版了比较的复杂,所以我就把主要的关于客户端和服务端的代码贴出来了。

#Makefile
.PHONY:all
all:tcp_server tcp_clienttcp_server:Main.cc g++ -o $@ $^ -std=c++14 -lpthread
tcp_client:tcp_client.ccg++ -o $@ $^ -std=c++14 .PHONY:clean
clean:rm -f tcp_server tcp_client
//tcp_client.cc
#include <iostream>
#include <memory>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>#include "log.hpp"#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)
#define Retry_Count 5const static int defaultsize = 1024;void Usage(std::string process)
{std::cout << "Usage:\n"<< process << "server_ip server_port\n"<< std::endl;
}bool VisitServer(std::string server_ip, uint16_t server_port, int *count)
{// 1.创建socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);ssize_t m = 0;ssize_t r = 0;std::string inbuffer;char service_list[1024];if (sockfd < 0){std::cout << "client socket error" << errno << " " << strerror(errno) << std::endl;return -1;}// std::cout << "socket create success socket:" << sockfd << std::endl;// 2.绑定网络信息// client需要绑定网络信息但是不需要显式绑定,在client发送第一条消息时系统会随机给他绑定一个端口号// 为什么client需要绑定网络信息是因为client会有很多如果我们显式绑定很容易重复// 收集server的信息等到发送时使用struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr); // 从字符串ip到4字符ip 从系统序列变为网络序列bool result = true;// 3.连接客户端int n = connect(sockfd, CONV(&server), sizeof(server));if (n < 0){result = false;goto END;}*count = 0;std::cout << "connect success"<< std::endl;// 3.发送消息和接收信息r = read(sockfd, &service_list, sizeof(service_list) - 1);if (r > 0){service_list[r] = 0;std::cout << "service list is " << service_list << std::endl;}std::cout << "Please select service: ";getline(std::cin, inbuffer);write(sockfd, inbuffer.c_str(), inbuffer.size());std::cout << "Enter: ";getline(std::cin, inbuffer);if (inbuffer == "quit"){return true;}m = write(sockfd, inbuffer.c_str(), inbuffer.size());if (m > 0){char outbuffer[defaultsize];r = read(sockfd, &outbuffer, sizeof(outbuffer) - 1);if (r > 0){outbuffer[r] = 0;std::cout << outbuffer << std::endl;}else if (r == 0){return result;}else{lg.LogMessage(Fatal, "client read error, %d : %s\n", errno, strerror(errno));result = false;goto END;}}else{lg.LogMessage(Fatal, "client write error, %d : %s\n", errno, strerror(errno));result = false;goto END;}
END:close(sockfd);return result;
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);return -1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);int cnt = 1;while (cnt <= Retry_Count){bool result = VisitServer(server_ip, server_port, &cnt);if (result){break;}else{sleep(1);std::cout << "client is retring connect, count: " << cnt << std::endl;cnt++;}}if (cnt > Retry_Count){std::cout << "server is unonline..." << std::endl;}return 0;
}
//Main.cc
#include <iostream>
#include <memory>
#include <algorithm>#include "tcp_server.hpp"
#include "Translate.hpp"
#include "Daemon.hpp"void Usage(std::string process)
{std::cout << "Usage:\n"<< process << " local_port\n"<< std::endl;
}void Interact(int fd, InetAddr addr, std::string inbuffer)
{char outbuffer[1024];// 先读再写while (1){ssize_t n = read(fd, &outbuffer, sizeof(outbuffer) - 1);if (n > 0){outbuffer[n] = 0;std::cout << "[" << addr.PrintDebug() << "]# " << outbuffer << std::endl;write(fd, inbuffer.c_str(), inbuffer.size());}else if (n < 0){lg.LogMessage(Fatal, "server read error, %d : %s\n", errno, strerror(errno));break;}else if (n == 0){lg.LogMessage(Debug, "client quit...\n");break;}}
}void Ping(int fd, InetAddr addr)
{lg.LogMessage(Debug, "%s select %s success, fd:%d\n", addr.PrintDebug().c_str(), "Ping", fd);Interact(fd, addr, "pong");
}translate ts;
void Translate(int fd, InetAddr addr)
{lg.LogMessage(Debug, "%s select %s success, fd:%d\n", addr.PrintDebug().c_str(), "Translate", fd);char english[128];ssize_t m = read(fd, &english, sizeof(english) - 1);if (m > 0){english[m] = 0;}std::string chinese = ts.Execute(english);write(fd, chinese.c_str(), chinese.size());lg.LogMessage(Debug, "translate success, %s->%s\n", english, chinese.c_str());
}void Transform(int fd, InetAddr addr)
{lg.LogMessage(Debug, "%s select %s success, fd:%d\n", addr.PrintDebug().c_str(), "Transform", fd);char message[128];int n = read(fd, message, sizeof(message) - 1);if (n > 0)message[n] = 0;std::string messagebuf = message;std::transform(messagebuf.begin(), messagebuf.end(), messagebuf.begin(), [](unsigned char c){ return std::toupper(c); });write(fd, messagebuf.c_str(), messagebuf.size());lg.LogMessage(Debug, "transform success, %s->%s\n", message, messagebuf.c_str());
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);return -1;}uint16_t port = std::stoi(argv[1]);// 将进程转换为守护进程化Daemon(false, false);lg.Enable(Classfile);lg.LogMessage(Debug, "Daemon success...\n");std::unique_ptr<TcpServer> tp_tcp = std::make_unique<TcpServer>(port);tp_tcp->EnrollFunc("ping", Ping);tp_tcp->EnrollFunc("translate", Translate);tp_tcp->EnrollFunc("transform", Transform);tp_tcp->Init();tp_tcp->Start();return 0;
}
//tcp_server.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <functional>
#include <unordered_map>#include "log.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "threadpool.hpp"#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)
#define ERROR "error"const static int default_backlog = 1;using task_t = std::function<void()>;
using callback_t = std::function<void(int, InetAddr &)>;class TcpServer;// class ThreadDate
// {
// public:
//     ThreadDate(int fd, TcpServer *server_ptr, struct sockaddr_in addr)
//         : _fd(fd), _server_ptr(server_ptr), _addr(addr)
//     {
//     }
//     ~ThreadDate()
//     {
//         close(_fd);
//     }
//     int GetFd()
//     {
//         return _fd;
//     }
//     TcpServer *GetServer()
//     {
//         return _server_ptr;
//     }
//     InetAddr GetAddr()
//     {
//         return _addr;
//     }//     int _fd;
//     TcpServer *_server_ptr;
//     InetAddr _addr;
// };class TcpServer : public NoCopy
{
public:TcpServer(uint16_t port): _port(port), _isrunning(false){}~TcpServer(){}void Init(){// 1.创建socket套接字_listenfd = socket(AF_INET, SOCK_STREAM, 0);if (_listenfd < 0){lg.LogMessage(Fatal, "socket error, %d : %s\n", errno, strerror(errno));exit(-1);}// 在使用tcp时会出现有多个套接字链接到一个端口上的情况,所以我们需要运行端口复用int opt = 1;setsockopt(_listenfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));lg.LogMessage(Info, "socket create success, socketfd:%d\n", _listenfd);// 2.填充本地网络信息并且bindstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = htonl(INADDR_ANY);local.sin_port = htons(_port);int n = bind(_listenfd, CONV(&local), sizeof(local));if (n != 0){lg.LogMessage(Fatal, "bind error, %d : %s\n", errno, strerror(errno));exit(-1);}lg.LogMessage(Info, "bind success, listenfd:%d\n", _listenfd);// 3.将socket设置为监听状态// 因为tcp协议是依靠链接来进行交互的所以我们需要将客户端的套接字// 从主动设置为被动从而监听用户端的连接行为int m = listen(_listenfd, default_backlog);if (m != 0){lg.LogMessage(Fatal, "listen error, %d : %s\n", errno, strerror(errno));exit(-1);}lg.LogMessage(Info, "listen success, listenfd:%d\n", _listenfd);threadpool<task_t>::Getinstance()->ThreadStart();_funcs.insert(std::make_pair("defaulthandle", std::bind(&TcpServer::DefaultHandle, this, std::placeholders::_1, std::placeholders::_2)));}void Service(int sockfd, InetAddr addr){char outbuffer[1024];// 先读再写while (1){ssize_t n = read(sockfd, &outbuffer, sizeof(outbuffer) - 1);if (n > 0){outbuffer[n] = 0;std::cout << "[" << addr.PrintDebug() << "]# " << outbuffer << std::endl;std::string inbuffer = "pong";write(sockfd, inbuffer.c_str(), inbuffer.size());}else if (n < 0){lg.LogMessage(Fatal, "server read error, %d : %s\n", errno, strerror(errno));break;}else if (n == 0){lg.LogMessage(Debug, "client quit...\n");break;}}}// 因为这个函数是成员函数所以会带有一个this指针从而导致参数不对// 所以我们需要将其转换为静态成员函数// static void *HandleFunction(void *args)// {//     // 线程分离//     pthread_detach(pthread_self());//     ThreadDate *td = static_cast<ThreadDate *>(args);//     td->GetServer()->Service(td->GetFd(),td->GetAddr());//     delete td;//     return nullptr;// }void Routinue(int fd, InetAddr addr){_funcs["defaulthandle"](fd, addr);std::string type = Read(fd);lg.LogMessage(Debug, "%s select %s\n", addr.PrintDebug().c_str(), type.c_str());if (type == "ping"){_funcs[type](fd, addr);}else if (type == "translate"){_funcs[type](fd, addr);}else if (type == "transform"){_funcs[type](fd, addr);}else{}close(fd);}void EnrollFunc(std::string name, callback_t func){_funcs[name] = func;}std::string Read(int fd){char type[128];ssize_t n = read(fd, &type, sizeof(type) - 1);if (n > 0){type[n] = 0;lg.LogMessage(Debug,"server read success, message:%s\n",type);}else if (n < 0){lg.LogMessage(Fatal, "server read error, %d : %s\n", errno, strerror(errno));return ERROR;}else if (n == 0){lg.LogMessage(Debug, "client quit...\n");return ERROR;}return type;}void DefaultHandle(int fd, InetAddr addr){std::string lists = "|";for (auto &ch : _funcs){lists += ch.first;lists += "|";}write(fd, lists.c_str(), lists.size());}void Start(){// 服务器是一个死循环_isrunning = true;signal(SIGCHLD, SIG_IGN);while (_isrunning){sleep(1);// 4.获取连接// 当客户端进入监听状态后我们还需要让客户端使用accept函数来获取连接也就是获取一个全新的文件描述符struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listenfd, CONV(&peer), &len);if (sockfd < 0){lg.LogMessage(Fatal, "accept error, %d : %s\n", errno, strerror(errno));continue;}lg.LogMessage(Debug, "accept success, get a new sockfd:%d\n", sockfd);// 5.提供服务// 版本1// 问题:只能有一个客户端连接服务器// Service(sockfd);// close(sockfd);// 版本2// 利用多进程会继承文件描述符表来解决版本1的问题//     pid_t id = fork();//     if (id == 0)//     {//         // 子进程//         // 子进程需要关闭不需要的文件描述符即_listenfd//         close(_listenfd);//         pid_t iid = fork();//         if (iid > 0)//         {//             //子进程//             exit(0);//         }//         else//         {//             //孙子进程//             //在子进程退出后孙子进程就变成了孤儿进程//             //然后就被系统领养//             Service(sockfd);//             close(sockfd);//             exit(0);//         }//     }//     else if (id > 0)//     {//         // 父进程//         // 子进程需要关闭不需要的文件描述符即sockfd//         close(sockfd);//         pid_t rid = waitpid(id, nullptr, 0);//         if (rid == id)//         {//             lg.LogMessage(Debug, "wait success...\n");//         }//     }//     else//     {//         lg.LogMessage(Fatal, "fork error, %d : %s\n", errno, strerror(errno));//         close(sockfd);//         continue;//     }// 版本3// 利用多进程中子进程退出时会发出信号后修改信号的处理方法// 从而让父进程不用等待子进程,系统会自动回收子进程的资源// pid_t id = fork();// if (id == 0)// {//     // 子进程//     // 子进程需要关闭不需要的文件描述符即_listenfd//     close(_listenfd);//     Service(sockfd);//     close(sockfd);//     exit(0);// }// else if (id > 0)// {//     // 父进程//     // 子进程需要关闭不需要的文件描述符即sockfd//     close(sockfd);// }// else// {//     lg.LogMessage(Fatal, "fork error, %d : %s\n", errno, strerror(errno));//     close(sockfd);//     continue;// }// 版本4// 利用多线程会共享一个文件描述符表的特性来让新线程去执行服务// 并且将新线程进行分离从而达到自动回收资源的目的// ThreadDate *td = new ThreadDate(sockfd, this, peer);// pthread_t tid;// pthread_create(&tid, nullptr, HandleFunction, td);// 版本5// 利用线程池提前创建线程并向其分配任务// 问题:不能给客户端提供长服务task_t ts = std::bind(&TcpServer::Routinue, this, sockfd, InetAddr(peer));threadpool<task_t>::Getinstance()->Push(ts);}}private:uint16_t _port;// int _fd;int _listenfd;bool _isrunning;std::unordered_map<std::string, callback_t> _funcs;
};

如果大家需要全部的代码可以去我的gitee中自行拷贝或者私信我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/68632.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mysql常见问题处理集锦

Mysql常见问题处理集锦 root用户密码忘记&#xff0c;重置的操作(windows上的操作)MySQL报错&#xff1a;ERROR 1118 (42000): Row size too large. 或者 Row size too large (&#xff1e; 8126).场景&#xff1a;报错原因解决办法 详解行大小限制示例&#xff1a;内容来源于网…

分类问题(二元,多元逻辑回归,费歇尔判别分析)spss实操

分类模型&#xff1a; 二分类和多分类&#xff1a; 对于二分类模型 &#xff0c;我们将介绍逻辑回归和Fisher线性判别分析两种分类算法; 对于多分类模型&#xff0c;我们将简单介绍Spss中的多分类线性判别分析和多分类逻辑回归的操作步骤 二分类: 基于广义线性模型&#x…

NPC与AI深度融合结合雷鸟X3Pro AR智能眼镜:引领游戏行业沉浸式与增强现实新纪元的畅想

if… NPC&#xff08;非玩家角色&#xff09;与AI&#xff08;人工智能&#xff09;的深度融合&#xff0c;正引领游戏行业迈向一个全新的沉浸式与增强现实&#xff08;AR&#xff09;相结合的新时代。这一创新不仅预示着游戏体验的质变&#xff0c;更可能全面革新游戏设计与叙…

Dom的学习

DOM&#xff08;文档对象模型&#xff0c;Document Object Model&#xff09;是一个编程接口&#xff0c;用于HTML和XML文档。它将文档表示为一个树形结构&#xff0c;其中每个节点都是文档的一部分&#xff0c;例如元素、属性和文本内容。通过DOM&#xff0c;开发者可以使用编…

游戏行业销售数据分析可视化

完整源码项目包获取→点击文章末尾名片&#xff01; &#x1f31f;分析&#xff1a; 可看出最近五年用户最喜爱的游戏类型依然还是Action-动作类&#xff08;当然市场发行的也很多&#xff09; Sports-运动类和Shooter-射击类顺序互换,但我估计现在大环境局势紧张可以会推动射击…

Golang Gin系列-4:Gin Framework入门教程

在本章中&#xff0c;我们将深入研究Gin&#xff0c;一个强大的Go语言web框架。我们将揭示制作一个简单的Gin应用程序的过程&#xff0c;揭示处理路由和请求的复杂性。此外&#xff0c;我们将探索基本中间件的实现&#xff0c;揭示精确定义路由和路由参数的技术。此外&#xff…

靠右行驶数学建模分析(2014MCM美赛A题)

笔记 题目 要求分析&#xff1a; 比较规则的性能&#xff0c;分为light和heavy两种情况&#xff0c;性能指的是 a.流量与安全 b. 速度限制等分析左侧驾驶分析智能系统 论文 参考论文 两类规则分析 靠右行驶&#xff08;第一条&#xff09;2. 无限制&#xff08;去掉了第一条…

PyTorch使用教程(11)-cuda的使用方法

1. 基本概念 CUDA&#xff08;Compute Unified Device Architecture&#xff09;是NVIDIA开发的一种并行计算平台和编程模型&#xff0c;专为图形处理器&#xff08;GPU&#xff09;设计&#xff0c;旨在加速科学计算、工程计算和机器学习等领域的高性能计算任务。CUDA允许开发…

金融项目实战 07|Python实现接口自动化——连接数据库和数据清洗、测试报告、持续集成

目录 一、投资模块&#xff08;投资接口投资业务&#xff09; 二、连接数据库封装 和 清洗数据 1、连接数据库 2、数据清洗 4、调用 三、批量执行测试用例 并 生成测试报告 四、持续集成 1、代码上传gitee 2、Jenkin持续集成 一、投资模块&#xff08;投资接口投资业务…

Ubuntu22.04安装paddle GPU版本

文章目录 确立版本安装CUDA与CUDNN安装paddle 确立版本 查看官网信息&#xff0c;确立服务版本&#xff1a;https://www.paddlepaddle.org.cn/documentation/docs/zh/2.6/install/pip/linux-pip.html 安装CUDA与CUDNN 通过nvidia-smi查看当前显卡驱动版本&#xff1a; 通过…

网络编程-UDP套接字

文章目录 UDP/TCP协议简介两种协议的联系与区别Socket是什么 UDP的SocketAPIDatagramSocketDatagramPacket 使用UDP模拟通信服务器端客户端测试 完整测试代码 UDP/TCP协议简介 两种协议的联系与区别 TCP和UDP其实是传输层的两个协议的内容, 差别非常大, 对于我们的Java来说, …

Unity补充 -- 协程相关

1.协程。 协程并不是线程。线程是主线程之外的另一条 代码按照逻辑执行通道。协程则是在代码在按照逻辑执行的同时&#xff0c;是否需要执行额外的语句块。 2.协程的作用。 在update执行的时候&#xff0c;是按照帧来进行刷新的&#xff0c;也是按照帧执行代码的。但是又不想…

IoTDB 常见问题 QA 第四期

关于 IoTDB 的 Q & A IoTDB Q&A 第四期来啦&#xff01;我们将定期汇总我们将定期汇总社区讨论频繁的问题&#xff0c;并展开进行详细回答&#xff0c;通过积累常见问题“小百科”&#xff0c;方便大家使用 IoTDB。 Q1&#xff1a;Java 中如何使用 SSL 连接 IoTDB 问题…

Json转换类型报错问题:java.lang.Integer cannot be cast to java.math.BigDecimal

Json转换类型报错问题&#xff1a;java.lang.Integer cannot be cast to java.math.BigDecimal 小坑规避指南 小坑规避指南 项目中遇到json格式转换成Map&#xff0c;已经定义了Map的key和value的类型&#xff0c;但是在遍历Map取值的时候出现了类型转换的报错问题&#xff08…

数据结构——队列和栈(介绍、类型、Java手搓实现循环队列)

我是一个计算机专业研0的学生卡蒙Camel&#x1f42b;&#x1f42b;&#x1f42b;&#xff08;刚保研&#xff09; 记录每天学习过程&#xff08;主要学习Java、python、人工智能&#xff09;&#xff0c;总结知识点&#xff08;内容来自&#xff1a;自我总结网上借鉴&#xff0…

python http server运行Angular 单页面路由时重定向,解决404问题

问题 当Angular在本地ng server运行时候&#xff0c;可以顺利访问各级路由。 但是运行ng build后&#xff0c;在dist 路径下的打包好的额index.html 必须要在服务器下运行才能加载。 在服务器下我们第一次访问路由页面时是没有问题的&#xff0c;但是尝试刷新页面或手动输入路…

SQL表间关联查询详解

简介 本文主要讲解SQL语句中常用的表间关联查询方式&#xff0c;包括&#xff1a;左连接&#xff08;left join&#xff09;、右连接&#xff08;right join&#xff09;、全连接&#xff08;full join&#xff09;、内连接&#xff08;inner join&#xff09;、交叉连接&…

Android Jni(一) 快速使用

文章目录 Android Jni&#xff08;一&#xff09; 快速使用1、 环境配置下载 NDK2、右键 add c to module3、创建一个 native 方法&#xff0c;并更具提示&#xff0c;自动创建对应的 JNI 实现4、实现对应 Jni 方法5、static loadLibrary6、调用执行 遇到的问题1、[CXX1300] CM…

【HarmonyOS之旅】基于ArkTS开发(二) -> UI开发之常见布局

目录 1 -> 自适应布局 1.1 -> 线性布局 1.1.1 -> 线性布局的排列 1.1.2 -> 自适应拉伸 1.1.3 -> 自适应缩放 1.1.4 -> 定位能力 1.1.5 -> 自适应延伸 1.2 -> 层叠布局 1.2.1 -> 对齐方式 1.2.2 -> Z序控制 1.3 -> 弹性布局 1.3.1…

物联网网关Web服务器--Boa服务器移植与测试

1、Boa服务器介绍 BOA 服务器是一个小巧高效的web服务器&#xff0c;是一个运行于unix或linux下的&#xff0c;支持CGI的、适合于嵌入式系统的单任务的http服务器&#xff0c;源代码开放、性能高。 Boa 嵌入式 web 服务器的官方网站是http://www.boa.org/。 特点 轻量级&#x…