网络套接字

news2024/11/20 3:24:00

网络套接字

森格 (1)

文章目录

  • 网络套接字
      • 认识端口号
      • 初识TCP协议
      • 初识UDP协议
      • 网络字节序
    • socket编程接口
        • socket创建socket文件描述符
        • bind绑定端口号
        • sockaddr结构体
          • netstat -nuap:查看服务器网络信息
        • 代码编译运行展示
    • 实现简单UDP服务器开发

认识端口号

端口号(port)是传输层协议的内容

  • 端口号是一个2字节16位的整数。在Linux系统下其类型为uint16_t
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程
  • 一个端口号只能被一个进程占用

初识TCP协议

先对TCP((Transmission Control Protocol)有一个直观的认识,它有以下性质

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

初识UDP协议

先对对UDP(User Datagram Protocol)有一个直观的认识,它有以下性质

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

网络字节序

内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

首先先回顾一下大小端

image-20230809175701662

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址存放高字节,高地址存放低字节
  • 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据,如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可

为了使得网络程序具备可移植性,使得同样的代码在大端和小端计算机上编译后都能运行,可以调用以下库函数,使得网络字节序和主机字节序的相互转换。

 #include <arpa/inet.h>

       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);
  • h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 例如htonl(uint16_t hostshort)意思是将16位的短整形主机字节序转换成网络字节序,例如端口号的发送
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回

socket编程接口

socket创建socket文件描述符

函数原型

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
  • domain指协议域,常见的协议有AF_INET(ipv4) AF_INET6(ipv6) AF_LOCAL(本地协议)。协议决定了socket的地址类型,在通信中必须采用相应的地址。例如使用的是ipv4的协议,那么参数需要传AF_INET

  • type为socket的类型,主要分为流格式套接字(SOCK_STREAM)即使用TCP协议和数据报格式,也因此称之为面向连接的套接字,是一种可靠的、双向的通信数据流.它的数据可以准确无误的到达另一台里算计,如果损坏或者丢失会重新发送套接字;(SOCK_DGRAM)即使用UDP协议,也因此称之为无连接的套接字,计算机只负责传输数据,不进行数据校验

  • protocol默认输入0

  • 若创建成功,返回值是一个socket文件描述符,若创建失败,返回-1,错误信息保存在错误码

bind绑定端口号

函数原型

#include <sys/socket.h>     
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
  • socket指需要绑定的socket文件描述符
  • address指一个指向特定协议的地址结构的指针
  • address_len指该地址结构的长度。

sockaddr结构体

image-20230809193508849

很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是其他的,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

sackaddr_in结构源码

struct sockaddr_in
{
    __SOCKADDR_COMMON(sin_);
    int_port_t sin_port; /*Port number. */
    struct in_addr sin_addr; /*Internet address. */
    
    /*Pad to size of 'struct sockaddr'. */
    unsigned char sin_zero[sizeof(struct sockaddr)-
        __SOCKADDR_COMMON_SIZE-
        sizeof(in_port_t)-
        sizeof(struct in_addr)];
}

__SOCKADDR_COMMON的宏定义

#define __SOCKADDR_COMMON_(sa_prefix)
sa_family_t sa_prefix##fimily
  • 第一个成员传sin_进去,##具有将左右两个符号合并成一个符号的作用,因此返回sin_family,参数类型是sa_family_t ,该参数称为协议家族,对应sackaddr_in结构的16位地址类型
  • sin_port成员指对应的端口号,其类型是uint16_t
  • sin_addr成员指对应的IP地址,其类型是uint32_t。实际上这个类型用于网络通信,而常见的192.155.172.83这样风格的IP地址类型是string,又被称为点分十进制,这个类型唯一的用处是可读性好,专门用于在用户机上供用户读取。实际上OS提供有相应接口供类型uint32_t和string类型的相互转换
  • 剩余部分用于填充结构体剩余部分

in_addr结构源码

/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
  • in_addr结构体内有一个in_addr_t类型的s_addr变量,实际上是用的这个变量接收的IP地址

需要注意的是:

  1. 对于虚拟机用户来说,填入sackaddr_in结构体中的ip地址不能填入公网ip即连接上虚拟机使用的公网ip。但可以填入内网ip
netstat -nuap:查看服务器网络信息

image-20230809231456727

根据上面的接口可以写出server端的部分代码

netstat -nupa

server.c

  1 #include"server.hpp"                                                                             
  2 #include<memory>
  3 
  4 using namespace std;
  5 using namespace udpServer ;
  6 static void Usage(string proc)
  7 {
  8     cerr<<"Usage: \n\t"<<proc<<" serverport"<<endl;
  9 }
 10 int main(int argc,char* argv[])
 11 {
 12 if(argc!=2)
 13 {
 14  Usage(argv[0]);
 15  exit(USAGE_ERR);
 16 }
 17 
 18 uint16_t port=atoi(argv[1]);//atoi作用:把port的str类型转化成int类型。而uin16_t本质类型是int
 19 
 20 unique_ptr<udpserver> uspr(new udpserver(port));
 21 uspr->initudpserver();//初始化服务端
 22 uspr->start();//启动服务端
 23 return 0;
 24 }

