Linux -- socket 编程

目录

[socket 函数(创建套接字)](#socket 函数(创建套接字))

[bind 函数(绑定套接字和本地地址)](#bind 函数(绑定套接字和本地地址))

[recvfrom 函数(接收数据报)](#recvfrom 函数(接收数据报))

[sendto 函数(发送数据报)](#sendto 函数(发送数据报))

主要代码

[Log.hpp / LockGuard.hpp](#Log.hpp / LockGuard.hpp)

[makefile(一次生成 2 个可执行程序)](#makefile(一次生成 2 个可执行程序))

InetAddr.hpp

UdpServer.hpp

Main.cc

UdpClient.cc

运行结果(本地测试):

客户端

服务端

​编辑


socket 函数(创建套接字)

网络编程中用于创建套接字(socket)的一个关键系统调用。它返回一个描述符,这个描述符可以用来识别新创建的套接字,并且在后续的操作中使用,比如绑定地址、监听连接请求、接受连接、发送和接收数据等。

cpp 复制代码
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

domain:指定通信域,也称为协议族(protocol family),指定了所使用的寻址方案。常见的值有:

  • AF_INETPF_INET:IPv4互联网协议。

  • AF_INET6PF_INET6:IPv6互联网协议。

  • AF_UNIXPF_UNIX:本地通信(同一主机内的进程间通信)。
    type指定套接字的类型,决定了通信语义。主要的套接字类型包括:

  • SOCK_STREAM:提供面向连接的、可靠的、双向传输服务,通常对应于TCP协议。

  • SOCK_DGRAM:提供无连接的、不可靠的数据报文服务,通常对应于UDP协议。

  • SOCK_RAW:提供对低层协议的直接访问,如ICMP或IGMP
    protocol指定具体的协议通常设置为0,表示使用默认协议

例如,对于SOCK_STREAM类型的套接字,默认协议是TCP;对于SOCK_DGRAM类型的套接字,默认协议是UDP。

如果需要明确指定某个协议,可以使用IPPROTO_TCPIPPROTO_UDP等常量。

  • 函数成功时返回一个新的文件描述符,该描述符代表了新创建的套接字;
  • 如果发生错误,则返回**-1**,并将errno设置为适当的错误码。

bind 函数(绑定套接字和本地地址)

用于将特定的本地地址(包括IP地址和端口号)分配给一个未绑定的套接字。这一步骤对于服务器应用程序尤其重要,因为它们需要监听某个特定的端口以接收来自客户端的连接请求或数据报文。

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:这是通过 socket() 系统调用创建的套接字描述符
addr:指向包含要绑定到套接字上的地址信息的结构体的指针。这个结构体通常是一个**struct sockaddr_in或者 struct sockaddr_in6(对于IPv6),但在传递给 bind() 时必须被强制转换为 struct sockaddr * 类型**。
addrlen:表示 addr 参数所指向的地址结构体的大小,通常是**sizeof(struct sockaddr_in)**或 sizeof(struct sockaddr_in6)
返回值

  • 如果成功bind() 返回 0
  • 如果失败,返回 -1,并设置全局变量 errno 来指示错误的原因。若要绑定的端口已经被其他服务占用,bind() 调用将会失败。

recvfrom 函数(接收数据报)

用于接收来自无连接套接字(例如UDP)的数据报文的系统调用。它不仅可以接收数据,还可以获取发送方的地址信息

cpp 复制代码
#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:指定 buf 缓冲区的最大长度
flags:提供额外的选项来影响接收操作的行为。常见的标志包括:

  • MSG_PEEK:窥探消息,即读取数据但不将其从输入队列中移除。

  • MSG_WAITALL:等待直到读取到请求的所有数据。

  • MSG_DONTWAIT:非阻塞操作;如果数据不可用,则立即返回。
    src_addr:指向一个 struct sockaddr 结构体的指针,该结构体将被填充以包含发送方的地址信息 。如果你不需要发送方的地址信息,可以传递 NULL。是输出型参数
    addrlen:这是一个指向 socklen_t 类型变量的指针,初始时应设置为 src_addr 的大小。函数执行后,它会被更新为实际填充到 src_addr 中的地址长度。是输出型参数
    返回值

  • 如果成功recvfrom() 返回接收到的字节数

  • 如果到达文件末尾(仅适用于某些类型的套接字),则返回0。

  • 如果发生错误,则返回 -1,并设置全局变量 errno 以指示错误的原因。

sendto 函数(发送数据报)

用于向无连接套接字(例如UDP)发送数据报文的系统调用。

cpp 复制代码
#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:指定 buf 中数据的长度(以字节为单位)。
flags:提供额外的选项来影响发送操作的行为。常见的标志包括:

  • MSG_CONFIRM:请求确认路径MTU是否仍然有效。

  • MSG_DONTROUTE:跳过路由表,直接将数据发送到目标网络接口。

  • MSG_DONTWAIT:使调用变为非阻塞模式。

  • MSG_EOR:指示消息的结束(仅对某些协议有意义)。

  • MSG_NOSIGNAL:抑制SIGPIPE信号(当写入一个已关闭写入的管道或套接字时产生的信号)。
    dest_addr:指向 struct sockaddr 结构体的指针,该结构体包含了接收方的地址信息。如果你之前已经通过 connect() 将套接字连接到了某个地址,可以传递 NULL 和0作为 dest_addraddrlen
    addrlen:指定 dest_addr 的大小,通常是 sizeof(struct sockaddr_in)sizeof(struct sockaddr_in6)
    返回值

  • 如果成功sendto() 返回实际发送的字节数

  • 如果发生错误,则返回 -1,并设置全局变量 errno 以指示错误的原因。

主要代码

Log.hpp / LockGuard.hpp

Linux -- 线程池-CSDN博客https://blog.csdn.net/2301_76973016/article/details/144820263?spm=1001.2014.3001.5501

makefile(一次生成 2 个可执行程序)

cpp 复制代码
.PHONY:all
all:udpserver udpclient


udpserver:Main.cc
	g++ -o $@ $^ -std=c++14
udpclient:UdpClient.cc
	g++ -o $@ $^ -std=c++14

.PHONY:clean
clean:
	rm -f udpclient udpserver

InetAddr.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// 从结构体中获得IP和端口号
class InetAddr
{
private:
    void GetAddress(std::string *ip, uint16_t *port)
    {
        *port = ntohs(_addr.sin_port);
        *ip = inet_ntoa(_addr.sin_addr);
    }

public:
    InetAddr(const struct sockaddr_in &addr)
        : _addr(addr)
    {
        GetAddress(&_ip, &_port);
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    ~InetAddr()
    {
    }

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

UdpServer.hpp

创建套接字、填充结构体 并 调用 bind 函数,这部分的代码是常见的写法套路,需要熟练掌握。

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "InetAddr.hpp"
#include "Log.hpp"

enum
{
    SOCKET_ERROR = 1, // 套接字创建失败
    BIND_ERROR,       // 绑定失败
    USAGE_ERROR       // 用法错误
};

const static int defaultfd = -1; // 默认描述符的值为-1
class UdpServer
{
public:
    // 外界只需要传端口号
    UdpServer(uint16_t port)
        : _port(port), _sockfd(defaultfd), _isrunning(false)
    {
    }
    ~UdpServer()
    {
    }
    void InitServer()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);

        if (_sockfd < 0) // 创建失败
        {
            LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);

        // 填 sockaddr_in
        struct sockaddr_in local;     // 在栈上开辟空间
        bzero(&local, sizeof(local)); // 将内存设置为全0

        local.sin_family = AF_INET;         // 协议族(用哪个协议)
        local.sin_port = htons(_port);      // 端口号,转为网络序列(大端)
        local.sin_addr.s_addr = INADDR_ANY; // IP地址

        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        // 绑定失败
        if (n < 0)
        {
            LOG(FATAL, "bind error,%s,%d\n", strerror(errno), errno);
            exit(BIND_ERROR);
        }
    }

    // 启动服务器
    void Start()
    {
        _isrunning = true;
        while (true)
        {
            char buffer[1024];
            struct sockaddr_in peer;//发送方的地址
            socklen_t len = sizeof(peer);

            // 接收数据报,返回值为接收到的字节数
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buffer[n] = 0;
                InetAddr addr(peer);//发送方的IP和端口号
                LOG(DEBUG, "get message from [%s:%d]:%s\n", addr.Ip().c_str(), addr.Port(), buffer);

                // 发送数据报,将我们收到的数据报发回给对方
                sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
            }
            _isrunning = false;
        }
    }

private:
    int _sockfd;

    uint16_t _port;
    bool _isrunning;
};

Main.cc

cpp 复制代码
#include<iostream>
#include<memory>
#include"UdpServer.hpp"

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

int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(USAGE_ERROR);
    }
    EnableScreen();

    uint16_t port=std::stoi(argv[1]);
    std::unique_ptr<UdpServer> usvr=std::make_unique<UdpServer>(port);
    usvr->InitServer();
    usvr->Start();
    
    return 0;

}

UdpClient.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip=argv[1];//获得IP
    uint16_t serverport=std::stoi(argv[2]);;//获得端口号

    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        std::cerr<<"socket error"<<std::endl;
    }

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

    //客户端不需要调用bind函数
    std::string message;//消息
    while(true)
    {
        std::cout<<"Please Enter# ";
        std::getline(std::cin,message);//输入消息

        //发送数据报
        sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));

        struct sockaddr_in peer;
        socklen_t len=sizeof(peer);
        char buffer[1024];

        //接收数据报
        ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
        if(n>0)
        {
            buffer[n]=0;
            std::cout<<"server echo# "<<buffer<<std::endl;
        }

    }
    return 0;
}

