当前位置: 首页 > news >正文

网络套接字

网络套接字

森格 (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本质类型是int19 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 udpServer15 {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 udpserver20     {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->string69             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本质类型是intunique_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 udpClient16 {17     class udpclient18     {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_t45     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);//键盘上的内容写入缓冲区cmdbuffer54         cmdbuffer[strlen(cmdbuffer)-1]=0;55         message=cmdbuffer;56         sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));57         //将缓冲区的内容发送到套接字里通常用于UDP58     }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 udpServerudpClient:udpClient.ccg++ -o $@ $^ -std=c++11 -pthreadudpServer:udpServer.ccg++ -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"; //TODOstatic 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->点分十进制IPuint16_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

相关文章:

网络套接字

网络套接字 文章目录 网络套接字认识端口号初识TCP协议初识UDP协议网络字节序 socket编程接口socket创建socket文件描述符bind绑定端口号sockaddr结构体netstat -nuap&#xff1a;查看服务器网络信息 代码编译运行展示 实现简单UDP服务器开发 认识端口号 端口号(port)是传输层协…...

对话 4EVERLAND:Web3 是云计算的新基建吗?

在传统云计算的发展过程中&#xff0c;数据存储与计算的中心化问题&#xff0c;对用户来说一直存在着潜在的安全与隐私风险——例如单点故障可能会导致网络瘫痪和数据泄露等危险。同时&#xff0c;随着越来越多 Web3 项目应用的落地&#xff0c;对于数据云计算的性能要求也越来…...

iOS申请证书(.p12)和描述文件(.mobileprovision)

打包app时&#xff0c;经常会用到ios证书&#xff0c;但很多人都苦于没有苹果电脑&#xff0c;即使有苹果电脑的&#xff0c;也会觉得苹果电脑操作也很麻烦&#xff0c;这里记录一下&#xff0c;用香蕉云编&#xff0c;申请证书及描述文件的过程。 香蕉云编的地址&#xff1a;…...

Java:PO、VO、BO、DO、DAO、DTO、POJO

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Java&#xff1a;PO、VO、BO、DO、DAO、DTO、POJO PO持久化对象&#xff08;Persistent Object&#xff09; PO是持久化对象&#xff0c;用于表示数据库中的实体或表…...

c语言每日一练(8)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…...

周期 角频率 频率 振幅 初相角

周期 角频率 频率 振幅 初相角 当我们谈论傅里叶级数或波形分析时&#xff0c;以下术语经常出现&#xff1a; 周期 T T T: 函数在其图形上重复的时间或空间的长度。周期的倒数是频率。 频率 f f f: 周期的倒数&#xff0c;即一秒内波形重复的次数。单位通常为赫兹&#xff…...

根据一棵树的两种遍历构造二叉树

题目 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,null,…...

stack 、 queue的语法使用及底层实现以及deque的介绍【C++】

文章目录 stack的使用queue的使用适配器queue的模拟实现stack的模拟实现deque stack的使用 stack是一种容器适配器&#xff0c;具有后进先出&#xff0c;只能从容器的一端进行元素的插入与提取操作 #include <iostream> #include <vector> #include <stack&g…...

没学C++,如何从C语言丝滑过度到python【python基础万字详解】

大家好&#xff0c;我是纪宁。 文章将从C语言出发&#xff0c;深入介绍python的基础知识&#xff0c;也包括很多python的新增知识点详解。 文章目录 1.python的输入输出&#xff0c;重新认识 hello world&#xff0c;重回那个激情燃烧的岁月1.1 输出函数print的规则1.2 输入函…...

haproxy负载均衡

1、配置环境 作用环境windows测试  192.168.33.158 172.25.0.11 haproxy负载均衡haproxy&#xff1a;2.8.1&#xff0c;centos7172.25.0.31web服务器1--rs1Apache&#xff1a;2.4&#xff0c;redhat9172.25.0.32web服务器2--rs2Apache&#xff1a;2.4 &#xff0c; redhat9 2、…...

【数据结构】顺序队列模拟实现

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …...

TiDB数据库从入门到精通系列之六:使用 TiCDC 将 TiDB 的数据同步到 Apache Kafka

TiDB数据库从入门到精通系列之六&#xff1a;使用 TiCDC 将 TiDB 的数据同步到 Apache Kafka 一、技术流程二、搭建环境三、创建Kafka changefeed四、写入数据以产生变更日志五、配置 Flink 消费 Kafka 数据 一、技术流程 快速搭建 TiCDC 集群、Kafka 集群和 Flink 集群创建 c…...

Spring对象装配

在spring中&#xff0c;Bean的执行流程为启动spring容器&#xff0c;实例化bean&#xff0c;将bean注册到spring容器中&#xff0c;将bean装配到需要的类中。 既然我们需要将bea装配到需要的类中&#xff0c;那么如何实现呢&#xff1f;这篇文章&#xff0c;将来阐述一下如何实…...

bigemap如何添加mapbox地图?

第一步 打开浏览器&#xff0c;找到你要访问的地图的URL地址&#xff0c;并且确认可以正常在浏览器中访问&#xff1b;浏览器中不能访问&#xff0c;同样也不能在软件中访问。 以下为常用地图源地址&#xff1a; 天地图&#xff1a; http://map.tianditu.gov.cn 包含&…...

python爬虫6:lxml库

python爬虫6&#xff1a;lxml库 前言 ​ python实现网络爬虫非常简单&#xff0c;只需要掌握一定的基础知识和一定的库使用技巧即可。本系列目标旨在梳理相关知识点&#xff0c;方便以后复习。 申明 ​ 本系列所涉及的代码仅用于个人研究与讨论&#xff0c;并不会对网站产生不好…...

Linux查找命令

find find /dir -name filename 按名字查找 find . -name “*.c” 将当前目录及其子目录下所有文件后缀为 .c 的文件列出来 find . -type f 将当前目录及其子目录中的所有文件列出 find . -ctime 20 将当前目录及其子目录下所有最近 20 天内更新过的文件列出 find / -type f -…...

在 IntelliJ IDEA 中使用 Docker 开发指南

目录 一、IDEA安装Docker插件 二、IDEA连接Docker 1、Docker for Windows 连接 2、SSH 连接 3、Connection successful 连接成功 三、查看Docker面板 四、使用插件生成镜像 一、IDEA安装Docker插件 打开 IntelliJ IDEA&#xff0c;点击菜单栏中的 "File" -&g…...

【并发编程】自研数据同步工具的优化:创建线程池多线程异步去分页调用其他服务接口获取海量数据

文章目录 场景&#xff1a;解决方案 场景&#xff1a; 前段时间在做一个数据同步工具&#xff0c;其中一个服务的任务是调用A服务的接口&#xff0c;将数据库中指定数据请求过来&#xff0c;交给kafka去判断哪些数据是需要新增&#xff0c;哪些数据是需要修改的。 刚开始的设…...

python函数、运算符等简单介绍3(无顺序)

set&#xff08;集合&#xff09; 集合(set) -> 负责存储【不重复的数据】&#xff0c;并且是【无序存储】 的容器&#xff0c;主要用来去重和逻辑比较 set1 {1,2,3,4,58,7,4,1,2,3,5} print(set1) print(type(set1)) # 输出结果&#xff1a; {1, 2, 3, 4, 5, 7, 58} <…...

TCP服务器(套接字通信)

服务器 客户端 结果...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机

这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机&#xff0c;因为在使用过程中发现 Airsim 对外部监控相机的描述模糊&#xff0c;而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置&#xff0c;最后在源码示例中找到了&#xff0c;所以感…...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时&#xff0c;显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...