[linux网络编程]UDP协议和TCP协议的使用

目录

看以下内容前,你要先了解main函数带参数有什么用、

了解socket的相关函数接口

 如果不了解socket的相关函数接口请先看我这篇文章

main函数带参数有什么用

UDP

udp_server

1.生成socket文件描述符

2.填充sockaddr_in信息

3.bind

4.发(收)消息

5.关闭socket文件描述符

udp_client

1.生成socket文件描述符

2.填充sockaddr_in信息

3.客户端不用手动bind

4.发(收)消息 (client会在首次发送数据的时候会自动进行bind)

5.关闭socket文件描述符

 完整代码

Makefile

udp_server.hpp

udp_server.cc

udp_client.cc

TCP

tcp_server

1.生成socket文件描述符

2.填充sockaddr_in信息

3.bind

4.监听与获取连接

5.发(收)消息

6.关闭socket文件描述符

tcp_client

1.生成socket文件描述符

2.填充sockaddr_in信息

3.客户端不用手动bind

4.连接服务器(server)

5.发(收)消息

6.关闭socket文件描述符

完整代码

Makefile

tcp_server.hpp

tcp_server.cc

tcp_client.cc

代码运行结果视频展示


看以下内容前,你要先了解main函数带参数有什么用、

了解socket的相关函数接口

 如果不了解socket的相关函数接口请先看我这篇文章

[网络编程]socket嵌套字的一些常用接口-CSDN博客

main函数带参数有什么用

我就给一下例子知道怎么用就行

例如:

在服务端

意思就是启动服务器,端口号为8888,这里在服务端我们没有设置ip, 只要端口号正确,任意IP都能和我们的服务器连接

那么

argc == 2

argv[0] == ./server

argv[1] == 8888

在客户端

 

 意思是启动客户端,连接ip地址为127.0.0.1,端口号为8888的服务器

 那么

argc == 3

argv[0] == ./client

argv[1] == 127.0.0.1

argv[2] == 8888

UDP

udp_server

1.生成socket文件描述符

        //创建socket
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            std::cerr << "creater socket fail!!" << std::endl;
            return false;
        }

socket函数参数说明:

  • AF_INET:仍然指定使用IPv4地址族。
  • SOCK_DGRAM:指定创建的是一个UDP socket。
  • 0:同样,这里让系统自动选择默认的协议。

2.填充sockaddr_in信息

//填充sockaddr_in
//定义sockaddr_in结构体变量---1
struct sockaddr_in saddr;
//初始化server结构体---2
memset(&saddr, 0, sizeof(saddr));
//设置地址族---3
saddr.sin_family = AF_INET;//协议家族
//设置端口号---4
saddr.sin_port = htons(_port);//主机字节序转网络字节序
//inet_pton(AF_INET, _ip.c_str(), &(saddr.sin_addr));//点分十进制转网络字节序
//设置IP地址---5
saddr.sin_addr.s_addr = INADDR_ANY;

 解释:

  1. 这行代码定义了一个sockaddr_in类型的变量saddrsockaddr_in是用于IPv4的套接字地址结构。
  2. 使用memset函数将saddr结构体的所有字节都设置为0。这是为了确保结构体中的所有未明确设置的字段都从一个已知的、安全的初始值开始。
  3. saddr结构体的sin_family字段设置为AF_INET,表示这是一个IPv4地址。
  4. sin_port字段用于存储端口号。htons函数用于将主机字节序的端口号转换为网络字节序。_port是一个之前定义的变量,它包含了要设置的端口号。
  5. 这里将sin_addr字段的s_addr成员设置为INADDR_ANYINADDR_ANY是一个特殊的常量,用于表示套接字应该绑定到所有可用的网络接口,通常用于服务器程序,以便它们可以接受来自任何IP地址的连接。

3.bind

//bind
int n = ::bind(_sockfd, CONV(&saddr), sizeof(saddr));
if(n < 0)
{
    std::cerr << "bind fail!" << std::endl;
}
  • ::bind:这是bind函数的全局作用域解析运算符形式。这通常用于避免与局部作用域或类作用域中的bind函数或变量名冲突。
  • _sockfd:这是一个之前已经创建并初始化的套接字文件描述符。
  • CONV(&saddr):这里CONV可能是一个宏函数,用于将sockaddr_in* 转换为sockaddr*,适合bind函数期望的地址结构体的指针类型。
  • sizeof(saddr):这指定了saddr的大小,告诉bind函数要绑定多少字节的地址信息。

4.发(收)消息

//1
struct sockaddr_in peer;
//2
memset(&peer, 0, sizeof(peer)); // 初始化
socklen_t len = sizeof(peer);
// 收发消息
while (true)

    // 使用recvfrom收消息
    char buffer[1024];
    //3
    ssize_t rn = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&peer), &len);
    if (rn <= 0)
    {
        std::cerr << "receive message is empty" << std::endl;
        return false;
    }
    //输出从客户端接收到的消息
    buffer[rn] = 0; // 添加'/0'
    std::cout << "client say# " << buffer << std::endl;
    // 处理消息:在消息前加字符串"server say# "
    std::string message = "server say# ";
    message += buffer;
    //4
    // 发消息回client
    ssize_t sn = sendto(_sockfd, message.c_str(), message.size(), 0, CONV(&peer), len);
    
    if (sn <= 0)
    {
        std::cerr << " send message fail!!!" << std::endl;
        return false;
    }
}

1:

这里定义了一个 struct sockaddr_in 类型的变量 peer,用于存储客户端的地址信息。

2:

  • memset(&peer, 0, sizeof(peer));:使用 memset 函数初始化 peer 结构体,将其所有字节设置为0。这是为了避免使用未初始化的内存,并确保 peer 结构体在 recvfrom 调用前处于已知状态。
  • socklen_t len = sizeof(peer);:定义了一个 socklen_t 类型的变量 len,用于存储客户端地址结构的实际大小。这个变量将在 recvfrom 调用中更新,以反映实际接收到的地址信息的大小。

3:

  • char buffer[1024];:定义了一个字符数组 buffer,大小为1024字节,用于存储从客户端接收到的消息。
  • recvfrom 函数用于从 _sockfd 套接字接收消息。这里,sizeof(buffer) - 1 确保 buffer 数组有足够的空间来存储一个额外的空字符 '\0',以标记字符串的结束。CONV(&peer) 假设是一个宏函数,用于将 sockaddr_in 结构体转换为 sockaddr 结构体

4:

  • std::string message = "server say# ";:创建一个字符串 message,并初始化为 "server say# "
  • message += buffer;:将接收到的客户端消息追加到 message 字符串的末尾。
  • sendto 函数用于将处理后的消息发送回客户端。这里,message.c_str() 获取 message 字符串的C字符串表示,message.size() 获取字符串的长度。

5.关闭socket文件描述符

 // 关闭sockfd
close(_sockfd);

udp_client

1.生成socket文件描述符

//创建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd <  0)
{
    std::cerr << "creater socket fail!!!" << std::endl;
}

2.填充sockaddr_in信息

uint16_t port = std::stoi(argv[2]);
// 填充server的sockaddr_in信息
// 定义sockaddr_in结构体变量---1
struct sockaddr_in server;
// 初始化server结构体---2
memset(&server, 0, sizeof(server));
// 定义socklen_t变量---3
socklen_t len = sizeof(server);
// 设置地址族---4
server.sin_family = AF_INET;
// 设置端口号---5
server.sin_port = htons(port);
// 设置IP地址---6
inet_pton(AF_INET, argv[1], &server.sin_addr);

1:

这里定义了一个 sockaddr_in 类型的变量 server,用于存储服务器的地址信息。

2:

使用 memset 函数将 server 结构体的所有字节设置为0。这是为了确保结构体中的所有字段都被正确地初始化为默认值,避免使用未初始化的内存。

3:

定义了一个 socklen_t 类型的变量 len,并赋值为 server 结构体的大小。这个变量通常用于 bindconnect 等网络函数中,表示地址结构体的长度。

4:

设置 server 结构体的 sin_family 字段为 AF_INET。这表示使用的是IPv4地址族。

5:

首先,从命令行参数 argv[2] 中获取端口号,并将其转换为整数类型 uint16_t。然后,使用 htons 函数将端口号从主机字节序转换为网络字节序,并赋值给 server.sin_port

6:

使用 inet_pton 函数将点分十进制的IP地址字符串(从 argv[1] 中获取)转换为二进制形式,并存储在 server.sin_addr 中。AF_INET 参数表示正在处理的是IPv4地址。

3.客户端不用手动bind

在首次发送数据时,如果没有显式调用bind,操作系统会自动为该socket分配一个本地端口

4.发(收)消息 (client会在首次发送数据的时候会自动进行bind)

while (true)
{
    // 发信息---1
    std::string message;
    std::getline(std::cin, message);
    sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));

    // 收消息---2
    char buffer[1024];
    ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&server), &len);
    //3
    if (n > 0)
    {
        buffer[n] = 0; // 添加'/0'
        std::cout << buffer << std::endl;
    }
    else
    {
        break;
    }
}

 1:

  • std::string message;:定义一个std::string类型的变量message,用于存储用户输入的消息。
  • std::getline(std::cin, message);:从标准输入(通常是键盘)读取一行文本,并将其存储在message字符串中。
  • sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));:使用sendto函数将message发送到服务器。message.c_str()返回指向message内部字符数组的指针,message.size()返回消息的长度。CONV是一个宏函数,用于将sockaddr_in结构体转换为sockaddr结构体。sizeof(server)提供服务器地址结构的大小。

