IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> socket(2) -> 正文阅读

[系统运维]socket(2)

服务器需要绑定端口号,但是客户端不需要绑定,这是为什么?

  • 客户端不需要绑定端口号和ip,但是客户端也有自己的端口号和ip。
  • 一台电脑有很多个客户端,如果你想要客户端强行绑定端口号,那么就需要所有的公司进行协商,每个客户端使用不同的端口号。但这是不可能的。如果你强行让客户端绑定端口号,那么就极有可能引起冲突,使得某些客户端启动失败。
  • 但是服务器不一样,因为服务器一般只有一个,而且服务器一般是一个公司内部的东西,可以协商。而且服务器的端口号和ip地址必须是确定的,众所周知的,因为一台服务器连接着很多客户端,否则就可能找不到服务器。
  • 客户端也需要唯一性,但是不要求确定性。我们可以让操作系统来帮助我们分配端口号。因为端口号资源也有上限(16位),操作系统需要管理端口号。所以哪些端口号没有被使用,只有操作系统知道。
  • 客户端也有ip地址和端口号,在recv和send的时候,操作系统会帮助我们自动绑定。

INADDR_ANY

  • 绑定ip填0,代表本地ip地址。

  • 实际上,服务器的绑定时不需要传入ip地址的。我们将网络地址设置为INADDR_ANY,这个宏表示本地任意的ip地址。因为服务器可能有多张网卡,每张网卡可能连接多个ip地址。这样设置可以在所以的ip地址上监听,直到与某个客户端建立连接时才确定用哪个ip地址。

  • 因为ip地址时标识唯一主机的,那么我们通过任一关联该主机的ip地址,应该都可以与该主机通信。但是如果绑定确定的ip,那么就会导致只有通过该ip地址才能与主机通信。

  • 这个宏起到一个判定的作用,如果检测到你绑定的ip == INADDR_ANY,那么操作系统收到的所有ip报文都交给服务器。如果绑定具体ip,那么只有从这个ip上来的报文才交给你。

  • 一个服务器可以创建很多udp,tcp的套接字。socket也是文件,也需要被管理。

在C、C++中打开一个文件(udp除外),也被称为打开一个流。

telnet ip port #链接到一个服务器

tcp版本的套接字

/*************** tcpServer.hpp **************/
  1 #include <iostream>                                                                                  
  2 #include <cstdlib>
  3 #include <sys/types.h>
  4 #include <string>
  5 #include <sys/socket.h>
  6 #include <unistd.h>
  7 #include <arpa/inet.h>
  8 
  9 #define BACKLOG 10
 10 
 11 class TcpServer{
 12 private: 
 13     int port;
 14     int sockfd;
 15 public:
 16     TcpServer(int _port)
 17       : port(_port)
 18       , sockfd(-1)
 19   {}
 20 
 21     void InitServer(){
 22       sockfd = socket(AF_INET, SOCK_STREAM, 0);
 23       //bind
 24       struct sockaddr_in local;
 25       local.sin_family = AF_INET;
 26       local.sin_port = htons(port);
 27       local.sin_addr.s_addr = INADDR_ANY;                                                            
 28       if(bind(sockfd, (struct sockaddr*)&local, sizeof(local)) < 0){
 29         std::cerr << "bind error" << std::endl;
 30         exit(1);
 31       }
 32       //listen
 33       if(listen(sockfd, BACKLOG) < 0){
 34         std::cerr << "listen error" << std::endl;
 35         exit(2);
 36       }
 37     }
 38 
 39     void Start(){
 40        struct sockaddr_in end_point;
 41        socklen_t len = sizeof(end_point);
 42       for(;;){
 43       // listen successfully, accept
 44        int acc_sock = accept(sockfd, (struct sockaddr*)&end_point, &len);
 45        if(acc_sock < 0){
 46          std::cerr << "accept error" << std::endl;                                                   
 47          continue;
 48        }
 49        // 连接建立完成, 开始通信
 50         std::cout << "get a new link... " << std::endl;
 51         Service(acc_sock);
 52       }
 53    }
 54     ~TcpServer(){
 55       close(sockfd);
 56     }
 57 
 58 private:
 59     void Service(int sockfd){
 60       for(;;){
 61       //ssize_t recv(int sockfd, void *buf, size_t len, int flags);
 62        char buf[64] = {0};
 63       ssize_t s = recv(sockfd, buf, sizeof(buf)-1, 0);
 64       if(s > 0){
 65         buf[s-1] = 0;
 66         std::cout << buf << std::endl;
 67 
 68         //int send(int s, const void *msg, size_t len, int flags);
 69         std::string str = buf;
 70            // str += "[server] ";
 71         send(sockfd, str.c_str(), str.size(), 0);
 72       }
 73     }
 74   }
 75 }; 

