C++socket网络编程——udp服务器

C++socket网络编程——udp服务器

目录

一.端口号 VS  PID

二.套接字编程的类型

三.socket编程接口

四.基于udp的服务端和客户端全部代码

客户端

服务端

五.解释与运行

一些细节:

六.总结


一.端口号 VS  PID

pid已经能够标识一台主机上的一个唯一一个进程了,为什么还需要端口号?

  1. 不是所有的进程都需要网络通信,但是所有的进程都需要都pid;
  2. 系统和网络功能解耦。

        另外,一个进程可以绑定多个端口,但一个端口只能被一个进程绑定。

        系统内定的端口号【0,1023】一般都要有固定的应用层协议使用,如http:80,https:443。

二.套接字编程的类型

  1. 域间套接字编程——同一个机器内
  2. 原始套接字编程——网络工具
  3. 网络套接字编程——用户间的网络通信

        不同的套接字编程类型的接口需要是相同的。

        如同用一个统一的sockaddr作为基类,sockaddr_in和sockaddr_un作为子类。使用的时候我们用if语句区分具体是什么类型,如同下面这段代码:

if(address->type == AF_INET) { // 网络套接字 } else if(address->type == AF_UNIX) { // 域间套接字 }

        为什么操作系统接口设计者在设计的时候不直接使用void*呢,因为那时候C语言还没有void*

三.socket编程接口

// 创建 socket 文件描述符(TCP/UDP,客户端 + 服务器) int socket(int domain, int type, int protocol); // 绑定端口号(TCP/UDP,服务器) int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 开始监听socket(TCP,服务器) int listen(int sockfd, int backlog); // 接收请求(TCP,服务器) int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // 建立连接(TCP,客户端) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 

四.基于udp的服务端和客户端全部代码

客户端

代码如下:

client.cc:

#include <iostream> #include <sys/types.h> #include <sys/socket.h> // #include <stdio.h> #include <unistd.h> #include <strings.h> #include <arpa/inet.h> // sockaddr_in using namespace std; void usage(const std::string& proc) { cout << "Please use like this : " + proc + " [ip] [port]" << endl; } int main(int argc, char* argv[]) { if(argc != 3) { usage(argv[0]); exit(1); } string serverip = argv[1]; uint16_t serverport = stoi(argv[2]); int sockFd = socket(AF_INET, SOCK_DGRAM, 0); if(sockFd < 0) cerr << "sockFd error" << endl; struct sockaddr_in server; socklen_t sz = sizeof(server); bzero(&server, sz); server.sin_family = AF_INET; server.sin_addr.s_addr = inet_addr(serverip.c_str()); server.sin_port = htons(serverport); std::string temp; while(true) { cout << "请输入消息@ "; getline(cin, temp); int ret = sendto(sockFd, temp.c_str(), temp.size(), 0, (struct sockaddr*)&server, sz); if(ret < 0) std::cerr << "send error" << std::endl; char buffer[1024]; struct sockaddr_in retServer; socklen_t len = sizeof(retServer); int n = recvfrom(sockFd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&retServer, &len); if(n > 0) { buffer[n] = 0; cout << "收到消息:" << buffer << endl; } } close(sockFd); return 0; } 

服务端

代码如下:

UdpServer.hpp:

#include <string> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <unistd.h> // #include <netinet/in.h> #include <arpa/inet.h> // sockaddr_in #include <iostream> #include <vector> #include <functional> using func_t = std::function<std::string(const std::string&)>; const std::string defaultip = "0.0.0.0"; const uint16_t defaultport = 8080; class UdpServer { public: UdpServer():_sockFd(-1) {} ~UdpServer() { if(_sockFd >= 0) close(_sockFd); } void init(const std::string& ip = defaultip, uint16_t port = defaultport) { _sockFd = socket(AF_INET, SOCK_DGRAM, 0); if(_sockFd < 0) std::cerr << "创建套接字失败" << std::endl; struct sockaddr_in local; bzero(&local, sizeof(local)); local.sin_family = AF_INET; local.sin_addr.s_addr = inet_addr(ip.c_str()); local.sin_port = htons(port); int ret = bind(_sockFd, (const struct sockaddr*)&local, sizeof(local)); if(ret < 0) std::cerr << "绑定本地套接字信息失败" << std::endl; } void run(func_t func) { while(true) { struct sockaddr_in client; socklen_t sz = sizeof(client); char buffer[1024]; int n = recvfrom(_sockFd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&client, &sz); buffer[n] = 0; std::string clientip = inet_ntoa(client.sin_addr); uint16_t clientport = ntohs(client.sin_port); std::cout << "[" << clientip + ":" << clientport << "] send information : " << buffer << std::endl; std::string temp = func(buffer); int ret = sendto(_sockFd, temp.c_str(), temp.size(), 0, (const struct sockaddr*)&client, sz); if(ret < 0) std::cerr << "send error" << std::endl; } } private: int _sockFd; }; 

main.cc:

#include "UdpServer.hpp" #include <memory> #include <string> #include <string.h> std::string echo(const std::string& cData) { return "server : " + cData; } std::string command(const std::string& cData) { std::string ret; FILE* fp = popen(cData.c_str(), "r"); if(fp == nullptr) { perror("popen"); exit(1); } while(true) { char buffer[1024]; memset(buffer, sizeof(buffer), 0); char* ok = fgets(buffer, sizeof(buffer) - 1, fp); if(ok == nullptr) break; ret += buffer; } pclose(fp); return ret; } int main() { std::unique_ptr<UdpServer> udpServer(new UdpServer()); udpServer->init(); udpServer->run(command); return 0; } 

makefile:

.PHONY:all all:UdpServer client UdpServer:main.cc g++ -o $@ $^ -std=c++11 client:client.cc g++ -o $@ $^ -std=c++11 -lpthread .PHONY:clean clean: rm -f UdpServer client

五.解释与运行

  • 客户端:接收用户输入的字符串(可以是普通消息或系统命令),通过 UDP 发送给指定 IP 和端口的服务端,然后等待并打印服务端的响应。
  • 服务端:以类的形式封装(UdpServer),监听指定端口,接收客户端的消息后,通过回调函数处理(默认是执行客户端发送的系统命令并返回执行结果,也可切换为简单回显),再将处理结果通过 UDP 返回给客户端。

运行结果:

        这样,我们就可以远程输入命令了,xshell远程连接服务器就是这样类似的原理。

一些细节:

        AF_INET指明使用ipv4协议,sock_DGRAM指明面向数据报的udp协议,第2个参数协议类型不用管。

        绑定之前将自己的信息填入sockaddr_in这个数据结构中。AF_INET表明是网络socket编程,s_addr表明服务端的ip地址,调用inet _addr函数将它转换成32位数字并且转换为网络字节序列,也就是大端。htons也就是将short类型数据转换为网络字节序列,也就是大端。

        倒数第一个参数len 既是输入型参数,也是输出型参数;倒数第二个参数是输出型参数。

        网络转成主机的字节序,并且32位数字的ip转换为点分十进制字符串形式。

六.总结

        我们还可以进行扩展。将这个服务弄成多人聊天。服务端用一个哈希和客户端的ip地址唯一标识一个客户端,管理起来之后就可以对每一条消息进行广播给每一个客户端了,客户端是多线程的,一个线程负责发送消息,一个线程负责接收消息,为了发送和接收不在一个终端进行,我们可以将接收线程里的标准输出或者标准错误重定向到/dev/pts/某某序号的这个文件夹,用dup2这个函数就可以做到,然后我们客户端就可以在一个终端发送消息,一个终端接收消息。

Read more

《算法闯关指南:优选算法--位运算》--38.消失的两个数字

《算法闯关指南:优选算法--位运算》--38.消失的两个数字

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 38. 消失的两个数字 * 解法(位运算): * 算法思路: * C++算法代码: * 代码一:使用了提取最右边的1 * 代码二:循环找出找出 a,b 中比特位不同的那⼀位 * 算法总结&&笔记展示: * 结语: 前言: 聚焦算法题实战,系统讲解三大核心板块:优选算法:剖析动态规划、二分法等高效策略,学会寻找“最优解”。 递归与回溯:掌握问题分解与状态回退,攻克组合、排列等难题。 贪心算法:理解“局部最优”到“全局最优”

By Ne0inhk
通俗易懂->哈希表详解

通俗易懂->哈希表详解

目录 一、什么是哈希表? 1.1哈希表长什么样? 1.2为什么会有哈希表? 1.3哈希表的特点 1.3.1 取余法、线性探测 1.3.2 映射 1.3.3负载因子 1.4哈希桶 1.5闲散列与开散列 1.6总结 二、设计hash表 1、哈希表的设计   1)插入   2)查找  3)删除 4)字符串哈希算法 2、封装map和set 1、完成对hash表的基础功能 2、完成封装 3、对应的迭代器 4、【】方括号重载 三、