2:

  • char buffer[1024];:定义一个字符数组buffer,大小为1024字节,用于存储从服务器接收到的消息。
  • recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&server), &len);:使用recvfrom函数从服务器接收消息。sizeof(buffer) - 1确保buffer数组有足够的空间来存储一个额外的空字符'\0'。接收到的字节数将存储在n中。

3:

  • 如果recvfrom返回的n(接收到的字节数)大于0,表示成功接收到了消息。
  • buffer[n] = '\0';:在接收到的消息字符串的末尾添加一个空字符'\0',确保它是一个合法的C字符串。
  • std::cout << buffer << std::endl;:输出从服务器接收到的消息。
  • 如果n小于或等于0,表示没有接收到有效的消息(可能是因为连接断开或错误),因此break语句将终止无限循环。

5.关闭socket文件描述符

close(sockfd);

 完整代码

Makefile

.PHOINY:all clean
all:client server

server:udp_server.cc
	g++ -o $@ $^ -std=c++11
	
client:udp_client.cc
	g++ -o $@ $^ -std=c++11 
clean:
	rm -f client server

udp_server.hpp

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <string>
#define CONV(in) ((struct sockaddr *)in)

const int socketfddefault = -1;
class UdpServer
{
public:
    UdpServer(uint16_t port, int sockfd = socketfddefault)
        : _port(port)
    {
    }
    bool Init()
    {
        // 创建socket
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            std::cerr << "creater socket fail!!" << std::endl;
            return false;
        }
        // 填充sockaddr_in
        // 定义sockaddr_in结构体变量---1
        struct sockaddr_in saddr;
        // 初始化server结构体---2
        memset(&saddr, 0, sizeof(saddr));
        // 设置地址族---3
        saddr.sin_family = AF_INET; // 协议家族
        // 设置端口号---4
        saddr.sin_port = htons(_port); // 主机字节序转网络字节序
        // inet_pton(AF_INET, _ip.c_str(), &(saddr.sin_addr));//点分十进制转网络字节序
        // 设置IP地址---5
        saddr.sin_addr.s_addr = INADDR_ANY;

        // bind
        int n = ::bind(_sockfd, CONV(&saddr), sizeof(saddr));
        if (n < 0)
        {
            std::cerr << "bind fail!" << std::endl;
        }
        return true;
    }
    bool Start()
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer)); // 初始化
        socklen_t len = sizeof(peer);
        // 收发消息
        while (true)
        {
            // 使用recvfrom收消息
            char buffer[1024];
            ssize_t rn = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&peer), &len);
            if (rn <= 0)
            {
                std::cerr << "receive message is empty" << std::endl;
                return false;
            }
            //输出从客户端接收到的消息
            buffer[rn] = 0; // 添加'/0'
            std::cout << "client say# " << buffer << std::endl;
            // 处理消息:在消息前加字符串"server say# "
            std::string message = "server say# ";
            message += buffer;

            // 发消息回client
            ssize_t sn = sendto(_sockfd, message.c_str(), message.size(), 0, CONV(&peer), len);
            if (sn <= 0)
            {
                std::cerr << " send message fail!!!" << std::endl;
                return false;
            }
        }
        return true;
    }
    int GetFd()
    {
        return _sockfd;
    }
    uint16_t GetPort()
    {
        return _port;
    }

    ~UdpServer()
    {
        // 关闭sockfd
        close(_sockfd);
    }

private:
    // 不需要,设置为任意ip都可以连接服务器
    // std::string _ip;//点分十进制ip地址
    int _sockfd;
    uint16_t _port; // 16位端口号
};

udp_server.cc

#include "udp_server.hpp"
#include <memory>
void Usage(const std::string& process)
{
    std::cout << "Usage:" << std::endl;;
    std::cout <<  "\t\t"<< process << " port, example: " << process << " 8888" << std::endl;
}
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return 0;
    }

    std::unique_ptr<UdpServer> server(new UdpServer(std::stoi(argv[1])));
    if(!server->Init())
    {
        std::cout << "server->Init() fail" << std::endl;
        return 1;
    }
    if(!server->Start())
    {
        std::cout << "server->Start() fail" << std::endl;
        return 2;
    }
    return 0;
}

udp_client.cc

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#define CONV(in) ((struct sockaddr *)in)

