当前位置: 首页> 健康> 科研 > 日本java和python_网络服务商和网络运营商_保定seo博客_微信加精准客源软件

日本java和python_网络服务商和网络运营商_保定seo博客_微信加精准客源软件

时间:2025/7/10 8:29:47来源:https://blog.csdn.net/2301_76973016/article/details/144993347 浏览次数:0次
日本java和python_网络服务商和网络运营商_保定seo博客_微信加精准客源软件

目录

TCP 协议

 TCP 编程流程图

listen 函数(监听连接请求)

accept 函数(等待并接收连接)

connect 函数(建立连接)

shutdown 函数

gitee

主要代码

TcpServer.hpp

如何让服务器一次处理多个客户端的请求?

version 多进程:

version 多线程:

version 线程池:

完整代码: 

MainClient.cc

运行结果

version 多进程: 

version 多线程:

version 线程池: 


TCP 协议

TCP(传输控制协议,Transmission Control Protocol)是互联网协议套件中的核心协议之一,它提供了面向连接、可靠字节流服务。

因为 TCP 是面向连接的协议,在客户端和服务器通信前,需要先建立连接

TCP 编程流程图

listen 函数(监听连接请求)

#include <sys/socket.h>int listen(int sockfd, int backlog);

sockfd 是想要设置为监听模式的套接字描述符。这个描述符是由之前的 socket() 系统调用返回的。

backlog 参数指定了操作系统可以为该套接字排队的最大连接请求数。它是一个建议性的最大值,实际的最大长度可能会由操作系统限定。

  • listen() 成功执行时,它会返回 0
  • 如果发生错误,则返回 -1,并且会设置全局变量 errno 来指示具体的错误类型。

listen 被调用后,套接字就进入了监听状态,并开始排队连接请求。一旦有新的连接请求到达,它会被加入到队列中,直到服务器程序调用 accept 函数来处理这些连接请求。 

accept 函数(等待并接收连接)

#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:这是由之前的 socket() 系统调用创建,并通过 bind() 和 listen() 设置为监听模式的套接字描述符。

addr:这是一个指向 struct sockaddr 结构的指针,用于返回已连接客户端的地址信息。如果不需要客户端地址信息,可以将此参数设置为 NULL。是输出型参数。

addrlen:这是一个指向 socklen_t 类型变量的指针,该变量在调用时应包含 addr 指向结构的大小(以字节为单位)。调用后,它会被更新为实际存储在 addr 中的地址长度。如果 addr 是 NULL,那么这个参数也可以是 NULL。是输出型参数

返回值:

  • 如果成功accept 返回一个新的文件描述符,这个描述符代表与客户端之间的连接。服务器程序可以通过这个新描述符与客户端通信。
  • 如果有错误发生,accept 返回 -1,并设置全局变量 errno 以指示错误类型。

accept 函数会阻塞直到有一个新的连接建立。当有连接到达并且操作系统将其加入到队列中之后,accept 就会返回一个新连接的文件描述符,而原来的监听套接字 sockfd 仍然保持监听状态,可以继续接收更多的连接请求。

如何理解监听套接字 sockfd 和 accept 的返回值之间的关系呢?

一般的餐饮店门口会有一个揽客的,店内也会有服务员,揽客的揽到客人之后,由服务员为客人提供服务,揽客的继续在店门口揽客,并不会做服务员的工作,服务员也只做服务员的工作,不会到门口揽客,两个人各司其职。揽客的就是监听套接字 sockfd,服务员就是 accept 的返回值,sockfd 接收到连接后,就继续监听,accept 的返回值会为通信提供服务。

connect 函数(建立连接)

connect 函数是用于建立客户端与服务器之间的连接的系统调用或库函数。它主要用于 TCP(传输控制协议)套接字,但也可以用于其他类型的面向连接的协议。当一个程序需要主动发起一个网络连接到远程服务时,就会使用 connect 函数

#include <sys/types.h>
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd 是一个整数,表示之前通过 socket() 系统调用创建的未连接套接字描述符。

addr 是一个指向 sockaddr 结构的指针,该结构包含了要连接的服务器地址信息。通常你会使用 sockaddr_in 或 sockaddr_in6 来具体指定 IPv4 或 IPv6 地址及端口。

addrlen 是上述地址结构的大小,以字节为单位。

返回值:

  • 如果成功connect 返回 0
  • 如果发生错误,则返回 -1,并设置 errno 变量以指示错误类型。

shutdown 函数(关闭套接字描述符)