运行结果(本地测试):

客户端

服务端

相关推荐
孙克旭_1 小时前
PXE_Kickstart_无人值守自动化安装系统
linux·运维·自动化
皓月盈江2 小时前
Linux电脑本机使用小皮面板集成环境开发调试WEB项目
linux·php·web开发·phpstudy·小皮面板·集成环境·www.xp.cn
深井冰水2 小时前
mac M2能安装的虚拟机和linux系统系统
linux·macos
leoufung2 小时前
内核内存锁定机制与用户空间内存锁定的交互分析
linux·kernel
菜菜why3 小时前
AutoDL租用服务器教程
服务器
IT专业服务商3 小时前
联想 SR550 服务器,配置 RAID 5教程!
运维·服务器·windows·microsoft·硬件架构
忧虑的乌龟蛋4 小时前
嵌入式Linux I2C驱动开发详解
linux·驱动开发·嵌入式·iic·i2c·读数据·写数据
I_Scholar5 小时前
OPENSSL-1.1.1的使用及注意事项
linux·ssl
Johny_Zhao5 小时前
K8S+nginx+MYSQL+TOMCAT高可用架构企业自建网站
linux·网络·mysql·nginx·网络安全·信息安全·tomcat·云计算·shell·yum源·系统运维·itsm
稳联技术5 小时前
Ethercat转Profinet网关如何用“协议翻译术“打通自动化产线任督二脉
linux·服务器·网络