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

目录

[一.端口号 VS PID](#一.端口号 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语句区分具体是什么类型,如同下面这段代码:

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

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

三.socket编程接口

cpp 复制代码
// 创建 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:

cpp 复制代码
#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:

cpp 复制代码
#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:

cpp 复制代码
#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:

bash 复制代码
.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这个函数就可以做到,然后我们客户端就可以在一个终端发送消息,一个终端接收消息。

相关推荐
安科士andxe6 小时前
深入解析|安科士1.25G CWDM SFP光模块核心技术,破解中长距离传输痛点
服务器·网络·5g
寻寻觅觅☆9 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
YJlio9 小时前
1.7 通过 Sysinternals Live 在线运行工具:不下载也能用的“云端工具箱”
c语言·网络·python·数码相机·ios·django·iphone
fpcc9 小时前
并行编程实战——CUDA编程的Parallel Task类型
c++·cuda
CTRA王大大9 小时前
【网络】FRP实战之frpc全套配置 - fnos飞牛os内网穿透(全网最通俗易懂)
网络
testpassportcn10 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
ceclar12310 小时前
C++使用format
开发语言·c++·算法
通信大师11 小时前
深度解析PCC策略计费控制:核心网产品与应用价值
运维·服务器·网络·5g
lanhuazui1011 小时前
C++ 中什么时候用::(作用域解析运算符)
c++
charlee4411 小时前
从零实现一个生产级 RAG 语义搜索系统:C++ + ONNX + FAISS 实战
c++·faiss·onnx·rag·语义搜索