用完的套接字描述符必须关掉,因为描述符的数量是有限的,用完不关掉会导致描述符泄露

#include <sys/socket.h>int shutdown(int sockfd, int how);

 sockfd 是一个整数,表示要关闭的套接字描述符。

how 是一个整数,指定了如何关闭套接字,它可以取以下值之一:

  • SHUT_RD不再接收数据。对于双向套接字,这会阻止进一步的数据接收。
  • SHUT_WR不再发送数据。对于双向套接字,这会发送一个FIN包给对端,表明发送方已经完成发送,并且不会再发送更多数据。
  • SHUT_RDWR不再接收不再发送数据。这是组合了前两种情况的效果,等价于分别调用 SHUT_RD 和 SHUT_WR

返回值:

  • 如果成功shutdown 返回 0
  • 如果发生错误,则返回 -1,并设置 errno 变量以指示错误类型。

gitee

tcp_echo_server · zihuixie/Linux_Learning - 码云 - 开源中国icon-default.png?t=O83Ahttps://gitee.com/zihuixie/linux_-learning/tree/master/tcp_echo_server

主要代码

TcpServer.hpp

如何让服务器一次处理多个客户端的请求?

version 多进程:

为什么创建子进程?

创建子进程后,父进程继续接收连接,子进程则处理通信任务,提供服务,就可以实现一次处理多个请求。

为什么关掉描述符?

创建子进程时,父进程就把 accept 的返回值 sockfd 交给了子进程。

父进程如果不关掉 sockfd,会导致可用的 套接字描述符 越来越少,而且父进程不需要用到 sockfd,父进程只需要监听。子进程也不需要监听,所以子进程关掉 _listensock。

由于进程的独立性,子进程 关掉 _listensock 并不会影响 父进程继续监听,父进程关掉 sockfd 并不会影响子进程处理通信任务。

为什么创建孙子进程?

如果父进程等待子进程的执行,那么父进程需要等待 子进程 处理完当前的任务才可以继续接收连接,那么服务器还是一次只能处理一个请求,所以需要分离父子进程。

创建孙子进程,就可以解决服务器一次处理多个请求!

创建孙子进程后,子进程退出,那么孙子进程变成僵尸进程,由系统收养,父进程和子进程都不需要关心孙子进程的执行,父进程可以继续接收连接。

//创建子进程
pid_t id = fork();
if (id == 0) // 子进程
{close(_listensock);//子进程不需要监听//创建孙子进程if (fork() > 0)exit(0); // 子进程退出//孙子进程变僵尸进程,由系统收养Service(sockfd, InetAddr(peer)); // 孙子进程执行任务exit(0);
}// 父进程
close(sockfd);//父进程不需要提供服务
waitpid(id,nullptr,0);
version 多线程:

注意线程不需要关掉描述符,因为线程间共享描述符表

pthread_t t;
ThreadData *data =new ThreadData(sockfd,InetAddr(peer),this);pthread_create(&t,nullptr,HandlerSock,data);
version 线程池:

多线程版本中是客户端请求连接后再创建线程,线程池是先创建线程,客户端请求连接时,服务器可以直接使用线程处理请求。

task_t t=std::bind(&TcpServer::Service,this,sockfd,InetAddr(peer));
ThreadPool<task_t>::GetInstance()->Enqueue(t);

完整代码: 

