目录
4.发(收)消息 (client会在首次发送数据的时候会自动进行bind)
看以下内容前,你要先了解main函数带参数有什么用、
了解socket的相关函数接口
如果不了解socket的相关函数接口请先看我这篇文章
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文件描述符
cpp//创建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信息
cpp
//填充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;
解释:
- 这行代码定义了一个
sockaddr_in
类型的变量saddr
。sockaddr_in
是用于IPv4的套接字地址结构。- 使用
memset
函数将saddr
结构体的所有字节都设置为0。这是为了确保结构体中的所有未明确设置的字段都从一个已知的、安全的初始值开始。- 将
saddr
结构体的sin_family
字段设置为AF_INET
,表示这是一个IPv4地址。sin_port
字段用于存储端口号。htons
函数用于将主机字节序的端口号转换为网络字节序。_port
是一个之前定义的变量,它包含了要设置的端口号。- 这里将
sin_addr
字段的s_addr
成员设置为INADDR_ANY
。INADDR_ANY
是一个特殊的常量,用于表示套接字应该绑定到所有可用的网络接口,通常用于服务器程序,以便它们可以接受来自任何IP地址的连接。
3.bind
cpp
//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.发(收)消息
cpp
//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文件描述符
cpp
// 关闭sockfd
close(_sockfd);
udp_client
1.生成socket文件描述符
cpp
//创建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
std::cerr << "creater socket fail!!!" << std::endl;
}
2.填充sockaddr_in信息
cpp
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
结构体的大小。这个变量通常用于bind
、connect
等网络函数中,表示地址结构体的长度。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)
cpp
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文件描述符
cpp
close(sockfd);
完整代码
Makefile
cpp
.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
cpp
#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
cpp
#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
cpp
#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文件描述符
cpp
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信息
cpp
//填充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
类型的变量local
。sockaddr_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
cpp
// 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.监听与获取连接
cpp
// 进入倾听状态
//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.发(收)消息
cpp
// 收发消息
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文件描述符
cpp
close(_listenfd);
close(_sockfd);
tcp_client
1.生成socket文件描述符
cpp
// 创建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信息
cpp
// 填充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)
cpp
// 连接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.发(收)消息
cpp
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()
返回字符串的长度(以字节为单位)。0
是send
函数的标志参数,通常用于控制发送行为。在这里,它设置为0,表示使用默认行为。
send
函数的返回值n
表示实际发送的字节数。如果n
小于message.size()
,则可能发生了部分发送,这在非阻塞套接字或网络拥塞时可能发生。2:
recv
函数用于从已连接的套接字接收数据。
sockfd
是已建立的套接字描述符。buffer
是接收数据的存储位置。sizeof(buffer) - 1
指定接收缓冲区的大小,减去一个字节用于存储字符串结束符。0
是recv
函数的标志参数,用于控制接收行为。
recv
函数的返回值n
表示实际接收到的字节数。
6.关闭socket文件描述符
cpp
close(sockfd);
完整代码
Makefile
cpp
.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
cpp
#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
cpp
#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
cpp
#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直播