server.hpp

1 #pragma once                                                                                            
  2 #include<iostream>
  3 #include <sys/types.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7  #include <errno.h>
  8  #include <strings.h>
  9  #include <stdlib.h>
 10   #include <string.h>
 11   #include<unistd.h>
 12 using namespace std;
 13 
 14 namespace udpServer
 15 {
 16    static const  string defaultIP="0.0.0.0";//默认IP地址
 17    static int SIZE=1024;
 18 enum {USAGE_ERR=1,SOCKET_ERR,BIND_ERR,OPEN_ERR};//用枚举函数定义各自错误的返回值
 19     class udpserver
 20     {
 21 public:
 22 
 23 udpserver(const uint16_t& port,const string& ip=defaultIP)
 24 :_port(port)
 25 ,_ip(ip)
 26 ,_sockfd(-1)
 27 {}
 28
29 void initudpserver()
 30 {
 31     //1.创建套接字
 32 _sockfd=socket(AF_INET,SOCK_DGRAM,0);//创建套接字文件描述符
 33 if(_sockfd==-1)
 34 {
 35 cerr<<"socket err"<<errno<<" : "<<strerror(errno)<<endl;
 36 exit(SOCKET_ERR);
 37 }
 38  cout<<"socket success"<<": "<< _sockfd<<endl;
 39 //2.绑定port端口号
 40 //2.1将port和ip填入结构体中,该结构体可以理解成用户定义的数据或用户栈
 41 struct sockaddr_in local;//创建结构
 42 bzero(&local,sizeof(local));//将结构清零
 43 local.sin_family=AF_INET;//填充协议家族
 44 local.sin_port=htons(_port);//填充端口号。htons将port主机序列转化为网络序列
 45 //local.sin_addr.s_addr=inet_addr(_ip.c_str());
 46 //填充ip地址。inet_addr函数作用:将ip地址的string类型转化为uint32_t,其次ip地址的将主机序列转化为网络序>    列
 47 local.sin_addr.s_addr=htons(INADDR_ANY);//不绑定指定ip,可以接收任何传达到指定端口号的ip主机发的数据
 48 //2.2将sockaddr_in与套接字进行绑定                                     
 49 int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
 50 if(n==-1)
 51 {
 52 cerr<<"bind err"<<errno<<" : "<<strerror(errno)<<endl;
 53 exit(BIND_ERR);
54 }
 55 }
 56 
 57 void start()
 58 {
 59     for(;;)
 60     {
 61         char buffer[SIZE];//缓冲区
 62        struct sockaddr_in out;
 63        socklen_t len=sizeof(out);
 64         ssize_t s=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(sockaddr*)&out,&len);
 65         if(s>0)
 66         {
 67             buffer[s]=0;
 68             string clientip=inet_ntoa(out.sin_addr);//网络序列转换为主机序列;uin32t_t->string
 69             uint16_t clientport=ntohs(out.sin_port);//网络序列转化为主机序列
 70             string message=buffer;
 71             cout<<clientip<<"["<<clientport<<"]# "<<message<<endl;
 72         }
 73 }
 74 }
 75                          
 76 ~udpserver()
 77 {}
 78 
 79 private:
 80 uint16_t _port;//端口号
 81 string _ip;//ip地址
 82 int _sockfd;//套接字文件描述符
 83     };
 84 }
  1. 根据server.hpp的47行可知:server端(服务端)不能绑定指定ip地址,绑定ip地址意味着只接收指定ip地址和端口号的主机发来的报文;而不绑定ip地址可以接收任意ip地址的主机只需绑定指定端口号即发送报文給服务端。因此在server.cc只需要传参端口号port

  2. server.hpp中start启动函数内是个死循环,即服务器本质是一个死循环,这个进程被称为常驻内存进程sockaddr_in类型的结构的ip地址INADDR_ANY就是全0,即接收任意绑定了对应端口的进程发送来的数据。

  3. server.hpp的42行的bzero函数用于将local结构体清零

bzero函数原型

 #include <strings.h>

       void bzero(void *s, size_t n);

将参数s 所指的内存区域前n 个字节全部设为零

  1. 第64行recvfrom用于从一个已连接的套接字接收数据的函数。它的作用是从指定的套接字接收数据,并将接收到的数据保存到指定的缓冲区中。

recvfrom函数原型

  #include <sys/types.h>
  #include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  • sockfd是接收数据的套接字文件描述符
  • buf是接收数据存放缓冲区
  • len是缓冲区的大小
  • flags是标志位默认为0表示阻塞式读取
  • src_addr是输入输出型参数,用于接收发送端发来的数据信息如ip和port。这里要传参结构体的地址
  • addrlen是src_addr结构体大小,这里要传参地址
  1. 第68行inet_ntoa函数用于将网络字节序的IP地址转换为点分十进制的字符串形式。一是将uint32_t类型转化为string类型,二是将网络序列转化为主机序列

函数原型

#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
  • in_addr表示网络字节序的IPv4地址的结构体

需要注意的是:

  • 云服务器的公网ip不能绑定,由于该服务器是虚拟服务器,因此公网ip也是虚拟ip,但内网ip:127.0.0.1可以绑定。而服务端绑定127.0.0.1,客户端接收数据,即数据完成了本地环回。在这个过程代码数据的传输不会到达物理层。
  • image-20230810202336549

image-20230809233127277

  • netstat -nuap:查看服务器网络信息

image-20230810203628100

client.cc


#include<memory>
#include"client.hpp"
using namespace udpClient;
static void startrouine(string proc)
{
    cout<<"\nstartroutine\n\t" <<proc<<"serverip serverport"<<endl;
}
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        startrouine(argv[0]);
        exit(1);
    }
   string serverip=argv[1];
   uint16_t serverport=atoi(argv[2]);//atoi作用:把port的str类型转化成int类型。而uin16_t本质类型是int

unique_ptr<udpclient> uct(new udpclient(serverip,serverport));
uct->initclient();
uct->run();
    return 0;
}

client.hpp

 1 #pragma once                                                                                          
    2 #include <iostream>
    3 #include <string>
    4 #include <strings.h>
    5 #include <cerrno>
    6 #include <cstring>
    7 #include <cstdlib>
    8 #include <unistd.h>
    9 #include <sys/types.h>
   10 #include <sys/socket.h>
   11 #include <arpa/inet.h>
   12 #include <netinet/in.h>
   13 #include <pthread.h>
   14 using namespace std;
   15 namespace udpClient
   16 {
   17     class udpclient
   18     {
   19 public:
   20 udpclient(const string& ip,const uint16_t port)
   21 :_serverip(ip)
  22 ,_serverport(port)
   23 ,_sockfd(-1)
   24 ,flag(false)
   25 {}
   26 void initclient()
   27 {
   28     //1.创建套接字
 29     _sockfd=socket(AF_INET,SOCK_DGRAM,0);
   30     if(_sockfd==-1)
   31     {
   32         cout<<"socket error"<<errno<<":"<<strerror(errno)<<endl;
   33         exit(2);
   34     }
   35     cout<<"socket success: "<<_sockfd<<endl;
   36     //2.绑定,但不用显示绑定,OS会自动绑定指定ip和端口
   37 }
   38 void run()
   39 {
   40   //  pthread_create(&_pt,nullptr,readmessage,(void*)&_sockfd);
   41 
   42     struct sockaddr_in server;
   43     server.sin_family=AF_INET;
   44     server.sin_addr.s_addr=inet_addr(_serverip.c_str());//主机序列转换为网络序列;string->uin32t_t
   45     server.sin_port=htons(_serverport);//主机序列转换为网络序列
   46 
   47     string message;
   48     char cmdbuffer[1024];
   49     while(!flag)
   50     {
   51         fprintf(stderr,"enter# ");                                                                    
   52         fflush(stderr);
   53         fgets(cmdbuffer,sizeof(cmdbuffer),stdin);//键盘上的内容写入缓冲区cmdbuffer
   54         cmdbuffer[strlen(cmdbuffer)-1]=0;
   55         message=cmdbuffer;
   56         sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
 57         //将缓冲区的内容发送到套接字里通常用于UDP
   58     }
   59 }
   60 ~udpclient(){}
   61 private:
   62     int _sockfd;
   63     string _serverip;
   64 uint16_t _serverport;
   65 bool flag;
   66     };
   67 } 
  1. client.cc中atoi函数用于将我们命令行输入的端口号string类型转化成int类型,而uin16_t本质类型是int
  2. client.hpp56行sendto函数是在Linux系统下用于发送数据报的函数。它可以向指定的socket文件描述符中发送数据报,适用于面向无连接的UDP套接字或以connectionless模式工作的AF_INET套接字。
  3. 可以看到在client端无需bind指定端口号和ip地址。一个端口只能被一个进程绑定,若客户端绑定指定端口,其他客户端就不能往该端口中发数据了。因此客户端不需要自主绑定,OS会自动绑定。在第一次发送数据时OS会帮我们绑定ip和端口即使用sendto函数时。而OS帮我们绑定端口是随机的。

sendto函数原型

  #include <sys/types.h>
  #include <sys/socket.h>
  ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
  • sockfd是传入数据报指定的socket文件描述符
  • buf是需要传输数据的缓冲区
  • len是缓冲区的大小
  • flags是标志位默认为0表示阻塞式读取
  • dest_addr是输入型参数,用于发送数据信息如ip和port。这里要传参结构体的地址
  • addrlen是dest_addr结构体的大小

代码编译运行展示