#include <iostream>
#include <string>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>#include "InetAddr.hpp"
#include "Log.hpp"
#include "ThreadPool.hpp"const static int gbacklog = 16;
const static int defaultsockfd = -1;
using task_t = std::function<void()>;enum
{SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR};class TcpServer;class ThreadData
{
public:ThreadData(int fd,InetAddr addr,TcpServer *s):_sockfd(fd),_ClientAddr(addr),_self(s){}
public:int _sockfd;InetAddr _ClientAddr;TcpServer *_self;
};class TcpServer
{
public:TcpServer(uint16_t port): _port(port), _isrunning(false), _listensock(defaultsockfd){}~TcpServer(){if (_listensock > defaultsockfd){close(_listensock); // 关闭监听}}void InitServer(){_listensock = socket(AF_INET, SOCK_STREAM, 0); // TCP协议用SOCK_STREAM// 创建失败if (_listensock < 0){LOG(FATAL, "socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG, "socket create success, sockfd: %d\n", _listensock);// 填地址struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;local.sin_port = htons(_port);// 绑定int n = bind(_listensock, (struct sockaddr *)&local, sizeof(local));// 绑定失败if (n < 0){LOG(FATAL, "%d bind error\n", _port);exit(BIND_ERROR);}LOG(DEBUG, "bind success,sockfd: %d\n", _listensock);// 开始监听连接n = listen(_listensock, gbacklog);// 监听失败if (n < 0){LOG(FATAL, "listen error\n");exit(LISTEN_ERROR);}LOG(DEBUG, "listen success,sockfd: %d\n", _listensock);}void Service(int sockfd, InetAddr client){LOG(DEBUG, "get a new link, info %s:%d, fd: %d \n", client.Ip().c_str(), client.Port(), sockfd);// 发送方端口号std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.Port()) + "]#";// 缓冲区char inbuffer[1024];while (true){// 读取字节流//  n:读到的字节数ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);if (n > 0){inbuffer[n] = 0;std::cout << clientaddr << inbuffer << std::endl;// 应答std::string echo_string = "[server echo]#";echo_string += inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0) // 读到文件结尾{// 尝试从当前位置继续读取数据但没有更多的数据可以读取// 那么就说程序已经到达了文件的结尾,client 退出LOG(INFO, "%s quit\n", clientaddr.c_str());break;}else // 读取失败{LOG(ERROR, "read error\n");break;}}// 服务器开始退出std::cout << "server start to quit..." << std::endl;// sockfd 才是提供服务的套接字描述符,_listensock 用于监听// 需要关掉提供提供服务的 sockfdshutdown(sockfd, SHUT_RD);std::cout << "shut_rd" << std::endl;}static void* HandlerSock(void *args){pthread_detach(pthread_self());//分离新、主线程ThreadData *td=static_cast<ThreadData*>(args);td->_self->Service(td->_sockfd,td->_ClientAddr);delete td;return nullptr;}// 循环接收连接void Loop(){_isrunning = true;while (_isrunning){// 输出型参数struct sockaddr_in peer;socklen_t len = sizeof(peer);// 等待并接收连接int sockfd = accept(_listensock, (struct sockaddr *)&peer, &len);// 接收失败if (sockfd < 0){LOG(WARNING, "accept error\n");// 本次接收失败之后还可以继续接收continue;}// 开始执行任务// version 0: 一次只能处理一个请求// Service(sockfd,InetAddr(peer));// version 1:用多进程// // 创建子进程// pid_t id = fork();// if (id == 0) // 子进程// {//     close(_listensock);//子进程不需要监听//     // 创建孙子进程//     if (fork() > 0)//         exit(0); // 子进程退出//     //孙子进程变僵尸进程,由系统收养//     Service(sockfd, InetAddr(peer)); // 孙子进程执行任务//     exit(0);// }// // 父进程// close(sockfd);//父进程不需要提供服务// waitpid(id,nullptr,0);// // version 2:用多线程// pthread_t t;// ThreadData *data =new ThreadData(sockfd,InetAddr(peer),this);// pthread_create(&t,nullptr,HandlerSock,data);// version 3:用线程池task_t t=std::bind(&TcpServer::Service,this,sockfd,InetAddr(peer));ThreadPool<task_t>::GetInstance()->Enqueue(t);}_isrunning = false;}private:int _listensock; // 用于监听uint16_t _port;bool _isrunning; // 是否正在运行
};

MainClient.cc

#include <iostream>
#include <sys/types.h>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << "serverip serverport" << std::endl;
}
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);return 1;}uint16_t serverport = std::stoi(argv[2]);std::string serverip = argv[1];int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;exit(2);}struct sockaddr_in server;memset(&server, 0, sizeof(struct sockaddr_in));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverip.c_str());server.sin_port = htons(serverport);// 客户端不需要调用bind 函数// 客户端与服务器建立连接int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));// 连接建立失败if (n < 0){std::cerr<<"connect error"<<std::endl;exit(3);}while(true){//1、输入消息std::cout<<"\nPlease Enter# ";std::string message;std::getline(std::cin,message);//2、发送消息ssize_t s=send(sockfd,message.c_str(),message.size(),0);if(s>0){//3、接收应答char inbuffer[1024];ssize_t r=recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);//接收成功if(r>0){inbuffer[r]=0;std::cout<<inbuffer<<std::endl;}//接收失败else{break;}}else{break;}}shutdown(sockfd,SHUT_WR);return 0;
}

运行结果

不同的端口号 可以看出服务器可以一次处理多个请求!

version 多进程: 

 

 

 

version 多线程:

 

version 线程池: 

 

 

关键字:日本java和python_网络服务商和网络运营商_保定seo博客_微信加精准客源软件

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: