[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];//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

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

#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

#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;}// 创建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.mzph.cn/pingmian/4436.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习100个Unity Shader (14) ---透明效果

文章目录 渲染队列透明度测试&#xff08;Alpha Test&#xff09;效果Shader 透明度混合&#xff08;Alpha Blending&#xff09;效果Shader 参考 渲染队列 由”Queue“ 标签决定&#xff0c;索引号越小越早被渲染&#xff1a; 名称队列索引号Background1000Geometry2000Alph…

从虚拟化走向云原生,红帽OpenShift“一手托两家”

汽车行业已经迈入“软件定义汽车”的新时代。吉利汽车很清醒地意识到&#xff0c;只有通过云原生技术和数字化转型&#xff0c;才能巩固其作为中国领先汽车制造商的地位。 和很多传统企业一样&#xff0c;吉利汽车在走向云原生的过程中也经历了稳态业务与敏态业务并存带来的前所…

牛客NC98 判断t1树中是否有与t2树完全相同的子树【simple 深度优先dfs C++/Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/4eaccec5ee8f4fe8a4309463b807a542 思路 深度优先搜索暴力匹配 思路和算法这是一种最朴素的方法——深度优先搜索枚举 s 中的每一个节点&#xff0c;判断这个点的子树是否和 t 相等。如何判断一个节点的子树是否…

JSP在页面用<%=调用声明函数时出现HTTP 500错误

JSP在页面用<%调用声明函数时出现HTTP 500错误 错误描述&#xff1a; Eclipse在编写JSP页面时&#xff0c;在其中采用<%&#xff01;%>方式声明了函数&#xff0c;然后在页面中用<%函数名%>方式调用时&#xff0c;出现HTTP状态500错误&#xff0c;提示为&#…

【吊打面试官系列】Java高并发篇 - Java 中 notify 和 notifyAll 有什么区别?

大家好&#xff0c;我是锋哥。今天分享关于 【Java 中 notify 和 notifyAll 有什么区别&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; Java 中 notify 和 notifyAll 有什么区别&#xff1f; notify() 方法不能唤醒某个具体的线程&#xff0c;所以只有一个线程…

maya blendshape

目录 shape编辑器 maya创建blendshape python 脚本 添加形变动画 查看顶点个数 shape编辑器 打开方式&#xff1a; 窗口-动画编辑器-形变编辑器 maya创建blendshape python 脚本 import maya.cmds as cmds# 创建基础网格 - 球体 baseMesh cmds.polySphere(name"bas…

路透社:美国SEC将拒绝以太坊ETF

4月25日&#xff0c;据路透社报道&#xff0c;美国SEC在下个月将拒绝以太坊现货ETF申请。根据4位知情人士表示&#xff0c;在最近几周与美国证券交易委员会&#xff08;SEC&#xff09;进行了会议之后&#xff0c;美国发行商和其他公司预计SEC将拒绝他们推出与以太坊价格挂钩的…

15(第十四章,大数据和数据科学)

目录 概述 基本概念 数据仓库/传统商务智能与数据科学的比较 数据科学的过程 大数据 大数据来源 数据湖 机器学习 监督学习 无监督学习 强化学习 扩展 1、数据仓库&#xff08;Data Warehouse&#xff09; 2、数据湖(Data Lake) 3、大数据平台1.0 4、数据中台 …

基于ssm的高校课程评价评教系统(含源码+sql+视频导入教程+文档+PPT)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于ssm的高校课程评价评教系统拥2有四种角色 管理员&#xff1a;学生管理、指标管理、课程两个、院系管理、评教管理等 学生&#xff1a;评教教师、登录注册、查看课程信息等 专家&am…

MySQL-多表查询-练习

练习 1.写一个查询显示所有雇员的 last name、department id、anddepartment name。 SELECT e.LAST_NAME,e.DEPARTMENT_ID,d.DEPARTMENT_NAME FROM employees e,departments d WHERE e.DEPARTMENT_ID d.DEPARTMENT_ID;2.创建一个在部门 80 中的所有工作岗位的唯一列表&#x…