/***************tcpClient.hpp *************/
  1 #pragma once                                                                                         
  2 #include <iostream>                             
  3 #include <cstdlib>
  4 #include <sys/types.h>
  5 #include <string>
  6 #include <cstring>
  7 #include <sys/socket.h>
  8 #include <unistd.h>    
  9 #include <netinet/in.h>
 10 #include <arpa/inet.h>                       
 11                 
 12 class TcpClient{
 13 private:                                  
 14   std::string ip;                                       
 15   int port;        
 16   int sockfd;        
 17                                                        
 18 public:  
 19   TcpClient(std::string _ip, int _port)
 20     :ip(_ip)
 21     ,port(_port)
 22   {}           
 23                   
 24   void ClientInit(){
 25     sockfd = socket(AF_INET, SOCK_STREAM, 0);
 26     // 创建连接 
 27     // int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
 28     struct sockaddr_in server_sock;
 29     server_sock.sin_family = AF_INET;
 30     server_sock.sin_port = htons(port);
 31     server_sock.sin_addr.s_addr = inet_addr(ip.c_str()); //注意字符串
 32     if(connect(sockfd, (struct sockaddr*)&server_sock, sizeof(server_sock)) < 0){
 33       std::cerr << "connect error" << std::endl;
 34       exit(1);
 35     }
 36   }
 37 
 38   void Start(){
 39     char buf[64] = {0};
 40     while(true){
 41       size_t s = read(0, buf, sizeof(buf)-1);
 42       if(s > 0){                                                                                     
 43         buf[s-1] = 0;
 44         send(sockfd, buf, strlen(buf), 0);
 45         size_t ss = recv(sockfd, buf, sizeof(buf)-1, 0);
 46         if(ss > 0){
 47           buf[s] = 0;
 48           std::cout << "server]$ " << buf << std::endl; 
 49         }
 50       }
 51     }
 52   }
 53   ~TcpClient(){
 54     close(sockfd);
 55   }
 56 };              
/********** tcpServer.cc ************/
  1 #include "tcpServer.hpp"
  2 
  3 void Usage(char* proc){
  4   std::cout <<"Usage :"<< std::endl;
  5   std::cout <<"    " << proc << " : port " << std::endl;
  6 }
  7 int main(int argc, char* argv[]){
  8   if(argc != 2){
  9     Usage(argv[0]);
 10     exit(5);
 11   }
 12 
 13   TcpServer* ts = new TcpServer(std::atoi(argv[1]));                                                 
 14   ts->InitServer();
 15   ts->Start();
 16 
 17   delete ts;
 18 }

/*********** tcpClient.cc *************/
  1 #include "tcpClient.hpp"
  2 
  3 void Usage(char* proc){
  4   std::cout << "Usage :" << std::endl;
  5   std::cout << "      " << proc << " ip  port" << std::endl;
  6 }
  7 int main(int argc, char* argv[]){
  8   if(argc != 3){
  9     Usage(argv[0]);
 10     exit(2);
 11   }
 12 
 13   TcpClient* tc = new TcpClient(argv[1], std::atoi(argv[2]));
 14   tc->ClientInit();
 15   tc->Start();
 16 
 17   delete tc;                                                                                         
 18 }

  • 这就是第一个版本的单进程的tcp套接字。这种写法有很多问题。

问题1

我们发现tcp客户端断开之后,重新连接不上。

  • 因为我们的服务器不知道客户端已经退出,那么服务器的服务就会卡在某个逻辑中(revc 或者 send)。所以我们需要让服务器知道客户端是否退出,如果退出,让服务器直接结束对该客户端的服务,并且释放对该服务器的套接字资源。

  • 我们利用recv的返回值来判断客户端是否退出。如果返回0,那么说明已经客户端已经退出。然后在这个退出的逻辑里面结束服务,并且释放套接字资源。

ctrl + z # 将前台进程放到后台
bg 任务号  # 改成running状态
jobs # 查看任务

问题2

