文章目录
Linux平台下TCP客户/服务端程序
图片来源:https://subingwen.cn/linux/socket/
下面实现一个Linux平台下TCP客户/服务端程序:客户端向服务器发送:"你好,服务器...递增数字",然后服务器发送响应消息:"你好,客户端"。
服务端
server.cpp
cpp
#include <iostream>
#include <cstdlib> // std::exit
#include <cstring> // memset sprintf strlen
#include <arpa/inet.h> // inet_ntop, htons, ntohs, INADDR_ANY, INET_ADDRSTRLEN
#include <unistd.h> // close
// #include <sys/socket.h> // sockaddr_in, socket(), bind(), listen(), accept(), send(), recv(),SOCK_STREAM,AF_INET
/*
<arpa/inet.h>包含了<netinet/in.h>,而<netinet/in.h>包含了 <sys/socket.h>。
所以实际使用时,只需要#include <arpa/inet.h>,不需要#include <sys/socket.h>
*/
int main()
{
// 1. 创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0); // 创建一个TCP套接字
if (lfd == -1)
{
perror("socket"); // 错误处理
std::exit(EXIT_FAILURE);
}
// 2. 将socket()返回值和本地的IP端口绑定到一起
sockaddr_in addr; // 用于存储地址信息
addr.sin_family = AF_INET; // 地址族,IPv4
addr.sin_port = htons(10000); // 大端端口转换
//addr.sin_addr.s_addr = INADDR_ANY; // 绑定到任意IP地址
inet_pton(AF_INET, "172.31.108.107", &addr.sin_addr.s_addr); // 指定IP地址
int ret = bind(lfd, (sockaddr*)&addr, sizeof(addr)); // 绑定套接字到地址
if (ret == -1)
{
perror("bind"); // 错误处理
std::exit(EXIT_FAILURE);
}
// 3. 设置监听
ret = listen(lfd, 128); // 开始监听
if (ret == -1)
{
perror("listen"); // 错误处理
std::exit(EXIT_FAILURE);
}
// 4. 阻塞等待并接受客户端连接
sockaddr_in cliaddr; // 用于存储客户端地址信息
socklen_t clilen = sizeof(cliaddr); // 客户端地址结构的大小
int cfd = accept(lfd, (sockaddr*)&cliaddr, &clilen); // 接受客户端连接
if (cfd == -1)
{
perror("accept"); // 错误处理
std::exit(EXIT_FAILURE);
}
// 打印客户端的地址信息
char ip[INET_ADDRSTRLEN] = {0}; // 存储客户端IP地址
std::cout << "客户端的IP地址: "
<< inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)) // 将IP地址转换为字符串
<< ", 端口: "
<< ntohs(cliaddr.sin_port) << std::endl; // 端口号转换
// 5. 和客户端通信
while (true)
{
// 接收数据
char buf[1024]; // 接收缓冲区
memset(buf, 0, sizeof(buf)); // 清零缓冲区
int len = recv(cfd, buf, sizeof(buf), 0); // 从客户端读取数据
if (len > 0)
{
std::cout << "客户端: " << buf << std::endl; // 打印客户端发送的消息
sprintf(buf, "你好, 客户端\n"); // 格式化字符串
send(cfd, buf, strlen(buf), 0); // 回应客户端
}
else if (len == 0)
{
std::cout << "客户端断开了连接..." << std::endl; // 客户端断开连接
break;
}
else
{
perror("recv"); // 错误处理
break;
}
}
close(cfd); // 关闭与客户端的连接
close(lfd); // 关闭监听套接字
return 0;
}
编译与运行
bash
g++ server.cpp -o server
./server
客户端
client.cpp
cpp
#include <iostream> // std::cout, std::cerr
#include <cstdlib> // std::exit
#include <unistd.h> // close, sleep
#include <cstring> // memset, strlen
#include <arpa/inet.h> // socket, connect, inet_pton, htons
int main()
{
// 1. 创建通信的套接字
int fd = socket(AF_INET, SOCK_STREAM, 0); // 创建一个TCP套接字
if (fd == -1)
{
perror("socket"); // 错误处理
std::exit(EXIT_FAILURE);
}
// 2. 连接服务器
sockaddr_in addr; // 用于存储服务器地址信息
addr.sin_family = AF_INET; // 地址族,IPv4
addr.sin_port = htons(10000); // 大端端口转换
inet_pton(AF_INET, "172.31.108.107", &addr.sin_addr.s_addr); // 将IP地址转换为网络字节顺序
int ret = connect(fd, (sockaddr*)&addr, sizeof(addr)); // 连接到服务器
if (ret == -1)
{
perror("connect"); // 错误处理
std::exit(EXIT_FAILURE);
}
// 3. 和服务器端通信
int number = 0;
while (true)
{
// 发送数据
char buf[1024]; // 数据缓冲区
sprintf(buf, "你好, 服务器...%d", number++); // 格式化字符串
send(fd, buf, strlen(buf), 0); // 发送数据
// 接收数据
memset(buf, 0, sizeof(buf)); // 清空缓冲区
int len = recv(fd, buf, sizeof(buf), 0); // 从服务器读取数据
if (len > 0)
{
std::cout << "服务器: " << buf; // 打印服务器发送的消息
}
else if (len == 0)
{
std::cout << "服务器断开了连接..." << std::endl; // 服务器断开连接
break;
}
else
{
perror("recv"); // 错误处理
break;
}
sleep(1); // 每隔1秒发送一条数据
}
close(fd); // 关闭套接字
return 0;
}
编译与运行
bash
g++ client.cpp -o client
./client
相关头文件介绍
-
<cstdlib>
:提供了一些常用的标准库函数,源自 C 的stdlib.h
,这些函数与程序控制、内存分配、随机数生成等功能相关。使用到的函数:std::exit(int status)
:终止程序执行,status
用来返回退出状态码,0
表示正常退出,非0
表示异常退出。 无返回值,直接终止程序。
-
<cstring>
:是对 C 语言string.h
的封装,提供了用于操作 C 风格字符串(以'\0'
结尾的字符数组)和内存操作的函数。使用到的函数:ptr = memset(void* ptr, int value, size_t num)
:将指定内存区域的前num
个字节设置为value
。 返回指向ptr
的指针,即被修改的内存区域的起始地址。n = sprintf(char* buffer, const char* format, ...)
:将格式化数据写入buffer
,并返回写入的字符数。返回写入buffer
中的字符数(不包括终止符'\0'
)。len = strlen(const char* str)
:返回 C 风格字符串str
的长度(不包括终止符'\0'
)。 返回str
的长度。
-
<arpa/inet.h>
:提供了一些用于网络编程的工具函数,主要用于 IP 地址与主机字节序、网络字节序的转换。使用到的函数和宏:inet_ntop(int af, const void* src, char* dst, socklen_t size)
:将网络格式(大端序)的二进制 IP 地址转换为可读的点分十进制或冒号分隔的字符串。inet_pton(int af, const char* src, void* dst)
:将可读的点分十进制或冒号分隔的字符串格式的 IP 地址转换为网络格式(大端序)的二进制格式。netshort = htons(uint16_t hostshort)
:将主机字节序(小端序)的 16 位数转换为网络字节序(大端序)。 返回转换后的网络字节序的 16 位数。hostshort = ntohs(uint16_t netshort)
:将网络字节序的 16 位数转换为主机字节序。 返回转换后的主机字节序的 16 位数。INADDR_ANY
:用于表示绑定到所有可用的本地接口(IP 地址为 0.0.0.0)。INET_ADDRSTRLEN
是一个常量,表示用于存储 IPv4 地址的字符串格式的最大长度。其值通常为 16,这是因为 IPv4 地址的最坏情况是点分十进制表示的字符串形式,如 "255.255.255.255",该字符串的长度为 15,加上一个字符串终止符 '\0',总共为 16。
-
<sys/socket.h>
:提供了与套接字编程相关的函数和数据结构,定义了套接字的创建、绑定、监听、接受连接、数据收发等功能。使用到的函数、宏和结构体:-
sockfd = socket(int domain, int type, int protocol)
:创建一个套接字,domain
表示协议族(如 IPv4 , IPv6),type
表示套接字类型(如SOCK_STREAM
表示 TCP),protocol
通常为0
,表示默认协议。 返回新的套接字描述符,失败时返回-1
。 -
result = bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen)
:将套接字绑定到特定地址(IP 和端口)。 返回0
表示成功,返回-1
表示出错。 -
result = listen(int sockfd, int backlog)
:将套接字设置为监听模式,backlog
表示队列中可以等待的最大连接数。 返回0
表示成功,返回-1
表示出错。 -
new_sockfd = accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
:接受连接请求,并返回一个新的套接字,失败时返回-1
。 -
bytes_sent = send(int sockfd, const void* buf, size_t len, int flags)
:通过连接的套接字发送数据。 返回成功发送的字节数,失败时返回-1
。 -
bytes_received = recv(int sockfd, void* buf, size_t len, int flags)
:从连接的套接字接收数据。 返回成功接收的字节数,返回0
表示对方关闭连接,失败时返回-1
。 -
AF_INET
: 是一个常量,用于指定地址族,表示使用 IPv4 地址。 -
SOCK_STREAM
:是一个常量,用于指定套接字类型,表示该套接字将使用 TCP 协议进行流式数据传输。 -
sockaddr_in
:专门用于 IPv4 地址的结构体,包含sin_family
(地址族)、sin_port
(端口号)、sin_addr
(IP 地址)等字段。cppstruct sockaddr_in { short int sin_family; // 地址族 unsigned short int sin_port; // 端口号 (网络字节序) struct in_addr sin_addr; // IP 地址 unsigned char sin_zero[8]; // 填充字段(未使用) };
-
-
<unistd.h>
:是 Unix-like 操作系统的头文件,提供了对系统调用的访问接口,包含文件操作、进程管理等低级功能。使用到的函数:result = close(int fd)
:关闭文件描述符fd
,在网络编程中用于关闭套接字。 返回0
表示成功,返回-1
表示出错。sleep(unsigned int seconds)
:暂停执行当前线程seconds
秒。