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

ops/2024/10/18 4:18:26/

目录

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

了解socket的相关函数接口

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

main函数带参数有什么用

UDP

udp_server-toc" style="margin-left:40px;">udp_server

1.生成socket文件描述符

2.填充sockaddr_in信息

3.bind

4.发(收)消息

5.关闭socket文件描述符

udp_client-toc" style="margin-left:40px;">udp_client

1.生成socket文件描述符

2.填充sockaddr_in信息

3.客户端不用手动bind

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

5.关闭socket文件描述符

 完整代码

Makefile

udp_server.hpp-toc" style="margin-left:80px;">udp_server.hpp

udp_server.cc-toc" style="margin-left:80px;">udp_server.cc

udp_client.cc-toc" style="margin-left:80px;">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">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];//3ssize_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// 发消息回clientssize_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">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)
{// 发信息---1std::string message;std::getline(std::cin, message);sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));// 收消息---2char buffer[1024];ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&server), &len);//3if (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 serverserver:udp_server.ccg++ -o $@ $^ -std=c++11client:udp_client.ccg++ -o $@ $^ -std=c++11 
clean:rm -f client server

udp_server.hpp">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结构体变量---1struct sockaddr_in saddr;// 初始化server结构体---2memset(&saddr, 0, sizeof(saddr));// 设置地址族---3saddr.sin_family = AF_INET; // 协议家族// 设置端口号---4saddr.sin_port = htons(_port); // 主机字节序转网络字节序// inet_pton(AF_INET, _ip.c_str(), &(saddr.sin_addr));//点分十进制转网络字节序// 设置IP地址---5saddr.sin_addr.s_addr = INADDR_ANY;// bindint 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;// 发消息回clientssize_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(){// 关闭sockfdclose(_sockfd);}private:// 不需要,设置为任意ip都可以连接服务器// std::string _ip;//点分十进制ip地址int _sockfd;uint16_t _port; // 16位端口号
};

udp_server.cc">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">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;}// 创建socketint 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结构体变量---1struct sockaddr_in server;// 初始化server结构体---2memset(&server, 0, sizeof(server));// 定义socklen_t变量socklen_t len = sizeof(server);// 设置地址族---3server.sin_family = AF_INET;// 设置端口号---4server.sin_port = htons(port);// 设置IP地址---5inet_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)
{// 收消息---1char 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;//2n = 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);//1ssize_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];// 收消息//2n = 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 clientserver:tcp_server.ccg++ -o $@ $^ -std=c++11
client:tcp_clinet.ccg++ -o $@ $^ -std=c++11clean: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.bindint ret = ::bind(_listenfd, CONV(&local), sizeof(local));if (ret < 0){std::cerr << "bind fail!!!" << std::endl;return false;}// 4.进入倾听状态ret = listen(_listenfd, 2); // 最多两client连接serverif (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(){// 关闭socketclose(_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.创建socketint 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.连接serverint 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.ppmy.cn/ops/21099.html

相关文章

刷题之Leetcode242题(超级详细)

242.有效的字母异位词 力扣题目链接(opens new window)https://leetcode.cn/problems/valid-anagram/ 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 示例 1: 输入: s "anagram", t "nagaram" 输出: true 示例 2…

三勾点餐系统,到店点餐,到店自取,开源点餐

“您着急的话可以扫码点餐&#xff0c;点好后凭取餐码取餐呢。”现在生活中去餐馆都能听到这样的一句话&#xff0c;扫码点餐小程序现在很多餐厅、奶茶店、火锅店等都有的一个功能&#xff0c;即方便了顾客&#xff0c;也为商家带来了许多便利。 扫码点餐越来越常见&#…

图论——基础概念

文章目录 学习引言什么是图图的一些定义和概念图的存储方式二维数组邻接矩阵存储优缺点 数组模拟邻接表存储优缺点 边集数组优缺点排序前向星优缺点链式前向星优缺点 学习引言 图论&#xff0c;是 C 里面很重要的一种算法&#xff0c;今天&#xff0c;就让我们一起来了解一下图…

vue3.0(三) Vite文件目录结构及SFC语法

文章目录 Vite介绍Vite文件目录结构SFC语法SFC 语法定义bug解决 Vite介绍 为什么使用Vite&#xff1f; 表现 与Vite的ESbuild预绑定使其比使用任何其他JS绑定器都快10到100倍。这是因为它有助于提高页面速度并将CommonJS/UMD模块转换为ESM。 基于Vite文件&#xff0c;“预绑定…

NDK 基础(一)—— C 语言知识汇总

本系列文章主要是介绍一些 NDK 开发所需的基础知识&#xff0c;目录如下&#xff1a; NDK 基础&#xff08;一&#xff09;—— C 语言知识汇总 NDK 基础&#xff08;二&#xff09;—— C 语言基础与特性1 NDK 基础&#xff08;三&#xff09;—— C 语言基础与特性2 NDK 基础…

六西格玛管理培训:初学者能收获哪些实用知识?

在瞬息万变的商业舞台上&#xff0c;卓越运营管理和持续改进成为企业稳固市场地位、保持竞争优势的关键。六西格玛管理&#xff0c;这把打开高质量之门的金钥匙&#xff0c;正受到越来越多追求卓越的企业的热烈追捧。对于刚刚涉足这一领域的初学者&#xff0c;参与六西格玛管理…

做大模型产品,如何设计prompt?

做GenAI产品&#xff0c;除了要设计好的AI任务流程&#xff0c;合理的拆分业务以外&#xff0c;最重要的就是写好prompt&#xff0c;管理好prompt&#xff0c;持续迭代prompt。 prompt一般有两种形式&#xff1a;结构化prompt和对话式prompt。 结构化prompt的优点是通过规范的…

数据集笔记:处理北大POI 数据:保留北京POI

数据来源&#xff1a;Map POI (Point of Interest) data - Official data of the contest (pku.edu.cn) windows 下载方法&#xff1a;数据集笔记&#xff1a;windows系统下载北大开放数据研究平台的POI数据-CSDN博客 1 读取数据 1.1 列出所有的文件 dir1D:/data/PKU POI/2…