我们发现当有多个客户端想连接服务器时,只有第一个服务器可以正常使用。

  • 这是因为单进程的服务器会导致所有任务共用一份资源,如果一个任务卡死,就会导致所有任务卡死。

  • 所以第一个客户端将服务器卡在了Service的死循环中,导致其他的客户端无法连上服务器。

  • 我们使用多进程来编写服务器,这样父进程主要用来接收连接,然后fork子进程去完成通信。

  • 基于以上,我们的多进程版本的tcp服务器如下:

  1 #include <iostream>                                                                                  
  2 #include <cstdlib>
  3 #include <sys/types.h>
  4 #include <string>
  5 #include <sys/socket.h>
  6 #include <unistd.h>
  7 #include <arpa/inet.h>
  8 #include <signal.h>
  9 
 10 #define BACKLOG 10
 11 
 12 class TcpServer{
 13 private:
 14     int port;
 15     int sockfd;
 16 public:
 17     TcpServer(int _port)
 18       : port(_port)
 19       , sockfd(-1)
 20   {}
 21 
 22     void InitServer(){
 23       sockfd = socket(AF_INET, SOCK_STREAM, 0);
 24       //bind
 25       struct sockaddr_in local;
 26       local.sin_family = AF_INET;
 27       local.sin_port = htons(port);
 28       local.sin_addr.s_addr = INADDR_ANY;
 29       if(bind(sockfd, (struct sockaddr*)&local, sizeof(local)) < 0){
 30         std::cerr << "bind error" << std::endl;
 31         exit(1);
 32       }
 33       //listen
 34       if(listen(sockfd, BACKLOG) < 0){
 35         std::cerr << "listen error" << std::endl;
 36         exit(2);
 37       }
 38     }
 39 
 40     void Start(){
 41         signal(SIGCHLD, SIG_IGN);                                                                    
 42 
 43        struct sockaddr_in end_point;
 44        socklen_t len = sizeof(end_point);
 45       for(;;){
 46       // listen successfully, accept
 47        int acc_sock = accept(sockfd, (struct sockaddr*)&end_point, &len);
 48        if(acc_sock < 0){
 49          std::cerr << "accept error" << std::endl;
 50          continue;
 51        }
 52        // 连接建立完成, 开始通信
 53         std::cout << "get a new link... " << std::endl;
 54         if(fork() == 0){
 55           close(sockfd);
 56         Service(acc_sock);
 57         exit(6);  //子进程退出
 58         }
 59 
 60         close(acc_sock); //关闭子进程的sock
 61       }
 62    }                                                                                                 
 63     ~TcpServer(){
 64       close(sockfd);
 65     }
 66 
 67 private:
 68     void Service(int sockfd){
 69        char buf[64] = {0};
 70       for(;;){
 71       //ssize_t recv(int sockfd, void *buf, size_t len, int flags);
 72       ssize_t s = recv(sockfd, buf, sizeof(buf)-1, 0);
 73       if(s > 0){
 74         buf[s] = 0;
 75         std::cout << buf << std::endl;
 76 
 77         //int send(int s, const void *msg, size_t len, int flags);
 78         std::string str = buf;
 79            // str += "[server] ";
 80         send(sockfd, str.c_str(), str.size(), 0);
 81       }
 82       else if(s == 0){
 83         std::cout << "client quit..." << std::endl;
 84         break;                                                                                       
 85       }
 86       else{
 87         std::cerr << "recv error" << std::cout;
 88         break;
 89       }
 90     }
 91   }
 92 }; 

  • 多进程的服务器还是有很多细节的。
  • 我们知道子进程和父进程会各自拥有独立的文件描述符数组,但是子进程的文件描述符数组信息跟父进程的一样。对于子进程来说,sockfd套接字没有任何作用,因为它只需要accept函数的返回值的acc_sock即可与客户端完成通信。所以最好在子进程中关闭sockfd。而对于父进程,acc_sock没有任何意义,必须关闭acc_sock,不然就可能导致套接字资源的泄露。
  • 子进程完成于客户端的通信任务后,子进程退出,那么需要父进程来处理它的退出信息。但是如果父进程使用wait函数来等待子进程退出,那么父进程仍会卡住,这与我们的想法背道而驰。
  • 我们的处理方法是,子进程完成工作后,不仅仅会将退出信息交给父进程,还会向父进程发送一个SIGCHLD信号,我们定义处理该信号的方式为忽略即可。
  • 一个小技巧:在多进程版本的服务器中,我们可以让子进程再继续fork出孙子进程,然后让子进程立刻退出,然后父进程进程等待,立即成功。随后孙子进程变成孤儿进程,由系统领养,与父进程不再具有关系。也能完成我们的目标。
  • 但是这样写不太好,因为创建进程代价很大

多进程版本的缺陷

  • 创建进程代价很大,很浪费时间,可能让客户很不爽。
  • 进程资源有限,如果客户较多,那么没有足够多的进程用来通信。

多线程版本