void Usage(const std::string &process)
{
    std::cout << "Usage:" << std::endl;
    std::cout << "\t\t" << process << " ip port, example: " << process << " 127.0.0.1 8888" << std::endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 0;
    }
    // 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "creater socket fail!!!" << std::endl;
    }

    std::cout << "client socket: " << sockfd << " precess pid: " << getpid() << std::endl;
    uint16_t port = std::stoi(argv[2]);
    // 填充server的sockaddr_in信息
    // 定义sockaddr_in结构体变量---1
    struct sockaddr_in server;
    // 初始化server结构体---2
    memset(&server, 0, sizeof(server));
    // 定义socklen_t变量
    socklen_t len = sizeof(server);
    // 设置地址族---3
    server.sin_family = AF_INET;
    // 设置端口号---4
    server.sin_port = htons(port);
    // 设置IP地址---5
    inet_pton(AF_INET, argv[1], &server.sin_addr);

    // 让操作系统自动bind:在首次发送数据时,如果没有显式调用bind,操作系统会自动为该socket分配一个本地端口
    while (true)
    {
        // 发信息
        std::string message;
        std::getline(std::cin, message);
        sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));

        // 收消息
        char buffer[1024];
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&server), &len);
        if (n > 0)
        {
            buffer[n] = 0; // 添加'/0'
            std::cout << buffer << std::endl;
        }
        else
        {
            break;
        }
    }
    close(sockfd);
}

TCP

tcp_server

1.生成socket文件描述符

std::cout << "process pid:" << getpid() << std::endl;
// 1.创建嵌套字
_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listenfd < 0)
{
    std::cerr << "creater listenfd fail!!!" << std::endl;
    return false;
}
  • AF_INET:指定使用IPv4地址族。这意味着套接字将使用IPv4地址来与网络上的其他设备进行通信。
  • SOCK_STREAM:指定创建的套接字类型为TCP套接字。TCP是一个面向连接的、可靠的、字节流的协议,适用于需要确保数据完整性和顺序的应用场景。
  • 0:通常设置为0,表示使用默认的协议。对于TCP套接字,这通常是TCP协议本身。

2.填充sockaddr_in信息

//填充sockaddr_in信息
// 定义sockaddr_in结构体变量---1
struct sockaddr_in local;
// 使用memset初始化结构体---2
memset(&local, 0, sizeof(local));
// 设置地址族---3
local.sin_family = AF_INET;
// 设置端口号---4
local.sin_port = htons(_port);
// 设置IP地址---5
local.sin_addr.s_addr = INADDR_ANY;

1:

这行代码定义了一个sockaddr_in类型的变量localsockaddr_in是一个结构体,通常用于IPv4地址的套接字编程。

2:

使用memset函数将local结构体的所有字节设置为0。这样做是为了确保结构体的所有未明确赋值的字段都被初始化为0,防止未初始化的内存导致的问题。

3:

sin_family字段设置为AF_INET,表示这个结构体用于IPv4地址。

4:

设置要绑定的端口号。_port是端口的整数值,它是以主机字节序存储的。htons函数用于将端口号从主机字节序转换为网络字节序(大端字节序),因为网络通信中通常使用网络字节序。

5:

sin_addr.s_addr字段设置为INADDR_ANY。这个特殊的宏表示套接字应该绑定到所有可用的网络接口上,即接受来自任何IP地址的连接。

3.bind

// bind
int ret = ::bind(_listenfd, CONV(&local), sizeof(local));
if (ret < 0)
{
    std::cerr << "bind fail!!!" << std::endl;
    return false;
}
  • :: 是一个作用域解析运算符,它在这里用来确保调用的是全局作用域中的bind函数,而不是某个类或者命名空间中可能存在的同名函数。
  • _listenfd 是之前通过socket函数创建的套接字文件描述符。
  • CONV是一个宏函数,用于将local的地址转换为正确的类型,以便作为bind函数的参数。通常,这个宏函数会转换为(struct sockaddr*),以确保类型匹配bind函数的第二个参数。
  • sizeof(local) 是local结构体的大小,它告诉bind函数要处理多少字节的地址信息。 

4.监听与获取连接

// 进入倾听状态
//1
ret = listen(_listenfd, 2); // 最多两client连接server
if (ret < 0)
{
    std::cerr << "listen fail!!!" << std::endl;
    return false;
}

// 获取连接
//2
struct sockaddr_in peer;
//3
memset(&peer, 0, sizeof(peer));
//4
socklen_t len = sizeof(peer);
//5
_sockfd = accept(_listenfd, CONV(&peer), &len);
if (_sockfd < 0)
{
    std::cerr << "accept link fail!!!" << std::endl;
    return false;
}
std::cout << "accept link success , socket fd:" << _sockfd << std::endl;

 1:

_listenfd这个套接字设置为监听状态,准备接受客户端的连接请求。参数2表示这个套接字队列中可以挂起的最大未完成连接数。换句话说,它限制了同时等待服务器处理的客户端连接数。

2:

定义了一个sockaddr_in类型的变量peer,用于存储客户端的地址信息。
3:

使用memset函数将peer结构体的所有字节设置为0,确保结构体的所有未明确赋值的字段都被初始化为0。

4:

定义了一个socklen_t类型的变量len,并初始化为peer结构体的大小。这个变量稍后会传递给accept函数,以便accept知道要填充多少字节的客户端地址信息。

