html5 爱情网站模板,电影网站怎么做不犯法,网站建设 域名 管理,做一个网站 如何盈利模式网络套接字 文章目录 网络套接字认识端口号初识TCP协议初识UDP协议网络字节序 socket编程接口socket创建socket文件描述符bind绑定端口号sockaddr结构体netstat -nuap#xff1a;查看服务器网络信息 代码编译运行展示 实现简单UDP服务器开发 认识端口号 端口号(port)是传输层协…网络套接字 文章目录 网络套接字认识端口号初识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)有一个直观的认识它有以下性质
传输层协议无连接不可靠传输面向数据报
网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢
首先先回顾一下大小端 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址TCP/IP协议规定,网络数据流应采用大端字节序,即低地址存放高字节高地址存放低字节不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可
为了使得网络程序具备可移植性使得同样的代码在大端和小端计算机上编译后都能运行可以调用以下库函数使得网络字节序和主机字节序的相互转换。 #include arpa/inet.huint32_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结构体 很多网络编程函数诞生早于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_tsin_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地址
需要注意的是
对于虚拟机用户来说填入sackaddr_in结构体中的ip地址不能填入公网ip即连接上虚拟机使用的公网ip。但可以填入内网ip
netstat -nuap查看服务器网络信息 根据上面的接口可以写出server端的部分代码
netstat -nupa server.c 1 #includeserver.hpp 2 #includememory3 4 using namespace std;5 using namespace udpServer ;6 static void Usage(string proc)7 {8 cerrUsage: \n\tproc serverportendl;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 portatoi(argv[1]);//atoi作用把port的str类型转化成int类型。而uin16_t本质类型是int19 20 unique_ptrudpserver uspr(new udpserver(port));21 uspr-initudpserver();//初始化服务端22 uspr-start();//启动服务端23 return 0;24 }server.hpp 1 #pragma once 2 #includeiostream3 #include sys/types.h4 #include sys/socket.h5 #include netinet/in.h6 #include arpa/inet.h7 #include errno.h8 #include strings.h9 #include stdlib.h10 #include string.h11 #includeunistd.h12 using namespace std;13 14 namespace udpServer15 {16 static const string defaultIP0.0.0.0;//默认IP地址17 static int SIZE1024;18 enum {USAGE_ERR1,SOCKET_ERR,BIND_ERR,OPEN_ERR};//用枚举函数定义各自错误的返回值19 class udpserver20 {21 public:22 23 udpserver(const uint16_t port,const string ipdefaultIP)24 :_port(port)25 ,_ip(ip)26 ,_sockfd(-1)27 {}28
29 void initudpserver()30 {31 //1.创建套接字32 _sockfdsocket(AF_INET,SOCK_DGRAM,0);//创建套接字文件描述符33 if(_sockfd-1)34 {35 cerrsocket errerrno : strerror(errno)endl;36 exit(SOCKET_ERR);37 }38 coutsocket success: _sockfdendl;39 //2.绑定port端口号40 //2.1将port和ip填入结构体中该结构体可以理解成用户定义的数据或用户栈41 struct sockaddr_in local;//创建结构42 bzero(local,sizeof(local));//将结构清零43 local.sin_familyAF_INET;//填充协议家族44 local.sin_porthtons(_port);//填充端口号。htons将port主机序列转化为网络序列45 //local.sin_addr.s_addrinet_addr(_ip.c_str());46 //填充ip地址。inet_addr函数作用将ip地址的string类型转化为uint32_t其次ip地址的将主机序列转化为网络序 列47 local.sin_addr.s_addrhtons(INADDR_ANY);//不绑定指定ip可以接收任何传达到指定端口号的ip主机发的数据48 //2.2将sockaddr_in与套接字进行绑定 49 int nbind(_sockfd,(struct sockaddr*)local,sizeof(local));50 if(n-1)51 {52 cerrbind errerrno : 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 lensizeof(out);64 ssize_t srecvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(sockaddr*)out,len);65 if(s0)66 {67 buffer[s]0;68 string clientipinet_ntoa(out.sin_addr);//网络序列转换为主机序列uin32t_t-string69 uint16_t clientportntohs(out.sin_port);//网络序列转化为主机序列70 string messagebuffer;71 coutclientip[clientport]# messageendl;72 }73 }74 }75 76 ~udpserver()77 {}78 79 private:80 uint16_t _port;//端口号81 string _ip;//ip地址82 int _sockfd;//套接字文件描述符83 };84 }根据server.hpp的47行可知server端服务端不能绑定指定ip地址绑定ip地址意味着只接收指定ip地址和端口号的主机发来的报文而不绑定ip地址可以接收任意ip地址的主机只需绑定指定端口号即发送报文給服务端。因此在server.cc只需要传参端口号port server.hpp中start启动函数内是个死循环即服务器本质是一个死循环这个进程被称为常驻内存进程sockaddr_in类型的结构的ip地址INADDR_ANY就是全0即接收任意绑定了对应端口的进程发送来的数据。 server.hpp的42行的bzero函数用于将local结构体清零
bzero函数原型 #include strings.hvoid bzero(void *s, size_t n);将参数s 所指的内存区域前n 个字节全部设为零
第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结构体大小这里要传参地址
第68行inet_ntoa函数用于将网络字节序的IP地址转换为点分十进制的字符串形式。一是将uint32_t类型转化为string类型二是将网络序列转化为主机序列
函数原型
#include arpa/inet.h
char *inet_ntoa(struct in_addr in);in_addr表示网络字节序的IPv4地址的结构体
需要注意的是
云服务器的公网ip不能绑定由于该服务器是虚拟服务器因此公网ip也是虚拟ip但内网ip127.0.0.1可以绑定。而服务端绑定127.0.0.1客户端接收数据即数据完成了本地环回。在这个过程代码数据的传输不会到达物理层。 netstat -nuap查看服务器网络信息 client.cc #includememory
#includeclient.hpp
using namespace udpClient;
static void startrouine(string proc)
{cout\nstartroutine\n\t procserverip serverportendl;
}
int main(int argc,char* argv[])
{if(argc!3){startrouine(argv[0]);exit(1);}string serveripargv[1];uint16_t serverportatoi(argv[2]);//atoi作用把port的str类型转化成int类型。而uin16_t本质类型是intunique_ptrudpclient uct(new udpclient(serverip,serverport));
uct-initclient();
uct-run();return 0;
}client.hpp 1 #pragma once 2 #include iostream3 #include string4 #include strings.h5 #include cerrno6 #include cstring7 #include cstdlib8 #include unistd.h9 #include sys/types.h10 #include sys/socket.h11 #include arpa/inet.h12 #include netinet/in.h13 #include pthread.h14 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 _sockfdsocket(AF_INET,SOCK_DGRAM,0);30 if(_sockfd-1)31 {32 coutsocket errorerrno:strerror(errno)endl;33 exit(2);34 }35 coutsocket success: _sockfdendl;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_familyAF_INET;44 server.sin_addr.s_addrinet_addr(_serverip.c_str());//主机序列转换为网络序列string-uin32t_t45 server.sin_porthtons(_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 messagecmdbuffer;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 } client.cc中atoi函数用于将我们命令行输入的端口号string类型转化成int类型而uin16_t本质类型是intclient.hpp56行sendto函数是在Linux系统下用于发送数据报的函数。它可以向指定的socket文件描述符中发送数据报适用于面向无连接的UDP套接字或以connectionless模式工作的AF_INET套接字。可以看到在client端无需bind指定端口号和ip地址。一个端口只能被一个进程绑定若客户端绑定指定端口其他客户端就不能往该端口中发数据了。因此客户端不需要自主绑定OS会自动绑定。在第一次发送数据时OS会帮我们绑定ip和端口即使用sendto函数时。而OS帮我们绑定端口是随机的。
sendto函数原型 #include sys/types.h#include sys/socket.hssize_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结构体的大小
代码编译运行展示 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 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.hssize_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 $ $^ -stdc11 -pthreadudpServer:udpServer.ccg -o $ $^ -stdc11 -pthread.PHONY:clean
clean:rm -rf udpClient udpServerudpClient.cc #include udpClient.hpp
#include memoryusing 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_ptrudpClient 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.hnamespace 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_castint *(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 ClientClient端的父进程进行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.hnamespace 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 functionvoid (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. 绑定portip(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.husing namespace std;
using namespace Server;const std::string dictTxt./dict.txt;
unordered_mapstring, 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_ptrudpServer usvr(new udpServer(handlerMessage, port));// std::unique_ptrudpServer usvr(new udpServer(execCommand, port));std::unique_ptrudpServer 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_ptrudpServer 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文件即热加载 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_ptrudpServer usvr(new udpServer(execCommand, port));usvr-initServer();usvr-start();return 0;
}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 函数来关闭子进程并释放资源 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.husing 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_mapstring, 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_ptrudpServer usvr(new udpServer(routeMessage, port));usvr-initServer();usvr-start();return 0;
}只需要打开一个服务器server端然后打开两个客户端但要求各自新建命名管道fifo将客户端接收到的信息通过命名管道读出来