对于Linux网络编程,最重要的就是socket接口,以及配合使用的select、poll、epoll等事件管理机制;共同构成了网络编程的基本框架。
socket的主要接口主要分为UDP和TCP两种,它们接口类似,不过TCP支持可靠连接,而UDP则不支持,所以接口有些差别。
tcp_socket的接口分为两部分,服务器端的接口如下:
// 创建socket
// @param domain: 协议族
// @param type: 套接字类型
// @param protocol: 协议类型
// @return: socket句柄
int socket (int __domain, int __type, int __protocol)
/*
AF_INET表示IPv4协议族, AF_INET6表示IPv6协议族,AF_UNIX表示UNIX域套接字
SOCK_STREAM表示面向流的套接字, 支持TCP协议
SOCK_DGRAM表示面向数据报的套接字, 支持UDP协议
SOCK_RAW表示原始套接字, 支持IP协议
*/
// 绑定socket
// @param sockfd: socket句柄
// @param addr: 服务器端设备地址
// @param addrlen: 地址长度
// @return: 0成功,-1失败
int bind (int __fd, const struct sockaddr *__addr, socklen_t __len)
// 监听socket
// @param sockfd: socket句柄
// @param backlog: 最大连接数
// @return: 0成功,-1失败
int listen (int __fd, int __n)
// 接受客户端连接
// @param sockfd: socket句柄
// @param addr: 客户端设备地址
// @param addrlen: 地址长度
// @return: 新的socket句柄
int accept (int __fd, struct sockaddr *__restrict __addr, socklen_t *__restrict __len)
// 发送数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 发送标志
// @return: 发送的字节数
ssize_t send (int __fd, const void *__buf, size_t __n, int __flags)
// 接收数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 接收标志
// @return: 接收的字节数
ssize_t recv (int __fd, void *__restrict __buf, size_t __n, int __flags)
// 关闭socket
// @param sockfd: socket句柄
// @return: 0成功,-1失败
int close (int __fd)
对于客户端就连接服务器,进行通讯,接口如下:
// 创建socket
// @param domain: 协议族
// @param type: 套接字类型
// @param protocol: 协议类型
// @return: socket句柄
int socket (int __domain, int __type, int __protocol)
// 连接服务器
// @param sockfd: socket句柄
// @param addr: 服务器端设备地址
// @param addrlen: 地址长度
// @return: 0成功,-1失败
int connect (int __fd, const struct sockaddr *__addr, socklen_t __len)
// 发送数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 发送标志
// @return: 发送的字节数
ssize_t send (int __fd, const void *__buf, size_t __n, int __flags)
// 接收数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 接收标志
// @return: 接收的字节数
ssize_t recv (int __fd, void *__restrict __buf, size_t __n, int __flags)
// 关闭socket
// @param sockfd: socket句柄
// @return: 0成功,-1失败
int close (int __fd)
服务器代码如下所示。
// tcp服务器端代码
int tcp_server_task(char *ipaddr, int port)
{
int sockfd;
int flag;
struct sockaddr_in server_addr, client_addr;
// 创建socket,TCP协议
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
printf("socket error\n");
return -1;
}
// 设置socket属性,允许地址重用
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ipaddr);
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { // 绑定socket
printf("bind error\n");
return -1;
}
listen(sockfd, 1);
while (1) {
int client_len = sizeof(client_addr);
int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len); // 接受客户端连接
if (client_fd < 0) {
printf("accept error\n");
return -1;
}
printf("client ip: %s, port: %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
while (1) {
char buf[1024] = {0};
int len = recv(client_fd, buf, sizeof(buf), 0); // 接收数据
if (len < 0) {
printf("recv error\n");
continue;
} else if (len == 0) {
printf("client close\n");
break;
} else {
// 发送数据
send(client_fd, buf, len, 0);
}
printf("recv: %s\n", buf);
}
}
}
客户端代码如下所示。
// tcp客户端代码
int tcp_client_task(char *ipaddr, int port)
{
int sockfd;
struct sockaddr_in server_addr;
// 创建socket,TCP协议
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
printf("socket error\n");
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ipaddr);
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { // 连接服务器
printf("connect error\n");
return -1;
}
while (1) {
char buf[1024] = {0};
printf("send: ");
scanf("%s", buf);
send(sockfd, buf, strlen(buf), 0); // 发送数据
int len = recv(sockfd, buf, sizeof(buf), 0); // 接收数据
if (len < 0) {
printf("recv error\n");
continue;
} else {
printf("recv: %s\n", buf);
sleep(10);
}
}
}
udp_socket的接口和TCP类似,不过UDP没有连接的概念,所以接口也有所不同。
// 创建socket
// @param domain: 协议族
// @param type: 套接字类型
// @param protocol: 协议类型
// @return: socket句柄
int socket (int __domain, int __type, int __protocol)
// 绑定socket
// @param sockfd: socket句柄
// @param addr: 服务器端设备地址
// @param addrlen: 地址长度
// @return: 0成功,-1失败
int bind (int __fd, const struct sockaddr *__addr, socklen_t __len)
// 发送数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 发送标志
// @param to: 目标地址
// @param addrlen: 地址长度
// @return: 发送的字节数
ssize_t sendto (int __fd, const void *__buf, size_t __n, int __flags, const struct sockaddr *__dest_addr, socklen_t __dest_len)
// 接收数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 接收标志
// @param from: 来源地址
// @param addrlen: 地址长度
// @return: 接收的字节数
ssize_t recvfrom (int __fd, void *__restrict __buf, size_t __n, int __flags, struct sockaddr *__restrict __addr, socklen_t *__restrict __len)
// 关闭socket
// @param sockfd: socket句柄
// @return: 0成功,-1失败
int close (int __fd)
UDP接收端接口如下所示。
// 创建socket
// @param domain: 协议族
// @param type: 套接字类型
// @param protocol: 协议类型
// @return: socket句柄
int socket (int __domain, int __type, int __protocol)
// 连接服务器
// @param sockfd: socket句柄
// @param addr: 服务器端设备地址
// @param addrlen: 地址长度
// @return: 0成功,-1失败
int connect (int __fd, const struct sockaddr *__addr, socklen_t __len)
// 发送数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 发送标志
// @param to: 目标地址
// @param addrlen: 地址长度
// @return: 发送的字节数
ssize_t send (int __fd, const void *__buf, size_t __n, int __flags)
// 接收数据
// @param sockfd: socket句柄
// @param buf: 数据缓冲区
// @param len: 数据长度
// @param flags: 接收标志
// @param from: 来源地址
// @param addrlen: 地址长度
// @return: 接收的字节数
ssize_t recv (int __fd, void *__restrict __buf, size_t __n, int __flags)
// 关闭socket
// @param sockfd: socket句柄
// @return: 0成功,-1失败
int close (int __fd)
UDP服务器的代码如下所示。
// udp服务器端代码
int udp_server_task(char *ipaddr, int port)
{
int sockfd;
struct sockaddr_in server_addr, client_addr;
// 创建socket,UDP协议
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
printf("socket error\n");
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ipaddr);
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { // 绑定socket
printf("bind error\n");
return -1;
}
while (1) {
int client_len = sizeof(client_addr);
char buf[1024] = {0};
int len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &client_len); // 接收数据
if (len < 0) {
printf("recv error\n");
continue;
} else {
printf("client ip: %s, port: %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
printf("recv: %s\n", buf);
// 发送数据
sendto(sockfd, buf, len, 0, (struct sockaddr *)&client_addr, client_len);
}
}
}
UDP客户端的代码如下所示。
// udp客户端代码
int udp_client_task(char *ipaddr, int port)
{
int sockfd;
struct sockaddr_in server_addr;
// 创建socket,UDP协议
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
printf("socket error\n");
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ipaddr);
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { // 连接服务器
printf("connect error\n");
return -1;
}
while (1) {
char buf[1024] = {0};
printf("send: ");
scanf("%s", buf);
send(sockfd, buf, strlen(buf), 0); // 发送数据
int len = recv(sockfd, buf, sizeof(buf), 0); // 接收数据
if (len < 0) {
printf("recv error\n");
continue;
}
printf("recv: %s\n", buf);
}
close(sockfd);
}
对于socket编程,最重要的就是事件管理机制,对于socket编程,最常用的事件管理机制就是select、poll、epoll。
select的接口如下所示。
// 清空文件描述符集合
// @param fdset: 文件描述符集合
void FD_ZERO (fd_set *__fdset)
// 添加文件描述符到集合
// @param fd: 文件描述符
// @param fdset: 文件描述符集合
void FD_SET (int __fd, fd_set *__fdset)
// 等待事件
// @param nfds: 最大的文件描述符+1
// @param readfds: 可读事件的文件描述符集合
// @param writefds: 可写事件的文件描述符集合
// @param exceptfds: 异常事件的文件描述符集合
// @param timeout: 超时时间
// @return: 就绪的文件描述符数量
int select (int __nfds, fd_set *__restrict __readfds, fd_set *__restrict __writefds, fd_set *__restrict __exceptfds, struct timeval *__restrict __timeout)
使用select的TCP服务器的代码如下所示。
// 使用select的TCP服务器
int tcp_server_with_select(char *ipaddr, int port)
{
int sockfd, max_fd;
fd_set read_fds;
struct sockaddr_in server_addr;
int client_fds[FD_SETSIZE]; // 客户端fd数组
int i;
// 初始化客户端fd数组
for (i = 0; i < FD_SETSIZE; i++) {
client_fds[i] = -1;
}
// 创建socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
printf("socket error\n");
return -1;
}
// 绑定地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ipaddr);
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
printf("bind error\n");
return -1;
}
// 监听
listen(sockfd, 5);
while (1) {
// 清空fd集合
FD_ZERO(&read_fds);
// 添加服务器socket到集合
FD_SET(sockfd, &read_fds);
max_fd = sockfd;
// 添加所有客户端socket到集合
for (i = 0; i < FD_SETSIZE; i++) {
if (client_fds[i] > 0) {
FD_SET(client_fds[i], &read_fds);
if (client_fds[i] > max_fd) {
max_fd = client_fds[i];
}
}
}
// 等待事件
int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (ret < 0) {
printf("select error\n");
continue;
}
// 检查是否有新连接
if (FD_ISSET(sockfd, &read_fds)) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if (new_fd < 0) {
printf("accept error\n");
continue;
}
// 将新连接添加到客户端数组
for (i = 0; i < FD_SETSIZE; i++) {
if (client_fds[i] < 0) {
client_fds[i] = new_fd;
break;
}
}
if (i == FD_SETSIZE) {
printf("too many clients\n");
close(new_fd);
}
}
// 处理客户端数据
for (i = 0; i < FD_SETSIZE; i++) {
if (client_fds[i] > 0 && FD_ISSET(client_fds[i], &read_fds)) {
char buf[1024] = {0};
int len = recv(client_fds[i], buf, sizeof(buf), 0);
if (len <= 0) {
// 客户端断开连接
close(client_fds[i]);
client_fds[i] = -1;
} else {
// 处理数据
send(client_fds[i], buf, len, 0);
}
}
}
}
close(sockfd);
return 0;
}
使用select的UDP服务器的代码如下所示。
// 使用select的UDP服务器
int udp_server_with_select(char *ipaddr, int port)
{
int sockfd, max_fd;
fd_set read_fds;
struct sockaddr_in server_addr, client_addr;
// 创建socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
printf("socket error\n");
return -1;
}
// 绑定地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ipaddr);
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
printf("bind error\n");
return -1;
}
while (1) {
// 清空fd集合
FD_ZERO(&read_fds);
// 添加服务器socket到集合
FD_SET(sockfd, &read_fds);
max_fd = sockfd;
// 等待事件
int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (ret < 0) {
printf("select error\n");
continue;
}
// 检查是否有新连接
if (FD_ISSET(sockfd, &read_fds)) {
socklen_t client_len = sizeof(client_addr);
char buf[1024] = {0};
int len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &client_len);
if (len < 0) {
printf("recvfrom error\n");
continue;
}
// 处理数据
sendto(sockfd, buf, len, 0, (struct sockaddr *)&client_addr, client_len);
}
}
close(sockfd);
}
本章全面介绍了Linux网络编程的核心技术,重点讲解了TCP和UDP两种主要协议的socket编程接口及其实现原理。通过详细的代码示例,展示了如何构建TCP/UDP服务器和客户端,并深入探讨了select事件管理机制在多路复用中的应用。此外,本章还涵盖了网络编程中的关键概念,如地址族、端口绑定、数据收发等,为开发者提供了从基础到实践的完整指导。这些知识是构建高效、可靠网络应用程序的基石,为后续学习更高级的网络编程技术奠定了坚实基础。
直接开始下一节说明: 进程间通讯方法