5:

  • 这行代码调用accept函数,尝试从_listenfd这个监听套接字上接受一个客户端的连接请求。如果成功,accept会返回一个新的套接字文件描述符(_sockfd),这个新的套接字用于与客户端通信。同时,客户端的地址信息会被填充到peer结构体中,len会被更新为实际填充的字节数。
  • CONV是一个宏函数,用于将local的地址转换为正确的类型,以便作为bind函数的参数。通常,这个宏函数会转换为(struct sockaddr*),以确保类型匹配bind函数的第二个参数。

5.发(收)消息

// 收发消息
while (true)
{
    // 收消息---1
    char buffer[1024];
    ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
    if (n > 0)
    {
        buffer[n] = 0; // 添加'/0'
        std::cout << "client syd# " << buffer << std::endl;
        // 处理信息
        // 发消息
        std::string message = "server syd# ";
        message += buffer;
        //2
        n = send(_sockfd, message.c_str(), message.size(), 0);
        if (n == 0)
        {
            std::cerr << "send message is empty!!!" << std::endl;
        }
        else if (n < 0)
        {
            std::cerr << "send message fail!!!" << std::endl;
        }
        else
        {
            std::cerr << "send message success, message len is: " << message.size() << std::endl;
        }
    }
}

1:

使用recv函数从与客户端通信的套接字_sockfd接收消息。接收的最多字节数是sizeof(buffer) - 1,确保留有一个字节的位置给字符串结束符'\0'

2:

使用send函数将处理后的消息发送回客户端。发送的内容是message的C字符串形式(通过message.c_str()获取),长度为message.size()。 

6.关闭socket文件描述符

close(_listenfd);
close(_sockfd);

tcp_client

1.生成socket文件描述符

// 创建socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0)
{
    std::cerr << "creater socket fail!!!" << std::endl;
}
  • AF_INET:这是地址族(address family)参数,表示使用IPv4地址。
  • SOCK_STREAM:这是套接字类型参数,表示创建的是一个面向连接的套接字,通常用于TCP协议。
  • 0:这是协议参数,通常设置为0,表示使用默认的协议。

2.填充sockaddr_in信息

// 填充sockaddr_in信息
//1
struct sockaddr_in client;
//2
memset(&client, 0, sizeof(client));
//3
client.sin_family = AF_INET;
//4
client.sin_port = htons((uint16_t)std::stoi(argv[2]));
//5
inet_pton(AF_INET, argv[1], &(client.sin_addr));

1:

定义一个sockaddr_in类型的变量client。这个结构体通常用于IPv4地址和端口的表示。

2:

使用memset函数将client结构体的所有字节设置为0。这是一个常见的做法,用于初始化结构体,确保没有未初始化的内存。

3:

设置sin_family字段为AF_INET,表示这个地址是IPv4地址。
4:

设置sin_port字段为网络字节序的端口号。htons函数用于将主机字节序的端口号转换为网络字节序。这里假设argv[2]是一个字符串形式的端口号,使用std::stoi函数将其转换为整数。

5:

inet_pton函数用于将点分十进制的IPv4地址字符串(如"192.168.1.1")转换为二进制形式,并存储在sin_addr字段中。这里假设argv[1]是一个字符串形式的IPv4地址。AF_INET表示我们正在处理IPv4地址。

3.客户端不用手动bind

客户端通常不需要调用bind,因为操作系统会在创建socket时自动为其分配一个未被使用的本地端口。当客户端调用connect函数去连接服务器时,操作系统会自动完成socket的绑定操作,并将socket与远程服务器的地址和端口关联起来。

4.连接服务器(server)


// 连接server
int n = connect(sockfd, CONV(&client), sizeof(client));
if (n < 0)
{
    std::cerr << "connect server fail!!!" << std::endl;
    return 1;
}
std::cerr << "connect server success!!!" << std::endl;

  • connect 函数是客户端用来建立与服务器的连接的。
  • sockfd 是一个套接字描述符,代表客户端的套接字。它通常通过 socket 函数创建。
  • CONV(&client) 这部分代码中的 CONV 是一个宏函数,用于将 sockaddr_in 类型的 client 转换为 connect 函数所需的 sockaddr 类型。
  • sizeof(client) 提供了 client 结构体的大小,这告诉 connect 函数要发送多少字节的地址信息。
  • n 存储 connect 函数的返回值。如果连接成功,返回值通常是0;如果连接失败,返回值是-1,并且全局变量 errno 会被设置为指示错误原因的值。

5.发(收)消息

while (true)
{
    // 发消息
    std::string message;
    getline(std::cin, message);
    //1
    ssize_t n = send(sockfd, message.c_str(), message.size(), 0);
    if (n == 0)
    {
        std::cout << "send message is empty!!!" << std::endl;
    }
    else if (n < 0)
    {
        std::cout << "send message fail!!!" << std::endl;
        return 2;
    }
    char buffer[1024];
    // 收消息
    //2
    n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (n == 0)
    {
        std::cout << "receive message is empty!!!" << std::endl;
    }
    else if (n < 0)
    {
        std::cout << "receive message fail!!!" << std::endl;
        return 2;
    }
    else
    {
        buffer[n] = 0; // 添加'/0'
        std::cout << buffer << std::endl;
        std::cerr << "receive message success, message len is: " << n << std::endl;
    }
}