image-20230810210937179

  • udpclient发送一条信息,udpserver接收一条信息。另外可以将udpserver打包发送到另一台服务器,该台服务器运行后输入8080端口号,本主机发送的信息在该台服务器也能接收到!

若绑定了IP地址,那么服务器只能接收改绑定的IP地址的数据,其他IP地址发送来的不能接收

INADDR_ANY:任意地址绑定,任何绑定了8080ip的地址都能发数据来并接收(全0)

socket函数原型

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
  • domain:协议域,常见的协议组用AF_INET(ipv4) AF_INET6(ipv6) AF_LOCAL AF_ROUTE . 协议族决定了socket的地址类型,在通信中必须采用相应的地址

  • type为socket的类型,主要分为流格式套接字(SOCK_STREAM)即使用TCP协议和数据报格式套接字(SOCK_DGRAM)即使用UDP协议

  • 默认输入0

image-20230806225510089

bind

#include <sys/socket.h>     
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
  • 套接字
  • sockaddr结构体地址
  • 结构体大小

recvfrom

recvfrom函数是用于从一个已连接的套接字接收数据的函数。它的作用是从指定的套接字接收数据,并将接收到的数据保存到指定的缓冲区中。

  #include <sys/types.h>
  #include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  • socket文件描述符
  • 缓冲区
  • 缓冲区大小
  • 标志位默认为0表示阻塞式读取
  • 存储的结构体地址
  • 结构体大小
  • src_addr是输入输出型参数,用于接收发送端发来的数据信息如ip和port
  • 读取成功返回读取到的字节数,读取失败返回-1

inet_ntoa()将网络序列转化为主机序列,将整数转化为字符串类型

课里讲的主要是代码和接口

inet_addr

inet_addr是一个用于将点分十进制表示的IP地址转换成网络字节序的32位二进制IP地址的函数。该函数定义在C语言的头文件<arpa/inet.h>中。一是将string类型转化为uint32_t类型,二是将主机序列转化为网络序列

 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h> 
in_addr_t inet_addr(const char *cp)
  • cp指主机序列string类型的ip

sendto

用于通过指定的套接字向目标地址发送数据。该函数通常用于面向无连接的协议(如UDP)中发送数据报。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
  • sockfd是套接字描述符,指定要发送数据的套接字
  • buf是指向要发送的数据的缓冲区的指针
  • len是要发送的数据的长度
  • flags是发送操作的标志位,通常设置为 0,即为阻塞式发送
  • dest_addr是指向目标地址的结构体指针,包括目标 IP 地址和端口号
  • addrlen是目标地址结构体的长度

实现简单UDP服务器开发

makefile

.PHONY:all
all:udpClient udpServer

udpClient:udpClient.cc
	g++ -o $@ $^ -std=c++11 -pthread

udpServer:udpServer.cc
	g++ -o $@ $^ -std=c++11 -pthread

.PHONY:clean
clean:
	rm -rf udpClient udpServer

udpClient.cc

#include "udpClient.hpp"
#include <memory>

using namespace Client;

static void Usage(string proc)
{
    cerr << "\nUsage:\n\t" << proc << " server_ip server_port\n\n";
}

// ./udpClient server_ip server_port
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr<udpClient> ucli(new udpClient(serverip, serverport));

    ucli->initClient();
    ucli->run();

    return 0;
}
  • 先创建一个udpClient对象,然后调用对象的initClient和run函数

udpClient.hpp

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>

namespace Client
{
    using namespace std;

    class udpClient
    {
    public:
        udpClient(const string &serverip, const uint16_t &serverport)
            : _serverip(serverip), _serverport(serverport), _sockfd(-1), _quit(false)
        {
        }
        void initClient()
        {
            // 创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(2);
            }
            cout << "socket success: "
                 << " : " << _sockfd << endl;

            // 2. client要不要bind[必须要的],client要不要显示的bind,需不需程序员自己bind?不需要!!!
            // 写服务器的是一家公司,写client是无数家公司 -- 由OS自动形成端口进行bind!-- OS在什么时候,如何bind
        }

        static void *readMessage(void *args)
        {
            int sockfd = *(static_cast<int *>(args));
            pthread_detach(pthread_self());
            while (true)
            {
                char buffer[1024];
                struct sockaddr_in temp;
                socklen_t temp_len = sizeof(temp);
                size_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &temp_len);
                if (n >= 0)
                    buffer[n] = 0;
                cout << buffer << endl;
            }

            return nullptr;
        }

        void run()
        {
            pthread_create(&_reader, nullptr, readMessage, (void *)&_sockfd);

            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            server.sin_port = htons(_serverport);

            string message;
            char cmdline[1024];
            while (!_quit)
            {
                //cerr << "# "; // ls -a -l
                // cin >> message;
                fprintf(stderr, "Enter# ");
                fflush(stderr);
                fgets(cmdline, sizeof(cmdline), stdin);
                cmdline[strlen(cmdline)-1] = 0;
                message = cmdline;
                sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
            }
        }
        ~udpClient()
        {
        }

    private:
        int _sockfd;
        string _serverip;
        uint16_t _serverport;
        bool _quit;

        pthread_t _reader;
    };
} // namespace Client
  • Client端的父进程进行sendto操作,子进程进行recvfrom操作。在客户端的命令行解析器输入数据后,客户端将数据sendto給服务器。服务器进行数据处理后,sendto回来給客户端,客户端的子进程进行recvfrom接收数据并打印

udpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

namespace Server
{
    using namespace std;

    static const string defaultIp = "0.0.0.0"; //TODO
    static const int gnum = 1024;

    enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR, OPEN_ERR};

    typedef function<void (int,string,uint16_t,string)> func_t;

    class udpServer
    {
    public:
        udpServer(const func_t &cb, const uint16_t &port, const string &ip = defaultIp)
        :_callback(cb), _port(port), _ip(ip), _sockfd(-1)
        {}
        void initServer()
        {
            // 1. 创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if(_sockfd == -1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(SOCKET_ERR);
            }
            cout << "socket success: " << " : " << _sockfd << endl;

            // 2. 绑定port,ip(TODO)
            // 未来服务器要明确的port,不能随意改变
            struct sockaddr_in local; // 定义了一个变量,栈,用户
            bzero(&local, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port); 
            local.sin_addr.s_addr = inet_addr(_ip.c_str());   // 1. string->uint32_t 2. htonl(); -> inet_addr
            //local.sin_addr.s_addr = htonl(INADDR_ANY); // 任意地址bind,服务器的真实写法
            int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
            if(n == -1)
            {
                cerr << "bind error: " << errno << " : " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
            // UDP Server 的预备工作完成
        }
        void start()
        {
            // 服务器的本质其实就是一个死循环
            char buffer[gnum];
            for(;;)
            {
                // 读取数据
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer); //必填
                ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
                // 1. 数据是什么 2. 谁发的?
                if(s > 0)
                {
                    buffer[s] = 0;
                    string clientip = inet_ntoa(peer.sin_addr); //1. 网络序列 2. int->点分十进制IP
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;

                    cout << clientip <<"[" << clientport << "]# " << message << endl;
                    // 对数据做处理
                    _callback(_sockfd, clientip, clientport, message);
                }
            }
        }
        ~udpServer()
        {
        }
    private:
        uint16_t _port;
        string _ip; 
        int _sockfd;
        func_t _callback; //回调
    };
}

udpServer.cc

#include "udpServer.hpp"
#include "onlineUser.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>

using namespace std;
using namespace Server;

const std::string dictTxt="./dict.txt";
unordered_map<string, string> dict;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

static bool cutString(const string &target, string *s1, string *s2, const string &sep)
{
    auto pos = target.find(sep);
    if(pos == string::npos) return false;
    *s1 = target.substr(0, pos); 
    *s2 = target.substr(pos + sep.size()); 
    return true;
}

static void initDict()
{
    ifstream in(dictTxt, std::ios::binary);
    if(!in.is_open())
    {
        cerr << "open file " << dictTxt << " error" << endl;
        exit(OPEN_ERR);
    }
    string line;
    std::string key, value;
    while(getline(in, line))
    {
        if(cutString(line, &key, &value, ":"))
        {
            dict.insert(make_pair(key, value));
        }
    }

    in.close();

    cout << "load dict success" << endl;
}

void reload(int signo)
{
    (void)signo;
    initDict();
}
// // demo1
void handlerMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    // 就可以对message进行特定的业务处理,而不关心message怎么来的 ---- server通信和业务逻辑解耦!
    // 婴儿版的业务逻辑
    string response_message;
    auto iter = dict.find(message);
    if(iter == dict.end()) response_message = "unknown";
    else response_message = iter->second;

    // 开始返回
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());

    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr*)&client, sizeof(client));
}

// // demo2
void execCommand(int sockfd, string clientip, uint16_t clientport, string cmd)
{
    //1. cmd解析,ls -a -l
    //2. 如果必要,可能需要fork, exec*

    if(cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("rmdir") != string::npos)
    {
        cerr << clientip << ":" << clientport << " 正在做一个非法的操作: " << cmd << endl;
        return;
    }

    string response;
    FILE *fp = popen(cmd.c_str(), "r");
    if(fp == nullptr) response = cmd + " exec failed";
    char line[1024];
    while(fgets(line, sizeof(line), fp))
    {
        response += line;
    }
    pclose(fp);

    // 开始返回
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());

    sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&client, sizeof(client));
}

OnlineUser onlineuser;

// demo3
void routeMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    if (message == "online") onlineuser.addUser(clientip, clientport);
    if (message == "offline") onlineuser.delUser(clientip, clientport);
    if (onlineuser.isOnline(clientip, clientport))
    {
        // 消息的路由
        onlineuser.broadcastMessage(sockfd, clientip, clientport, message);
    }
    else
    {
        struct sockaddr_in client;
        bzero(&client, sizeof(client));

        client.sin_family = AF_INET;
        client.sin_port = htons(clientport);
        client.sin_addr.s_addr = inet_addr(clientip.c_str());

        string response = "你还没有上线,请先上线,运行: online";

        sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&client, sizeof(client));
    }
}