/****************tcpServer.hpp 多线程版本*****************/
    1 #include <iostream>                                                                                
    2 #include <cstdlib>
    3 #include <sys/types.h>
    4 #include <string>
    5 #include <sys/socket.h>
    6 #include <unistd.h>
    7 #include <arpa/inet.h>
    8 #include <pthread.h>
    9 
   10 #define BACKLOG 10
   11 
   12 class TcpServer{
   13 private: 
   14     int port;
   15     int sockfd;
   16 public:
   17     TcpServer(int _port)
   18       : port(_port)
   19       , sockfd(-1)
   20   {}
   21 
   22     void InitServer(){
   23       sockfd = socket(AF_INET, SOCK_STREAM, 0);
   24       //bind
   25       struct sockaddr_in local;
   26       local.sin_family = AF_INET;
   27       local.sin_port = htons(port);
   28       local.sin_addr.s_addr = INADDR_ANY;
   29       if(bind(sockfd, (struct sockaddr*)&local, sizeof(local)) < 0){
   30         std::cerr << "bind error" << std::endl;
   31         exit(1);
   32       }
   33       //listen
   34       if(listen(sockfd, BACKLOG) < 0){
   35         std::cerr << "listen error" << std::endl;
   36         exit(2);
   37       }
   38     }
   39 
   40     void Start(){                                                                                  
   41        struct sockaddr_in end_point;
   42        socklen_t len = sizeof(end_point);
   43       for(;;){
   44       // listen successfully, accept
   45        int acc_sock = accept(sockfd, (struct sockaddr*)&end_point, &len);
   46        if(acc_sock < 0){
   47          std::cerr << "accept error" << std::endl;
   48          continue;
   49        }
   50        // 连接建立完成, 开始通信
   51         std::cout << "get a new link... " << std::endl;
   52 
   53         //细节,防止新线程没被创建出来,acc_sock被覆盖。
   54         int* p = new int(acc_sock);
   55         pthread_t tid;
   56         //int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
   57         //  void *(*start_routine) (void *), void *arg);
   58         pthread_create(&tid, nullptr, start_routine, (void*)p);
   59       }
   60    }
   61     ~TcpServer(){
   62       close(sockfd);                                                                               
   63     }
   64 
   65 private:
   66     static void* start_routine(void* arg){
   67       pthread_detach(pthread_self());
 68       int* p = static_cast<int*>(arg);  
 69         Service(*p);  
 70         return nullptr;                                                                              
 71     }                                                                                   
 72     static void Service(int sockfd){                                                    
 73        char buf[64] = {0};                                                              
 74       for(;;){                                                                          
 75       //ssize_t recv(int sockfd, void *buf, size_t len, int flags);                     
 76       ssize_t s = recv(sockfd, buf, sizeof(buf)-1, 0);                                  
 77       if(s > 0){                                                                        
 78         buf[s] = 0;                                                                     
 79         std::cout << buf << std::endl;                                                  
 80                                                                                         
 81         //int send(int s, const void *msg, size_t len, int flags);                      
 82         std::string str = buf;                                                          
 83            // str += "[server] ";                                                       
 84         send(sockfd, str.c_str(), str.size(), 0);                                       
 85       }                                                                                 
 86       else if(s == 0){  
 87         std::cout << "client quit..." << std::endl;
 88         break;
 89       }
 90       else{
 91         std::cerr << "recv error" << std::cout;
 92         break;
 93       }
 94     }
 95   }
 96 };        
  • 多线程版本也有很多细节。
  • 第一个就是我们说过的start_routine作为类内函数,必须是static,不然第一个参数是this指针。
  • 其次pthread_create的最后一个参数是否传入this呢?如果不传入this指针,那么我们无法使用类内部函数。如果传入this指针,那么,没有了acc_sock,我们无法实现通信。怎么办呢?我们发现Service函数没有使用到this指针,所以我们可以将Service函数也变成static,然后在start_routine种传入acc_sock。
  • 线程是公用文件描述符数组的,所以多线程能连接的客户端也是有限的。
  • 有一个很隐晦的bug,当新线程还没有被创建出来的时候,主线程又重新进行线程创建,这就导致sock被覆盖。
  • 我们可以这样 int* p = new(sock);
  • 这样会拷贝一个p的复制p1,即使p被覆盖,p1也是指向sock。

线程池版本的多线程tcp

多进程和多线程的代码都有一个问题:

  • 当客户端申请连接的时候,服务器才会给它创建进程/线程,这会让用户很不爽,所以有没有一种方法,在用户没有申请之前就创建好进程/线程,等用户一申请就立刻去执行任务呢?
  • 有的,这就是池化技术。
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-11-10 12:49:03  更:2021-11-10 12:49:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 22:54:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码