1:

send函数用于发送数据到已连接的套接字。这里,它发送message字符串的内容。

  • sockfd是已建立的套接字描述符。
  • message.c_str()返回字符串内容的C风格字符数组。
  • message.size()返回字符串的长度(以字节为单位)。
  • 0send函数的标志参数,通常用于控制发送行为。在这里,它设置为0,表示使用默认行为。

send函数的返回值n表示实际发送的字节数。如果n小于message.size(),则可能发生了部分发送,这在非阻塞套接字或网络拥塞时可能发生。

2:

recv函数用于从已连接的套接字接收数据。

  • sockfd是已建立的套接字描述符。
  • buffer是接收数据的存储位置。
  • sizeof(buffer) - 1指定接收缓冲区的大小,减去一个字节用于存储字符串结束符。
  • 0recv函数的标志参数,用于控制接收行为。

recv函数的返回值n表示实际接收到的字节数。

6.关闭socket文件描述符

close(sockfd);

完整代码

Makefile

.PHONY:all clean
all:server client

server:tcp_server.cc
	g++ -o $@ $^ -std=c++11
client:tcp_clinet.cc
	g++ -o $@ $^ -std=c++11

clean:
	rm -f server client

tcp_server.hpp

#pragma once
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>
#include <memory>
#include <cstring>
#include <iostream>
#define CONV(in) ((struct sockaddr *)in)
const int defaultsockfd = -1;
class TcpServer
{
public:
    TcpServer(uint16_t port, int listenfd = defaultsockfd, int sockfd = defaultsockfd)
        : _port(port), _listenfd(listenfd), _sockfd(sockfd)
    {
    }
    bool Init()
    {
        std::cout << "process pid:" << getpid() << std::endl;
        // 1.创建嵌套字
        _listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenfd < 0)
        {
            std::cerr << "creater listenfd fail!!!" << std::endl;
            return false;
        }
        std::cout << "creater listen socket success, listen fd:" << _listenfd << std::endl;
        // 2.填充sockaddr_in信息
        // 定义sockaddr_in结构体变量
        struct sockaddr_in local;
        // 使用memset初始化结构体
        memset(&local, 0, sizeof(local));
        // 设置地址族
        local.sin_family = AF_INET;
        // 设置端口号
        local.sin_port = htons(_port);
        // 设置IP地址
        local.sin_addr.s_addr = INADDR_ANY;

        // 3.bind
        int ret = ::bind(_listenfd, CONV(&local), sizeof(local));
        if (ret < 0)
        {
            std::cerr << "bind fail!!!" << std::endl;
            return false;
        }

        // 4.进入倾听状态
        ret = listen(_listenfd, 2); // 最多两client连接server
        if (ret < 0)
        {
            std::cerr << "listen fail!!!" << std::endl;
            return false;
        }

        // 5.获取连接
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        _sockfd = accept(_listenfd, CONV(&peer), &len);
        if (_sockfd < 0)
        {
            std::cerr << "accept link fail!!!" << std::endl;
            return false;
        }
        std::cout << "accept link success , socket fd:" << _sockfd << std::endl;
        return true;
    }
    void Start()
    {
        // 收发消息
        while (true)
        {
            // 收消息
            char buffer[1024];
            ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
            {
                buffer[n] = 0; // 添加'/0'
                std::cout << "client syd# " << buffer << std::endl;
                // 处理信息
                // 发消息
                std::string message = "server syd# ";
                message += buffer;
                n = send(_sockfd, message.c_str(), message.size(), 0);
                if (n == 0)
                {
                    std::cerr << "send message is empty!!!" << std::endl;
                }
                else if (n < 0)
                {
                    std::cerr << "send message fail!!!" << std::endl;
                }
                else
                {
                    std::cerr << "send message success, message len is: " << message.size() << std::endl;
                }
            }
        }
    }
    ~TcpServer()
    {
        // 关闭socket
        close(_listenfd);
        close(_sockfd);
    }

private:
    int _listenfd;
    int _sockfd;
    uint16_t _port;
};

tcp_server.cc

#include "tcp_server.hpp"

void ServerUsage(const std::string& process)
{
    std::cout << "ServerUsage\n\t\t" << process << " port, example: " << process << " 8888" << std::endl;
}

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        ServerUsage(argv[0]);
        return 0;
    }
    std::unique_ptr<TcpServer> server(new TcpServer(std::stoi(argv[1])));
    if(!server->Init())
    {
        std::cerr << "Init fail" << std::endl;
    }
    server->Start();

    return 0;

}