// ./udpServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    // string ip = argv[1];
     signal(2, reload);
    // initDict();

   //  std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port));
  //   std::unique_ptr<udpServer> usvr(new udpServer(execCommand, port));
    std::unique_ptr<udpServer> usvr(new udpServer(routeMessage, port));

    usvr->initServer();
    usvr->start();

    return 0;
}

demo1:实现一个英译中服务

static bool cutString(const string &target, string *s1, string *s2, const string &sep)
{
    auto pos = target.find(sep);
    if(pos == string::npos) return false;
    *s1 = target.substr(0, pos); 
    *s2 = target.substr(pos + sep.size()); 
    return true;
}

static void initDict()
{
    ifstream in(dictTxt, std::ios::binary);
    if(!in.is_open())
    {
        cerr << "open file " << dictTxt << " error" << endl;
        exit(OPEN_ERR);
    }
    string line;
    std::string key, value;
    while(getline(in, line))
    {
        if(cutString(line, &key, &value, ":"))
        {
            dict.insert(make_pair(key, value));
        }
    }

    in.close();

    cout << "load dict success" << endl;
}

void reload(int signo)
{
    (void)signo;
    initDict();
}

// // demo1
void handlerMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    // 就可以对message进行特定的业务处理,而不关心message怎么来的 ---- server通信和业务逻辑解耦!
    // 婴儿版的业务逻辑
    string response_message;
    auto iter = dict.find(message);
    if(iter == dict.end()) response_message = "unknown";
    else response_message = iter->second;

    // 开始返回
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());

    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr*)&client, sizeof(client));
}

// ./udpServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    // string ip = argv[1];
     signal(2, reload);
    // initDict();

     std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port));


    usvr->initServer();
    usvr->start();

    return 0;
}
  • 在客户端输入后将数据sendto給服务器,服务器对数据进行recvfrom接收,接收后进行解析,若输入的数据在unordermap dict的键值中,就将键值对应的value返回,而键值是英文,value是英文对应的中文。对应关系保存在当前目录的dict.txt文件中

dict.txt文件中数据对应关系

apple苹果
banana香蕉
hello你好
goodman好人
  • 启动服务器后,需要先发送2号信号加载dict.txt文件,即热加载

image-20230816165529481

demo2:将客户端发送来的代码当作命令行在服务器上做解析,即客户端输入命令对服务器进行命令行操作,操作后的服务器命令行解析器的结果返回給客户端

void execCommand(int sockfd, string clientip, uint16_t clientport, string cmd)
{
    //1. cmd解析,ls -a -l
    //2. 如果必要,可能需要fork, exec*

    if(cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("rmdir") != string::npos)
    {
        cerr << clientip << ":" << clientport << " 正在做一个非法的操作: " << cmd << endl;
        return;
    }

    string response;
    FILE *fp = popen(cmd.c_str(), "r");
    if(fp == nullptr) response = cmd + " exec failed";
    char line[1024];
    while(fgets(line, sizeof(line), fp))
    {
        response += line;
    }
    pclose(fp);

    // 开始返回
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());

    sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&client, sizeof(client));
}
// ./udpServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
     std::unique_ptr<udpServer> usvr(new udpServer(execCommand, port));
  
    usvr->initServer();
    usvr->start();

    return 0;
}
  1. popen函数执行cmd字符串里的命令,然后通过fgets按行读取将执行后的结果写入response字符串中,再通过sendto函数返回給client端

popen函数用于在一个子进程中执行一个 shell 命令,并建立一个与该子进程之间的管道,以便可以通过管道进行输入输出操作。

#include <stdio.h>
FILE *popen(const char *command, const char *type);
  • command是要执行的 shell 命令,以字符串形式传递
  • 指定管道的类型,可以是 "r"(读取模式)或 "w"(写入模式)
  • popen 函数将返回一个文件流指针(FILE *),您可以使用该指针进行读取或写入操作,具体取决于您指定的管道类型。当不再需要时,应使用 pclose 函数来关闭子进程并释放资源

image-20230816171520118

demo3:在客户端输入online上线,在服务器接收客户端发送来消息,当客户端发送online給服务器时,客户端才算上线成功,那么服务器才会将客户端发送来的信息返回給客户端

OnlineUser.hpp

#pragma once

#include <iostream>
#include <string>
#include <unordered_map>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

using namespace std;

class User
{
public:
    User(const string &ip, const uint16_t &port) : _ip(ip), _port(port)
    {
    }
    ~User()
    {
    }
    string ip(){ return _ip; }
    uint16_t port(){ return _port; }
private:
    string _ip;
    uint16_t _port;
};

class OnlineUser
{
public:
    OnlineUser() {}
    ~OnlineUser() {}
    void addUser(const string &ip, const uint16_t &port)
    {
        string id = ip + "-" + to_string(port);
        users.insert(make_pair(id, User(ip, port)));
    }
    void delUser(const string &ip, const uint16_t &port)
    {
        string id = ip + "-" + to_string(port);
        users.erase(id);
    }
    bool isOnline(const string &ip, const uint16_t &port)
    {
        string id = ip + "-" + to_string(port);
        return users.find(id) == users.end() ? false : true;
    }
    void broadcastMessage(int sockfd, const string &ip, const uint16_t &port, const string &message)
    {
        for (auto &user : users)
        {
            struct sockaddr_in client;
            bzero(&client, sizeof(client));

            client.sin_family = AF_INET;
            client.sin_port = htons(user.second.port());
            client.sin_addr.s_addr = inet_addr(user.second.ip().c_str());
            string s = ip + "-" + to_string(port) + "# ";
            s += message;
            sendto(sockfd, s.c_str(), s.size(), 0, (struct sockaddr *)&client, sizeof(client));
        }
    }

private:
    unordered_map<string, User> users;
};
OnlineUser onlineuser;

// demo3
void routeMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    if (message == "online") onlineuser.addUser(clientip, clientport);
    if (message == "offline") onlineuser.delUser(clientip, clientport);
    if (onlineuser.isOnline(clientip, clientport))
    {
        // 消息的路由
        onlineuser.broadcastMessage(sockfd, clientip, clientport, message);
    }
    else
    {
        struct sockaddr_in client;
        bzero(&client, sizeof(client));

        client.sin_family = AF_INET;
        client.sin_port = htons(clientport);
        client.sin_addr.s_addr = inet_addr(clientip.c_str());

        string response = "你还没有上线,请先上线,运行: online";

        sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&client, sizeof(client));
    }
}

// ./udpServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<udpServer> usvr(new udpServer(routeMessage, port));

    usvr->initServer();
    usvr->start();

    return 0;
}
  • 只需要打开一个服务器server端,然后打开两个客户端,但要求各自新建命名管道fifo,将客户端接收到的信息通过命名管道读出来

image-20230816184738000

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

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

相关文章

Python 3 使用HBase 总结

HBase 简介和安装 请参考文章&#xff1a;HBase 一文读懂 Python3 HBase API HBase 前期准备 1 安装happybase库操作hbase 安装该库 pip install happybase2 确保 Hadoop 和 Zookeeper 可用并开启 确保Hadoop 正常运行 确保Zookeeper 正常运行3 开启HBase thrift服务 使用命…

谈谈召回率(R值),准确率(P值)及F值

通俗解释机器学习中的召回率、精确率、准确率&#xff0c;一文让你一辈子忘不掉这两个词 赶时间的同学们看这里&#xff1a;提升精确率是为了不错报、提升召回率是为了不漏报 先说个题外话&#xff0c;暴击一下乱写博客的人&#xff0c;网络上很多地方分不清准确率和精确率&am…

