网站建设微信版网站根目录文件
- 作者: 五速梦信息网
- 时间: 2026年03月21日 07:44
当前位置: 首页 > news >正文
网站建设微信版,网站根目录文件,seo2短视频发布,怀柔 做网站的文章目录前言一、TCP Socket API1. socket2. bind3. listen4. accept5. connect二、封装TCPSocket三、服务端的实现1. 封装TCP通用服务器2. 封装任务对象3. 实现转换功能的服务器四、客户端的实现1. 封装TCP通用客户端2. 实现转换功能的客户端五、结果演示六、多进程版服务器七… 文章目录前言一、TCP Socket API1. socket2. bind3. listen4. accept5. connect二、封装TCPSocket三、服务端的实现1. 封装TCP通用服务器2. 封装任务对象3. 实现转换功能的服务器四、客户端的实现1. 封装TCP通用客户端2. 实现转换功能的客户端五、结果演示六、多进程版服务器七、线程池版服务器前言 TCP和UDP都是工作在传输层用于程序之间传输数据。二者之间的区别是TCP是面向连接的而UDP是面向数据报的。那就意味着TCP能够进行可靠的数据传输而UDP进行不可靠的数据传输。关于TCP协议和UDP协议的详细内容可见博主的后续文章本文的主要内容是关于TCP socket的网络编程。 接下来我们将基于TCP网络编程实现一个将小写字母转换成大写字母的网络服务器。 一、TCP Socket API 以下是关于使用TCP协议用到的socket API这些函数都包含在头文件sys/socket.h中。
- socket
函数定义
NAME//socket - create an endpoint for communication
SYNOPSIS#include sys/socket.hint socket(int domain, int type, int protocol);
功能 socket()会打开一个网络通信端口如果打开成功则像open()函数一样返回一个文件描述符如果失败则返回 -1。这样网络应用程序就可以像读写文件那样使用read/write在网络上读取和发送数据。
参数详解 第一个参数domain用于设置网络通信的域函数socket()根据这个参数选择通信协议的族。对于IPv4domain参数指定为AF_INET而IPv6则是AF_INET6。并且AF_INET和PFINET的值是一致的。 第二个参数type用于设置通信协议的族这些族也在文件sys/socket.h中定义包含如下表所示的值。
类型说明SOCK_STREAM用于TCP连接提供序列化、可靠的、双向连接的字节流SOCK_DGRAM用于UDP连接无连接状态的消息SOCK_SEQPACKET序列化包提供一个序列化的、可靠的、双向的基于连接的数据传输通道数据长度定长。每次调用读系统调用时数据需要将全部数据读出SOCK_RAWRAW类型提供原始网络协议访问SOCK_RDM提供可靠的数据报文不过可能数据会有乱序SOCK_PACKET这是一个专用类型不能在通用程序中使用用于直接从设备驱动接收数据 【补充说明】 类型为SOCK_STREAM的套接字表示一个双向的字节流与管道类似。流式的套接字在进行数据收发之前必须已经连接连接使用connet()函数进行。一旦连接可以使用read/write函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收当数据在一段时间内仍然没有接收完毕可以将这个连接认为已经断开。SOCK_DGRAM和SOCK_RAW这两种套接字可以使用函数sendto()来发送数据使用recvfrom()函数接收数据recvfrom()接收来自指定IP地址的发送方的数据。 第三个参数protocol用于指定某个协议的特定类型即type类型中的某个类型。通常某个协议中只有一种特定类型这样protocol参数仅能设置为0但是有些协议有多种类型就需要设置这个参数来选择特定的类型。 - bind 函数定义 NAME//bind - bind a name to a socketSYNOPSIS#include sys/socket.hint bind(int socket, const struct sockaddr *address, socklen_t address_len); 因为服务器程序所监听的网络地址和端口号通常都是固定不变的客户端得知了服务器程序的地址和端口号后就可以向服务器发起连接而服务器需要绑定一个固定的网络地址和端口号。因此bind()的作用是将参数sockfd和myaddr绑定在一起使sockfd这个用于网络通讯的文件描述符监听sockaddr所描述的地址和端口号。绑定成功返回0失败则返回-1。 博主的上一篇文章【网络套接字编程】中提到过struct sockaddr *是一个通用指针类型myaddr参数实际上可以接受多种协议的sockaddr结构体而它们的长度各不相同所以需要第三个参数addrlen指定结构体的长度。 在程序中myaddr的定义及初始化如下 struct sockaddr_in myaddr; bzero(myaddr, sizeof(myaddr)); myaddr.sin_family AF_INET; myaddr.sin_addr.s_addr htonl(INADDR_ANY); myaddr.sin_port htons(SERV_PORT);定义myaddr使用bzero函数将整个结构体清零设置网络通信的域为AF_INET将网络地址设置为INADDR_ANY这个宏表示本地的任意IP地址因为服务器可能有多个网卡每个网卡也可能绑定多个IP 地址这样设置可以在所有的IP地址上监听直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址最后填充端口号 虽然bind()中的第二个参数类型是sockaddr但是我们真正填充信息使用的数据结构是sockaddr_in这个结构里主要有三部分信息地址类型、端口号、IP地址。最后在进行函数传参的时候只需要将sockaddr_in*强制类型转换成sockaddr即可。
- listen 函数定义 NAME//listen - listen for socket connections and limit the queue of incoming connectionsSYNOPSIS#include sys/socket.hint listen(int socket, int backlog); listen()函数用于声明sockfd处于监听状态并且最多允许有backlog个客户端处于连接等待状态如果接收到更多的连接请求就忽略。详细内容可见博主的后续文章【TCP协议】。listen()函数调用成功返回0调用失败则返回 -1。
- accept 函数定义 NAMEaccept - accept a new connection on a socketSYNOPSIS#include sys/socket.hint accept(int socket, struct sockaddr *restrict_address, socklen_t *restrict_address_len); accept()函数的作用是当客户端与服务端的三次握手完成后服务器调用accept()函数接受连接。如果服务器调用accept()时还没有客户端的连接请求就阻塞等待直到有客户端请求连接。 返回值 调用成功则返回客户端socket()返回的文件描述符调用失败则返回 -1。 参数 第一个参数socket即是调用socket()函数返回的文件描述符。第二个参数restrict_address是输出型参数用于获取客户端的网络地址和端口号如果该参数为空则表示当前服务端不关心客户端的地址。第三个参数restrict_address_len也是输出型参数它表示的是缓冲区restrict_address的长度以避免缓冲区溢出问题最后传出客户端地址结构体的实际长度。 accept()函数在服务器程序中的使用结构如下 while (true) {sockaddr_in peer_addr;socklen_t len sizeof(peer_addr);int peer_sock accept(_fd, (sockaddr *)peer_addr, len);ssize_t read_size read(peer_sock, buf, sizeof(buf));…close(peer_sock); }5. connect 函数定义 NAME//connect - connect a socketSYNOPSIS#include sys/socket.hint connect(int socket, const struct sockaddr *address, socklen_t address_len); 作用与参数说明 connect函数用于客户端连接服务器。其参数与bind()函数的参数一致区别在于bind()函数绑定的参数是自己的地址而connect()函数的连接是服务器的地址。 返回值 调用成功返回0调用失败则返回 -1。 二、封装TCPSocket #pragma once#include iostream #include string #include cstring #include cassert#include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h#define CHECK_RET(exp) \if (!(exp)) { \return false; }class TcpSocket { public:TcpSocket() : _fd(-1) {}~TcpSocket() {}public:bool Socket(){_fd socket(AF_INET, SOCK_STREAM, 0); // AF_INET表示采用IPv4, SOCK_STREAM表示采用tcp协议if (_fd 0){std::cerr create socket error! std::endl;return false;}return true;}bool Close(){close(_fd);return true;}bool Bind(const std::string ip, uint16_t port){sockaddr_in addr;// 填充addr信息addr.sin_family AF_INET;addr.sin_port htons(port);// addr.sin_addr.s_addr ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str());ip.empty() ? (addr.sin_addr.s_addr INADDR_ANY) : inet_aton(ip.c_str(), addr.sin_addr);if (bind(_fd, (const sockaddr *)addr, sizeof(addr)) 0){std::cerr bind error! std::endl;return false;}return true;}bool Listen(int num){if (listen(_fd, num) 0){std::cerr listen error! std::endl;return false;}return true;}bool Accept(TcpSocket *peer, std::string *ip nullptr, std::uint16_t *port nullptr){sockaddr_in peer_addr;socklen_t len sizeof(peer_addr);int peer_sock accept(_fd, (sockaddr *)peer_addr, len);if (peer_sock 0){std::cerr accept error! std::endl;return false;}peer-_fd peer_sock;if (ip ! nullptr){*ip inet_ntoa(peer_addr.sin_addr);}if (port ! nullptr){*port ntohs(peer_addr.sin_port);}return true;}bool Recv(std::string *buf){buf-clear();char inbuf[1024 * 10] {0};ssize_t read_size recv(_fd, inbuf, sizeof(inbuf), 0);if (read_size 0){std::cerr recv error! std::endl;return false;}if (read_size 0){return false;}buf-assign(inbuf, read_size);return true;}bool Send(const std::string buf){ssize_t write_size send(_fd, buf.c_str(), buf.size(), 0);if (write_size 0){std::cerr send error! std::endl;return false;}return true;}bool Connect(const std::string ip, uint16_t port){sockaddr_in addr;// 填充addr信息addr.sin_family AF_INET;addr.sin_port htons(port);addr.sin_addr.s_addr ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str());if (connect(_fd, (sockaddr *)addr, sizeof(addr)) 0){std::cerr connect error! std::endl;return false;}return true;}int GetFd(){return _fd;}private:int _fd; };三、服务端的实现
- 封装TCP通用服务器 #include TcpSocket.hpp #include Task.hpptypedef std::functionvoid(TcpSocket, const std::string , uint16_t) Handler;// void transServer(TcpSocket sock, const std::string ip, uint16_t port) // { // std::string inbuf;// while (true) // { // if (!sock.Recv(inbuf)) // { // // 如果读取失败结束循环 // printf([client %s:%d] disconnect!\n, ip.c_str(), port); // break; // }// if (strcasecmp(inbuf.c_str(), quit) 0) // { // printf([client %s:%d] quit!\n, ip.c_str(), port); // break; // }// printf(transform before: %s[%d]– %s\n, ip.c_str(), port, inbuf.c_str()); // fflush(stdout);// for (int i 0; i inbuf.size(); i) // { // if (isalpha(inbuf[i]) islower(inbuf[i])) // { // inbuf[i] toupper(inbuf[i]); // } // }// printf(transform after: %s[%d]– %s\n, ip.c_str(), port, inbuf.c_str()); // fflush(stdout);// sock.Send(inbuf); // } // sock.Close(); // }class TcpServer { public:TcpServer(int port, const std::string ip ): _port(port), _ip(ip){_sock.Socket();}~TcpServer() {}public:bool Start(Handler handler){// 绑定IP和端口号CHECK_RET(_sock.Bind(_ip, _port));// 监听CHECK_RET(_sock.Listen(5));// 进入循环while (true){// 进行acceptTcpSocket peer_sock;std::string ip;uint16_t port 0;if (!_sock.Accept(peer_sock, ip, port)){continue;}printf([client %s:%d] connect!\n, ip.c_str(), port);// 执行任务// TODOTask task(peer_sock, ip, port, handler);task();}}private:// tcp socket对象TcpSocket _sock;// 端口号uint16_t _port;// ip地址std::string _ip; };2. 封装任务对象 #include iostream #include string #include functional #include pthread.h#include TcpSocket.hppclass Task {typedef std::functionvoid(TcpSocket, const std::string , uint16_t) callback_t;public:Task(TcpSocket sock, const std::string ip, uint16_t port, callback_t func):_sock(sock), _ip(ip), _port(port), _func(func) {}~Task() {}void operator()(){printf(线程ID[%p]处理client[%s:%d]的请求开始了…\n, pthread_self(), _ip.c_str(), _port);fflush(stdout);_func(_sock, _ip, _port);printf(线程ID[%p]处理client[%s:%d]的请求结束了…\n, pthread_self(), _ip.c_str(), _port);fflush(stdout);} private:TcpSocket _sock;std::string _ip;uint16_t _port;callback_t _func; // 处理任务的回调函数 };3. 实现转换功能的服务器 #include iostream #include TcpSocket.hpp #include TcpServer.hppvoid transServer(TcpSocket sock, const std::string ip, uint16_t port) {std::string inbuf;while (true){if (!sock.Recv(inbuf)){// 如果读取失败结束循环printf([client %s:%d] disconnect!\n, ip.c_str(), port);break;}if (strcasecmp(inbuf.c_str(), quit) 0){printf([client %s:%d] quit!\n, ip.c_str(), port);break;}printf(transform before: %s[%d]– %s\n, ip.c_str(), port, inbuf.c_str());fflush(stdout);for (int i 0; i inbuf.size(); i){if (isalpha(inbuf[i]) islower(inbuf[i])){inbuf[i] toupper(inbuf[i]);}}printf(transform after: %s[%d]– %s\n, ip.c_str(), port, inbuf.c_str());fflush(stdout);sock.Send(inbuf);}sock.Close(); }static void Usage(std::string proc) {std::cout Usage:\n\t proc ip port std::endl;std::cout example:\n\t proc 127.0.0.1 8080\n std::endl; }int main(int argc, char *argv[]) {if (argc ! 2 argc ! 3){Usage(argv[0]);return 1;}std::string ip;if (argc 3){ip argv[1];}uint16_t port atoi(argv[2]);TcpServer server(port, ip);server.Start(transServer);return 0; }四、客户端的实现
- 封装TCP通用客户端
#include TcpSocket.hppclass TcpClient
{
public:TcpClient(const std::string ip, uint16_t port):_ip(ip), _port(port) {_sock.Socket();}~TcpClient(){_sock.Close();}
public:bool Connect(){return _sock.Connect(_ip, _port);}bool Recv(std::string* buf){return _sock.Recv(buf);}bool Send(const std::string buf){_sock.Send(buf);}int GetFd(){return _sock.GetFd();}
private:TcpSocket _sock;std::string _ip;uint16_t _port;
};2. 实现转换功能的客户端
#include iostream
#include TcpClient.hppvolatile bool quit false;static void Usage(std::string proc)
{std::cerr Usage:\n\t proc serverIp serverPort std::endl;std::cerr Example:\n\t proc 127.0.0.1 8080\n std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{if (argc ! 3){Usage(argv[0]);return 1;}std::string serverIp argv[1];uint16_t serverPort atoi(argv[2]);TcpClient client(serverIp, serverPort);// 建立连接if (!client.Connect()){std::cout connecte errer! std::endl;return 2;}std::cout connecte success! fd: client.GetFd() std::endl;std::string message;while (!quit){message.clear();std::cout 请输入您的内容# ;std::getline(std::cin, message);if (strcasecmp(message.c_str(), quit) 0){quit true;}if (client.Send(message)){message.resize(message.size());if (client.Recv(message)){std::cout Server Echo — message std::endl;}}else{break;}}return 0;
}五、结果演示
启动服务端
启动客户端 客户端连接服务端成功此时服务端状态
测试样例 客户端输入样例发现转换成功。 存在的问题 此时启动又一个客户端连接服务器在此输入时我们会发现输入的内容会卡住显示屏上。 此时我们关闭第一个客户端后发现有得出转换后的结果。 原因在于当前的服务器是单进程版本的只能够同时为一个客户端服务所以再来一个客户端会阻塞等待服务器结束对上一个客户端的服务。以下的改进的多进程和多线程版本的服务器。 六、多进程版服务器 改进思想是父进程为每个客户端的请求都创建一个子进程去处理任务父进程不做任何工作但要注意的是父进程中要关闭不断创建的客户端的peer_sock避免内存泄漏。子进程执行客户端的请求在请求结束后调用exit函数退出但不必单独释放_sock对象因为会自动调用其析构函数。同时设置signal函数对SIGCHLD做忽略动作使得父进程不必等待子进程只处理自己的任务。 #pragma once#include TcpSocket.hpp #include Task.hpp #include cassert #include signal.h #include unistd.htypedef std::functionvoid(TcpSocket, const std::string , uint16_t) Handler;class TcpProcessServer { public:TcpProcessServer(int port, const std::string ip ): _port(port), _ip(ip){_sock.Socket();signal(SIGCHLD, SIG_IGN);}~TcpProcessServer() {}public:bool Start(Handler handler){// 绑定IP和端口号CHECK_RET(_sock.Bind(_ip, _port));// 监听CHECK_RET(_sock.Listen(5));// 进入循环while (true){// 进行acceptTcpSocket peer_sock;std::string ip;uint16_t port 0;if (!_sock.Accept(peer_sock, ip, port)){continue;}printf([client %s:%d] connect!\n, ip.c_str(), port);// 执行任务// 多进程 v1.0pid_t id fork();if (id 0){// 父进程不需要做什么peer_sock.Close(); // 父进程中需要关闭}else if (id 0){// 子进程// 子进程的 socket 的关闭在析构函数中进行Task task(peer_sock, ip, port, handler);task();// 处理任务结束退出子进程exit(0);}else{// fork失败std::cerr fork error! std::endl;return false;}}return true;}private:// tcp socket对象TcpSocket _sock;// 端口号uint16_t _port;// ip地址std::string _ip; };结果 此时服务器便可以为多个客户端同时进行服务。 七、线程池版服务器 实现线程池 #pragma once#include iostream #include queue #include cassert #include pthread.hconst uint32_t gDefaultThreadNum 5; // 默认线程池中线程数量template class T class ThreadPool { public:ThreadPool(uint32_t threadNum gDefaultThreadNum): _isStart(false), _ThreadNum(threadNum){pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_cond, nullptr);}~ThreadPool(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);}public:// 启动线程池void start(){// 判断线程池是否启动assert(!_isStart); // 如果已经启动则失败for (int i 0; i _ThreadNum; i){pthread_t tid;pthread_create(tid, nullptr, threadRoutine, this);}// 线程池已经启动_isStart true;}// 放入任务void push(const T in){lockQueue();_taskQueue.push(in);handleTask();unlockQueue();}// 消费任务T pop(){T task _taskQueue.front();_taskQueue.pop();return task;}private:static void *threadRoutine(void *args){ThreadPoolT *ptp static_castThreadPoolT *(args);while (true){ptp-lockQueue();// 判断当前任务队列中有没有任务while (!ptp-hasTask()){// 没有任务进行循环等待ptp-waitTask();}// 当前线程获取任务T t ptp-pop();ptp-unlockQueue();// 当前线程处理任务t();}}void lockQueue(){pthread_mutex_lock(_mutex);}void unlockQueue(){pthread_mutex_unlock(_mutex);}void waitTask(){pthread_cond_wait(_cond, _mutex);}void handleTask(){pthread_cond_signal(_cond);}bool hasTask(){return !_taskQueue.empty();}private:bool _isStart; // 判断线程池是否开启uint32_t _ThreadNum; // 线程池中的线程数量std::queueT _taskQueue; // 任务队列pthread_mutex_t _mutex; // 保护任务队列的锁pthread_cond_t _cond; // 线程池的条件变量 }; 线程池版服务器 #pragma once#include TcpSocket.hpp #include Task.hpp #include ThreadPool.hpptypedef std::functionvoid(TcpSocket, const std::string , uint16_t) Handler;class TcpThreadPoolServer { public:TcpThreadPoolServer(int port, const std::string ip ): _port(port), _ip(ip){_sock.Socket();_tp.start();}~TcpThreadPoolServer() {}public:bool Start(Handler handler){// 绑定IP和端口号CHECK_RET(_sock.Bind(_ip, _port));// 监听CHECK_RET(_sock.Listen(5));// 进入循环while (true){// 进行acceptTcpSocket peer_sock;std::string ip;uint16_t port 0;if (!_sock.Accept(peer_sock, ip, port)){continue;}printf([client %s:%d] connect!\n, ip.c_str(), port);// 执行任务// TODOTask task(peer_sock, ip, port, handler);_tp.push(task);}}private:// tcp socket对象TcpSocket _sock;// 端口号uint16_t _port;// ip地址std::string _ip;// 线程池ThreadPoolTask _tp; }; 结果
- 上一篇: 网站建设网站自助建设传奇手游在线玩网页游戏
- 下一篇: 网站建设违约怎么投诉wordpress输出分类
相关文章
-
网站建设网站自助建设传奇手游在线玩网页游戏
网站建设网站自助建设传奇手游在线玩网页游戏
- 技术栈
- 2026年03月21日
-
网站建设网站排名优化建设集团招工信息网站
网站建设网站排名优化建设集团招工信息网站
- 技术栈
- 2026年03月21日
-
网站建设网站建设哪里有wordpress写简历
网站建设网站建设哪里有wordpress写简历
- 技术栈
- 2026年03月21日
-
网站建设违约怎么投诉wordpress输出分类
网站建设违约怎么投诉wordpress输出分类
- 技术栈
- 2026年03月21日
-
网站建设维护的相关基础知识广州品牌网站设计公司
网站建设维护的相关基础知识广州品牌网站设计公司
- 技术栈
- 2026年03月21日
-
网站建设维护方向平台建设包括哪些方面
网站建设维护方向平台建设包括哪些方面
- 技术栈
- 2026年03月21日