tcp_client.cc

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>
#include <memory>
#include <cstring>
#include <iostream>
#define CONV(in) ((struct sockaddr *)in)
void ClinetUsage(const std::string &process)
{
    std::cout << "ServerUsage\n\t\t" << process << " ip port, example: " << process << " 127.0.0.1 8888" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        ClinetUsage(argv[0]);
        return 0;
    }
    // 1.创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0)
    {
        std::cerr << "creater socket fail!!!" << std::endl;
    }

    std::cout << "client socket: " << sockfd << " precess pid: " << getpid() << std::endl;

    // 2.填充sockaddr_in信息
    struct sockaddr_in client;
    memset(&client, 0, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_port = htons((uint16_t)std::stoi(argv[2]));
    inet_pton(AF_INET, argv[1], &(client.sin_addr));

    // 3.连接server
    int n = connect(sockfd, CONV(&client), sizeof(client));
    if (n < 0)
    {
        std::cerr << "connect server fail!!!" << std::endl;
        return 1;
    }
    std::cerr << "connect server success!!!" << std::endl;
    // 4.让操作系统会自动完成socket的绑定操作
    // 5.收发消息
    while (true)
    {
        // 发消息
        std::string message;
        getline(std::cin, message);
        ssize_t n = send(sockfd, message.c_str(), message.size(), 0);
        if (n == 0)
        {
            std::cout << "send message is empty!!!" << std::endl;
        }
        else if (n < 0)
        {
            std::cout << "send message fail!!!" << std::endl;
            return 2;
        }
        char buffer[1024];
        // 收消息
        n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n == 0)
        {
            std::cout << "receive message is empty!!!" << std::endl;
        }
        else if (n < 0)
        {
            std::cout << "receive message fail!!!" << std::endl;
            return 2;
        }
        else
        {
            buffer[n] = 0; // 添加'/0'
            std::cout << buffer << std::endl;
            std::cerr << "receive message success, message len is: " << n << std::endl;
        }
    }
    close(sockfd);
}

代码运行结果视频展示

以udp为例子(tcp也是同样的操作即可)

环境:Linux

软件:XShell

代码编写软件:Visual Studio Code(即vscode)连接远端服务器编写代码

注意:如果想使用XShell你需要买一个云端服务器

视频链接:udp代码运行展示-CSDN直播

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/579894.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

序列化与反序列化

【一】序列化跟反序列化 # api接口开发&#xff0c;最核心最常见的一个过程就是序列化&#xff0c;所谓序列化就是把数据转换格式&#xff0c;序列化可以分两个阶段&#xff1a;【序列化值的是转换数据格式&#xff1a;序列化&#xff0c;返序列化】# 序列化&#xff1a; 把我们…

使用frp实现内网穿透教程

文章目录 简介frp 是什么&#xff1f;为什么选择 frp&#xff1f; 概念工作原理代理类型 内网穿透教程服务端安装和配置本地Windows&#xff08;客户端&#xff09;安装和配置本地Linux虚拟机&#xff08;客户端&#xff09;安装和配置使用 systemd 管理服务端注意事项 简介 f…

openapi3和openapi2的注解区别

最近在该项目&#xff0c;发现openapi3版本相比起2版本已经发生了很大的变化&#xff0c;原来的注解全部失效了&#xff0c;取而代之的是新的注解

基于Transformer深度学习的翻译模型(英->中)源码系统

第一步&#xff1a;Transformer介绍 Transformer是一种基于注意力机制的序列到序列模型&#xff0c;它在机器翻译任务中表现出色并逐渐成为自然语言处理领域的主流模型。Transformer模型的核心思想是使用自注意力机制&#xff08;self-attention&#xff09;来捕捉输入序列中各…

【Java系列】给大家出一些JavaSE基础第七天的内容案例 , 让大家更好的理解与掌握

面向对象 好的&#xff0c;以下是一些Java面向对象基础的案例&#xff1a; 案例一 1. 定义一个学生类Student&#xff0c;包含姓名、年龄、性别等属性&#xff0c;以及学习、吃饭等行为。然后创建一个学生对象&#xff0c;调用其行为方法。 public class Student {private St…

Linux驱动开发:深入理解I2C时序

目录标题 I2C简介I2C时序关键点Linux内核中的I2C时序处理I2C适配器I2C算法I2C核心 代码示例&#xff1a;I2C设备访问调试I2C时序问题 在Linux驱动开发中&#xff0c;理解和正确处理I2C时序对于确保I2C设备正常工作至关重要。本文将详细介绍I2C通信协议的时序特征&#xff0c;并…

西门子程序专业备份软件BUDdy for S7和使用说明

西门子程序专业备份软件BUDdy for S7和使用说明

基于Springboot的web后端开发三层架构上手实操