前端实战系列:【2023酷炫前端特效】HTML蜂巢特效(附完整可执行代码 + 全网唯一!超详细注释分析 (熬夜换来的...),让你看得懂,敲的出代码!

久别重逢非昨日,万语千言不忍谈。 🎯作者主页: 追光者♂🔥 🌸个人简介: 💖[1] 计算机专业硕士研究生💖 🌿[2] 2023年城市之星领跑者TOP1(哈尔滨)🌿 🌟[3] 2022年度博客之星人工智能领域TOP4🌟 🏅[4] 阿里云社区特邀专家博主🏅 🏆…

【等保测评】等保初级测评师试题合集(3w字汇总)

【等保测评】信息安全等级保护初级测评师试题合集 一、法律法规单选多选判断 二、实施指南单选多选 三、定级指南四、基本要求五、测评准则六、信息安全等级测评模拟模拟试题1一、单选二、多选三、判断四、简答 模拟试题2一、单选二、多选三、判断四、简答 模拟试题3一、单选二…

MPLS基础知识

MPLS&#xff1a;多协议标签交换 多协议&#xff1a;可以基于多种不同的3层协议来生成2.5层的标签信息&#xff1b; 包交换—包为网络层的PDU&#xff0c;故包交换是基于IP地址进行数据转发&#xff1b;就是路由器的路由行为&#xff1b; 原始的包交换&#xff1a;数据包进入…

【自动化测试】接口自动化01

文章目录 一、熟悉若requests库以及底层方法的调用逻辑二、接口自动化以及正则和Jsonpath提取器的应用6. 高频面试题&#xff1a;9. 示例&#xff1a;接口关联13. 文件上传示例14. cookie关联的接口 努力经营当下 直至未来明朗 一、熟悉若requests库以及底层方法的调用逻辑 接…

on-java-8 知识总结(低频部分)

Perl简介 Perl 是 Practical Extraction and Report Language 的缩写,可翻译为 “实用报表提取语言”。最开始&#xff0c;Perl是一门文本处理语言&#xff0c;不过现在已经是通用的语言了。 作者吐槽其write-only&#xff0c;想必是因为其灵活性&#xff0c;同一目标下能写出…

Android设备通过蓝牙HID技术模拟键盘实现

目录 一&#xff0c;背景介绍 二&#xff0c;技术方案 2.1 获取BluetoothHidDevice实例 2.2 注册/解除注册HID实例 2.3 Hid report description描述符生成工具 2.4 键盘映射表 2.5 通过HID发送键盘事件 三&#xff0c;实例 一&#xff0c;背景介绍 日常生活中&#xff0…

第15集丨Vue 江湖 —— 组件

目录 一、为什么需要组件1.1 传统方式编写应用1.2 使用组件方式编写应用1.3 Vue的组件管理 二、Vue中的组件1.1 基本概念1.1.1 组件分类1.1.2 Vue中使用组件的三大步骤:1.1.3 如何定义一个组件1.1.4 如何注册组件1.1.5 如何使用组件 1.2 注意点1.2.1 关于组件名1.2.2 关于组件标…

14.Linkedin在中国市场的主要竞争对手

自Linkedin敲响了中国的大门之后,在国内市场也拥有了大量的用户。经过不断地发展了改革创新,更是成为了国内影响力比较大的职业社交平台之一。为了能够在国内市场中取得成功,在进入国内之前,Linkedin就采取了全新的模式,不仅仅是销售机构,也具备了产品技术、市场、公关等完整的…

达芬奇无法播放视频,黑屏,不能预览画面

说一下其中一个原因&#xff0c;是因为用了像是xdisplay&#xff0c;super display这类软件&#xff0c;他们会安装一个显卡驱动而这个驱动就会导致这个问题。 方法很简单&#xff0c;在设备管理器里面&#xff0c;显示卡一览&#xff0c;卸载掉除了你的电脑显卡以外的虚拟显卡…

webpack 从入门到放弃!

webpack webpack于2012年3月10号诞生&#xff0c;作者是Tobias(德国)。参考GWT(Google Web Toolkit)的code splitting功能在webpack中进行实现。然后在2014年Instagram团队分享性能优化时&#xff0c;提出使用webpack的code splitting特性从而大火。 现在webpack的出现模糊了任…

计算机视觉目标检测性能指标

目录 精确率&#xff08;Precision&#xff09;和召回率&#xff08;Recall&#xff09; F1分数&#xff08;F1 Score&#xff09; IoU&#xff08;Intersection over Union&#xff09; P-R曲线&#xff08;Precision-Recall Curve&#xff09;和 AP mAP&#xff08;mean…

Redis中使用lua脚本

微信公众号访问地址&#xff1a;Redis中使用lua脚本 推荐文章&#xff1a; 1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表; 2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据; 3、为什么引入Redisson分布式锁&#xff1f; 4、Redisso…

60页数字政府智慧政务大数据资源平台项目可研方案PPT

导读&#xff1a;原文《60页数字政府智慧政务大数据资源平台项目可研方案PPT》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 项目需求分析 项目建设原则和基本策略…

C++之类之间访问函数指针(一百八十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

一、window配置微软商店中的Ubuntu,及错误解决方法

&#xff08;1&#xff09;首先&#xff0c;在微软商店中搜索“Ubuntu”&#xff0c;下载你喜欢的版本(此处) &#xff08;2&#xff09;设置适用于window的Linux子系统&#xff0c;跟着红色方框走 点击“确定”之后&#xff0c;会提示你重启电脑&#xff0c;按要求重启电脑即可…

无涯教程-Perl - sub函数

描述 此函数定义一个新的子例程。上面显示的参数遵循以下规则- NAME是子例程的名称。可以在有或没有原型规范的情况下预先声明命名的子例程(没有关联的代码块)。 匿名子例程必须具有定义。 PROTO定义了函数的原型,调用该函数以验证提供的参数时将使用该原型。 ATTRS为解析…

管理类联考——逻辑——真题篇——按知识分类——汇总篇——一、形式逻辑——假言—公式化转换—等价+矛盾

文章目录 第一节 假言—公式化转换—等价矛盾真题&#xff08;2012—38&#xff09;—假言—A→B的公式化转换—A→B的等价命题&#xff1a;①逆否命题&#xff1a;非B→非A。真题&#xff08;2015—47&#xff09; —假言A→B的公式化转换—A→B的等价命题&#xff1a;①逆否…

重新定义物化视图,你必须拥有的极速湖仓神器!

当今企业在进行数据分析时&#xff0c;面临着多重问题和挑战&#xff0c;而数据加工作为其中不可或缺的一环显得尤为重要。 首要的挑战在于数据加工的复杂性。从数据的产生到最终产生价值的整个链路仍然十分复杂&#xff0c;涉及多个环节和技术方案&#xff0c;这导致了技术复…