By Ne0inhk
《数据结构风云》:二叉树遍历的底层思维>递归与迭代的双重视角

《数据结构风云》:二叉树遍历的底层思维>递归与迭代的双重视角

🔥@晨非辰Tong: 个人主页 👀专栏:《C语言》、《数据结构与算法入门指南》 💪学习阶段:C语言、数据结构与算法初学者 ⏳“人理解迭代,神理解递归。” 文章目录 * 引言 * 知识点前瞻 * 一、不一样的前序遍历 * 1.`要求描述:` * 2.`实现示例:` * 3.`算法思路:` * 3.1 `具体代码实现` * 3.2 **==注意要点==** * 二、不一样的中序遍历 * 1.`要求描述:` * 2.`实现示例` * 3.`算法思路:` * 3.1 `具体代码实现:` * 三、不一样的后序遍历 * 1.`要求描述:` * 2.`实现示例:` * 3.`算法思路:` * 3.1 `具体代码实现:` * 四、

By Ne0inhk
数据结构 | 队列:从概念到实战

数据结构 | 队列:从概念到实战

个人主页-爱因斯晨 文章专栏-数据结构 继续加油! 文章目录 * 个人主页-爱因斯晨 * 文章专栏-数据结构 * 一、队列的基本概念 * 二、队列的核心操作 * 三、C 语言实现队列 * 3.1 顺序队列(数组实现) * 3.2 链式队列(链表实现) * 四、队列的应用场景 * 五、两种实现的对比选择 一、队列的基本概念 队列是一种先进先出(FIFO,First In First Out) 的线性数据结构,仅允许在一端进行插入操作(队尾),另一端进行删除操作(队头)。 生活中的队列场景: * 银行窗口排队办理业务 * 打印机任务队列 * 消息队列中的消息传递 二、队列的核心操作 1. 初始化(InitQueue):创建一个空队列 2. 入队(

By Ne0inhk