递归、搜索与回溯算法:综合练习

例题一 解法&#xff1a; 算法思路&#xff1a; ⾸先&#xff0c;我们在第⼀⾏放置第⼀个皇后&#xff0c;然后遍历棋盘的第⼆⾏&#xff0c;在可⾏的位置放置第⼆个皇后&#xff0c;然后再遍历第三⾏&#xff0c;在可⾏的位置放置第三个皇后&#xff0c;以此类推&#xff0c…

解锁大模型高效推理:将 LlamaIndex 与抽象链集成

在语言理解领域&#xff0c;对忠实推理的追求促使研究人员探索各种途径。 大型语言模型&#xff08;LLMs&#xff09;在解释和执行指令方面取得了显著进展&#xff0c;但在准确回忆和组合现实世界知识方面仍然面临挑战。 为了解决这个问题&#xff0c;将外部工具集成到LLMs的…

北京摇号发展史!摇不到号是有原因的!

北京车牌摇号史可以追溯到较早的时期&#xff0c;但近年来随着城市发展和汽车保有量的增加&#xff0c;摇号政策经历了多次调整和完善。 在摇号政策实施初期&#xff0c;主要是为了解决城市交通拥堵和减少汽车尾气排放等问题。当时&#xff0c;申请车牌的人数相对较少&#xf…

龙腾启新·AI创未来—2024上海AIGC行业技术沙龙活动成功举办

随着人工智能技术的不断发展和创新&#xff0c;AIGC正逐渐成为科技领域的新热点。上海作为中国的经济中心和科技创新的重要城市&#xff0c;拥有众多优秀的科研机构、高校和企业&#xff0c;为AIGC领域的发展提供了丰富的人才和资源。 4月25日&#xff0c;上海市人工智能行业协…

第八周学习笔记DAY.4-实用类介绍

本课目标 能够定义并使用枚举类型 掌握包装类及装箱、拆箱概念 会使用Math类进行数学运算 枚举 枚举指由一组固定的常量组成的类型 Java API 常用Java API java.lang Enum、包装类、Math、String、StringBuffer、System… … java.util java.io java.sql … … 包装类…

大数据时代,保护个人隐私小Tips Get 起来!

随着大数据时代的到来&#xff0c;我们的隐私正处于越来越易被侵犯的风险中。在各种社交媒体和信息共享平台上&#xff0c;我们需要输入各种个人信息&#xff0c;而这些信息可能被不法分子盗取&#xff0c;甚至被用来进行欺诈行为。在如今的大数据时代&#xff0c;保护个人隐私…

进一步了解android studio 里 AGP,gradle等关系

目录 &#xff08;1&#xff09; gradle是什么 &#xff08;2&#xff09; 工程的jdk版本&#xff0c;及引用包的编译版本的关系 实践 问题与解决 编译成功与运行成功 编译成功 运行成功 &#xff08;1&#xff09; gradle是什么 Gradle是一个构建工具&#xff0c;它是…

(学习日记)2024.04.28:UCOSIII第五十二节:User文件夹函数概览(uC-LIB文件夹)第二部分

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

【八大排序(一)】插入排序与希尔排序

❣博主主页: 33的博客❣ ▶️文章专栏分类:八大排序◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多排序知识 目录 1.前言2.常见排序算法3.稳定性4.插入排序4.1概念4.2直接插入排序4.3希尔排序 5.总结 …

TechTool Pro for mac中文激活版:硬件监测和系统维护工具

TechTool Pro mac帮助用户实现系统硬件监测&#xff08;CPU、内存、硬盘、网络、USB等&#xff09;、内存测试、S.M.A.R.T检测、磁盘宗卷扫描、宗卷重建和优化、数据恢复和粉碎等等&#xff0c;定期使用&#xff0c;可以确保您的Mac保持优化和无故障。 TechTool Pro for mac v1…