引言 我们拿到了xml文件 我们要将将xml文件中的数据加载并解析 完成数据的处理 并且返回给前端页面(result格式) 1.将xml文件放在resources目录下 xml是我们需要解析的文件 查看xml文件 2.在springboot中引入dom4j依赖 解析xml需要在springboot中引入dom4j依赖 这边我们…

Docker——部署LNMP架构

目录 一、LNMP架构概述 1.项目环境 2.服务器环境 3.需求 二、搭建Linux系统基础镜像 三、部署Nginx 1.建立工作目录 2.编写Dockerfile脚本 3.准备Nginx.conf配置文件 4.生成镜像 5.创建自定义网络 6.启动镜像容器 7.验证Nginx 三、部署Mysql 1.建立工作目录 2.编…

【STM32+HAL】读取电池电量

一、准备工作 有关CUBEMX的初始化配置&#xff0c;参见我的另一篇blog&#xff1a;【STM32HAL】CUBEMX初始化配置 有关定时器触发ADC模式配置&#xff0c;详见【STM32HAL】ADC采集波形实现 有关软件触发ADC模式配置&#xff0c;详见【STM32HAL】三轴按键PS2摇杆 二、所用工具…

Python赋值运算符

目录 赋值运算符 将值赋给变量&#xff1a; 做加法运算之后完成赋值&#xff1a; 做减法运算之后完成赋值&#xff1a;- 做乘法运算之后完成赋值&#xff1a;* 做除法运算之后完成赋值&#xff1a;/ 做整除运算之后完成赋值&#xff1a;// 做幂次运算之后完成赋值&#xff1a;*…

自动驾驶框架 UniAD环境部署

感谢大佬们的开源工作 UniAD-github地址-YYDS更多bev算法部署参考如果您觉得本帖对您有帮助&#xff0c;感谢您一键三连支持一波^_^ 统一自动驾驶框架 (UniAD) &#xff0c;第一个将全栈驾驶任务整合到一个深度神经网络中的框架&#xff0c;并可以发挥每个子任务以及各个模块的…

【MySQL 数据宝典】【索引原理】- 004 优化示例-join in exist

一、join 优化原理 1.1 基本连接方式介绍 JOIN 是 MySQL 用来进行联表操作的&#xff0c;用来匹配两个表的数据&#xff0c;筛选并合并出符合我们要求的结果集。 1.2 驱动表的定义 1.2.1 什么是驱动表 多表关联查询时,第一个被处理的表就是驱动表,使用驱动表去关联其他表.驱…

基于springboot的考勤管理系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

Zynq 7000 系列中成功执行BootROM的条件

Zynq 7000设备的启动需要正确的电压序列和I/O引脚控制。BootROM的流程由复位类型、启动模式引脚设置以及启动映像来控制。BootROM对所选启动设备的引脚连接有特定的要求。 Zynq 7000 SoC设备具有电源、时钟和复位要求&#xff0c;这些要求必须得到满足&#xff0c;才能成功执行…

java:SpringBootWeb请求响应

Servlet 用java编写的服务器端程序 客户端发送请求至服务器 服务器启动并调用Servlet,Servlet根据客户端请求生成响应内容并将其传给服务器 服务器将响应返回给客户端 javaweb的工作原理 在SpringBoot进行web程序开发时,内置了一个核心的Servlet程序DispatcherServlet,称之…

RocketMQ快速入门:namesrv、broker、dashboard的作用及消息发送、消费流程(三)

0. 引言 接触rocketmq之后&#xff0c;大家首当其冲的就会发现需要安装3个组件&#xff1a;namesrv, broker, dashboard&#xff0c;其中dashboard也叫console&#xff0c;为选装。而这几个组件之前的关系是什么呢&#xff0c;消息发送和接收的过程是如何传递的呢&#xff0c;…

应用实战 | 别踩白块小游戏,邀请大家来PK挑战~

“踩白块会输”是一个简单的微信小程序游戏&#xff0c;灵感来自当年火热的别踩白块游戏&#xff0c;程序内分成三个模块&#xff1a;手残模式、经典模式和极速模式&#xff0c;分别对应由易到难的三种玩法&#xff0c;可以查看游戏排名。动画效果采用JS实现&#xff0c;小程序…

Spark-机器学习(6)分类学习之支持向量机

在之前的文章中&#xff0c;我们学习了分类学习之朴素贝叶斯算法&#xff0c;并带来简单案例&#xff0c;学习用法。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢…

基于YOLOV8+Pyqt5无人机航拍太阳能电池板检测系统

1.YOLOv8的基本原理 YOLOv8是一种前沿的目标检测技术&#xff0c;它基于先前YOLO版本在目标检测任务上的成功&#xff0c;进一步提升了性能和灵活性&#xff0c;在精度和速度方面都具有尖端性能。在之前YOLO 版本的基础上&#xff0c;YOLOv8 引入了新的功能和优化&#xff0c;…
最新文章