一、TCP套接字编程
1.socket函数
在执行网络 I/O 操作时,无论是服务器还是客户端,都需要调用 `socket` 函数来创建套接字。套接字是网络通信的端点,用于在不同设备之间传输数据。以下是 `socket` 函数的重要部分和示例代码:
(1)函数原型
int socket(int domain, int type, int protocol);
(2)参数
①`domain`:指定通信域,常见的有 `AF_INET`(IPv4)和 `AF_INET6`(IPv6)。
②`type`:指定套接字类型,常见的有 `SOCK_STREAM`(流套接字,用于 TCP)和 `SOCK_DGRAM`(数据报套接字,用于 UDP)。
③`protocol`:指定协议,通常为 0,表示使用默认协议。
(3)返回值
①成功:返回一个非负整数,表示套接字描述符。
②失败:返回 -1,并设置 `errno` 错误码。
(4)示例代码
展示如何使用 `socket` 函数创建一个 TCP 套接字:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 12345
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
int opt = 1;
int addrlen = sizeof(struct sockaddr_in);
// 创建 TCP 套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项,允许地址重用
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 初始化服务器地址结构体
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 绑定套接字到指定地址和端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 5) == -1) {
perror("listen failed");
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d...\n", PORT);
// 接受客户端连接
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, (socklen_t *)&addrlen);
if (client_fd == -1) {
perror("accept failed");
exit(EXIT_FAILURE);
}
printf("Client connected: IP %s, Port %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 关闭套接字
close(client_fd);
close(server_fd);
return 0;
}
(5)代码说明
①创建套接字:
server_fd = socket(AF_INET, SOCK_STREAM, 0);
- `AF_INET`:指定使用 IPv4。
- `SOCK_STREAM`:指定使用 TCP 协议。
- `0`:表示使用默认协议。
②设置套接字选项:
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
- `SOL_SOCKET`:指定操作套接字级别的选项。
- `SO_REUSEADDR`:允许套接字地址重用。
③绑定套接字:
bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
- 将套接字绑定到指定的 IP 地址和端口号。
④监听连接:
listen(server_fd, 5);
- 开始监听连接, backlog 参数设置为 5。
⑤接受连接:
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, (socklen_t *)&addrlen);
- 接受客户端的连接请求。
⑥关闭套接字:
close(client_fd);
close(server_fd);
- 关闭客户端和服务器套接字。
(6)总结
`socket` 函数是网络编程的基础,用于创建套接字。通过创建套接字,服务器和客户端可以进行网络通信。上述示例展示了如何使用 `socket` 函数创建一个 TCP 套接字,并进行简单的网络通信。
2.connect函数
TCP客户端使用connect函数来配置套接字,建立一个与TCP服务器的连接。
(1)函数原型
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
(2)参数
①`sockfd`:套接字描述符,由 `socket` 函数创建。
②`addr`:指向 `struct sockaddr` 类型的指针,包含服务器的地址信息。
③`addrlen`:`addr` 的长度,通常为 `sizeof(struct sockaddr_in)`。
(3)返回值
①成功:返回 0。
②失败:返回 -1,并设置 `errno` 错误码。
(4) 示例代码
以下是一个简单的示例,展示如何使用 `connect` 函数建立与 TCP 服务器的连接:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define SERVER_IP "127.0.0.1"
#define PORT 12345
int main() {
int client_fd;
struct sockaddr_in server_addr;
struct hostent *server;
// 创建 TCP 套接字
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 获取服务器的主机信息
server = gethostbyname(SERVER_IP);
if (server == NULL) {
perror("gethostbyname failed");
exit(EXIT_FAILURE);
}
// 初始化服务器地址结构体
bzero((char *)&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
// 建立连接
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect failed");
close(client_fd);
exit(EXIT_FAILURE);
}
printf("Connected to server at %s:%d\n", SERVER_IP, PORT);
// 关闭套接字
close(client_fd);
return 0;
}
(5)代码说明
①创建套接字:
client_fd = socket(AF_INET, SOCK_STREAM, 0);
- `AF_INET`:指定使用 IPv4。
- `SOCK_STREAM`:指定使用 TCP 协议。
- `0`:表示使用默认协议。
②获取服务器的主机信息:
server = gethostbyname(SERVER_IP);
- `gethostbyname`:获取服务器的主机信息。
③初始化服务器地址结构体:
bzero((char *)&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
- `bzero`:清零 `server_addr` 结构体。
- `sin_family`:指定地址族为 IPv4。
- `sin_port`:指定服务器的端口号,使用 `htons` 转换为网络字节序。
- `sin_addr.s_addr`:指定服务器的 IP 地址,使用 `inet_addr` 转换为网络字节序。
④建立连接:
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect failed");
close(client_fd);
exit(EXIT_FAILURE);
}
- `connect`:建立与服务器的连接。
- 如果连接失败,输出错误信息并关闭套接字。
⑤关闭套接字:
close(client_fd);
- 关闭客户端套接字。
(6)总结
`connect` 函数是 TCP 客户端编程中非常重要的部分,用于建立与服务器的连接。通过创建套接字、获取服务器的主机信息、初始化服务器地址结构体,最后调用 `connect` 函数来建立连接。
3.bind函数
在 TCP 服务器编程中,`bind` 函数用于将套接字绑定到特定的 IP 地址和端口号。
(1)函数原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
(2)参数
①`sockfd`:套接字描述符,由 `socket` 函数创建。
②`addr`:指向 `struct sockaddr` 类型的指针,包含要绑定的地址信息。
③`addrlen`:`addr` 的长度,通常为 `sizeof(struct sockaddr_in)`。
(3)返回值
①成功:返回 0。
②失败:返回 -1,并设置 `errno` 错误码。
(4)示例代码
以下是一个简单的示例,展示如何使用 `bind` 函数将套接字绑定到特定的 IP 地址和端口号:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 12345
int main() {
int server_fd;
struct sockaddr_in server_addr;
// 创建 TCP 套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项,允许地址重用
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 初始化服务器地址结构体
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定套接字到指定地址和端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server bound to port %d\n", PORT);
// 关闭套接字
close(server_fd);
return 0;
}
(5)代码说明
①绑定套接字:
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
- `bind`:将套接字绑定到指定的地址和端口。
- 如果绑定失败,输出错误信息并关闭套接字。
(6)总结
`bind` 函数是 TCP 服务器编程中非常重要的部分,用于将套接字绑定到特定的 IP 地址和端口号。通过创建套接字、设置套接字选项、初始化服务器地址结构体,最后调用 `bind` 函数来绑定套接字。
4.listen函数
在 TCP 服务器编程中,`listen` 函数用于将套接字转换为监听套接字,使其能够接受客户端的连接请求
(1)函数原型
int listen(int sockfd, int backlog);
(2)参数
①`sockfd`:套接字描述符,由 `socket` 函数创建。
②`backlog`:指定连接请求队列的最大长度。
(3)返回值
①成功:返回 0。
②失败:返回 -1,并设置 `errno` 错误码。
(4) 示例代码
以下是一个简单的示例,展示如何使用 `listen` 函数将套接字转换为监听套接字:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 12345
int main() {
int server_fd;
struct sockaddr_in server_addr;
// 创建 TCP 套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项,允许地址重用
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 初始化服务器地址结构体
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定套接字到指定地址和端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server bound to port %d\n", PORT);
// 将套接字设置为监听模式
if (listen(server_fd, 5) == -1) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d...\n", PORT);
// 关闭套接字
close(server_fd);
return 0;
}
(5) 代码说明
①设置监听模式:
if (listen(server_fd, 5) == -1) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
- `listen`:将套接字设置为监听模式。
- `5`:指定连接请求队列的最大长度。
- 如果设置监听模式失败,输出错误信息并关闭套接字。
(6)总结
`listen` 函数是 TCP 服务器编程中非常重要的部分,用于将套接字设置为监听模式,使其能够接受客户端的连接请求。通过创建套接字、设置套接字选项、初始化服务器地址结构体、绑定套接字,最后调用 `listen` 函数来设置监听模式。希望这些信息对你有帮助!如果还有其他问题,请随时提问。
5.accept函数
在 TCP 服务器编程中,`accept` 函数用于接受客户端的连接请求,创建一个新的已连接套接字。
(1)函数原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
(2)参数
①`sockfd`:监听套接字描述符,由 `socket` 函数创建。
②addr`:指向 `struct sockaddr` 类型的指针,用于存储客户端的地址信息。
③`addrlen`:指向 `socklen_t` 类型的指针,用于存储 `addr` 的长度。
(3)返回值
①成功:返回一个新的已连接套接字描述符。
②失败:返回 -1,并设置 `errno` 错误码。
(4)示例代码
以下是一个简单的示例,展示如何使用 `accept` 函数接受客户端的连接请求:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 12345
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
int opt = 1;
int addrlen = sizeof(struct sockaddr_in);
// 创建 TCP 套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项,允许地址重用
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 初始化服务器地址结构体
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定套接字到指定地址和端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server bound to port %d\n", PORT);
// 将套接字设置为监听模式
if (listen(server_fd, 5) == -1) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d...\n", PORT);
// 接受客户端连接
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, (socklen_t *)&addrlen);
if (client_fd == -1) {
perror("accept failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Client connected: IP %s, Port %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 关闭套接字
close(client_fd);
close(server_fd);
return 0;
}
(5)代码说明
①接受客户端连接:
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, (socklen_t *)&addrlen);
- `accept`:接受客户端的连接请求。
- `client_fd`:返回一个新的已连接套接字描述符。
- `client_addr`:存储客户端的地址信息。
- `addrlen`:存储 `client_addr` 的长度。
(6)总结
`accept` 函数是 TCP 服务器编程中非常重要的部分,用于接受客户端的连接请求,创建一个新的已连接套接字。通过创建套接字、设置套接字选项、初始化服务器地址结构体、绑定套接字、设置监听模式,最后调用 `accept` 函数来接受客户端连接。
6.数据传输函数
在网络编程和文件I/O操作中,`read`、`write`、`send` 和 `recv` 是四个非常重要的函数,它们用于在套接字或文件描述符之间传输数据。以下是这四个函数的详细说明和示例代码:
(1)write函数
①函数原型
ssize_t write(int fd, const void *buf, size_t count);
②参数
- `fd`:文件描述符或套接字描述符。
- `buf`:指向要写入的数据的缓冲区。
- `count`:要写入的数据字节数。
③返回值
- 成功:返回实际写入的字节数。
- 失败:返回 -1,并设置 `errno` 错误码。
(2)read函数
①函数原型
ssize_t read(int fd, void *buf, size_t count);
②参数
- `fd`:文件描述符或套接字描述符。
- `buf`:指向接收数据的缓冲区。
- `count`:要读取的最大字节数。
③返回值
- 成功:返回实际读取的字节数。
- 失败:返回 -1,并设置 `errno` 错误码。
- 文件结束:返回 0。
(3)send函数
①函数原型
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
②参数
- `sockfd`:套接字描述符。
- `buf`:指向要发送的数据的缓冲区。
- `len`:要发送的数据字节数。
- `flags`:控制标志,通常为 0。
③返回值
- 成功:返回实际发送的字节数。
- 失败:返回 -1,并设置 `errno` 错误码。
(4)recv函数
①函数原型
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
②参数
- `sockfd`:套接字描述符。
- `buf`:指向接收数据的缓冲区。
- `len`:要接收的最大字节数。
- `flags`:控制标志,通常为 0。
③返回值
- 成功:返回实际接收的字节数。
- 失败:返回 -1,并设置 `errno` 错误码。
- 连接关闭:返回 0。
(5)示例代码
以下是一个简单的示例,展示如何使用 `write`、`read`、`send` 和 `recv` 函数在客户端和服务器之间传输数据:
①服务器端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 12345
#define MAXDATASIZE 100
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
int opt = 1;
int addrlen = sizeof(struct sockaddr_in);
char buf[MAXDATASIZE];
// 创建 TCP 套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项,允许地址重用
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 初始化服务器地址结构体
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定套接字到指定地址和端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server bound to port %d\n", PORT);
// 将套接字设置为监听模式
if (listen(server_fd, 5) == -1) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d...\n", PORT);
// 接受客户端连接
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, (socklen_t *)&addrlen);
if (client_fd == -1) {
perror("accept failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Client connected: IP %s, Port %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 接收数据
int numbytes = recv(client_fd, buf, MAXDATASIZE, 0);
if (numbytes == -1) {
perror("recv failed");
close(client_fd);
close(server_fd);
exit(EXIT_FAILURE);
} else if (numbytes == 0) {
printf("Client disconnected\n");
close(client_fd);
close(server_fd);
exit(EXIT_SUCCESS);
}
buf[numbytes] = '\0';
printf("Received: %s\n", buf);
// 发送数据
const char *response = "Server received your message";
numbytes = send(client_fd, response, strlen(response), 0);
if (numbytes == -1) {
perror("send failed");
close(client_fd);
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Sent: %s\n", response);
// 关闭套接字
close(client_fd);
close(server_fd);
return 0;
}
②客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define SERVER_IP "127.0.0.1"
#define PORT 12345
#define MAXDATASIZE 100
int main() {
int client_fd;
struct sockaddr_in server_addr;
struct hostent *server;
char buf[MAXDATASIZE];
// 创建 TCP 套接字
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 获取服务器的主机信息
server = gethostbyname(SERVER_IP);
if (server == NULL) {
perror("gethostbyname failed");
exit(EXIT_FAILURE);
}
// 初始化服务器地址结构体
bzero((char *)&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
// 建立连接
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect failed");
close(client_fd);
exit(EXIT_FAILURE);
}
printf("Connected to server at %s:%d\n", SERVER_IP, PORT);
// 发送数据
const char *message = "Hello, Server";
int numbytes = send(client_fd, message, strlen(message), 0);
if (numbytes == -1) {
perror("send failed");
close(client_fd);
exit(EXIT_FAILURE);
}
printf("Sent: %s\n", message);
// 接收数据
numbytes = recv(client_fd, buf, MAXDATASIZE, 0);
if (numbytes == -1) {
perror("recv failed");
close(client_fd);
exit(EXIT_FAILURE);
} else if (numbytes == 0) {
printf("Server disconnected\n");
close(client_fd);
exit(EXIT_SUCCESS);
}
buf[numbytes] = '\0';
printf("Received: %s\n", buf);
// 关闭套接字
close(client_fd);
return 0;
}
(6)总结
`write`、`read`、`send` 和 `recv` 函数是网络编程和文件I/O操作中非常重要的部分,用于在套接字或文件描述符之间传输数据。通过创建套接字、设置套接字选项、初始化服务器地址结构体、绑定套接字、设置监听模式、接受客户端连接,最后调用这些函数来传输数据。
二、服务器的三种异常情况
1. 服务器主机崩溃
(1)情况描述
服务器主机突然崩溃,可能是由于硬件故障、操作系统崩溃或网络故障等原因导致的。此时,服务器无法继续运行,所有连接到该服务器的客户端连接都将被中断。
(2)对客户端的影响
- **连接中断**:客户端与服务器的连接将被突然中断,客户端无法继续发送或接收数据。
- **错误提示**:客户端可能会收到一个连接错误,例如 `Connection reset by peer` 或 `Connection timed out`。
- **数据丢失**:如果客户端在服务器崩溃时正在发送或接收数据,可能会导致数据丢失或不完整。
(3)示例代码(客户端处理)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define SERVER_IP "127.0.0.1"
#define PORT 12345
#define MAXDATASIZE 100
int main() {
int client_fd;
struct sockaddr_in server_addr;
struct hostent *server;
char buf[MAXDATASIZE];
// 创建 TCP 套接字
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 获取服务器的主机信息
server = gethostbyname(SERVER_IP);
if (server == NULL) {
perror("gethostbyname failed");
exit(EXIT_FAILURE);
}
// 初始化服务器地址结构体
bzero((char *)&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
// 建立连接
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect failed");
close(client_fd);
exit(EXIT_FAILURE);
}
printf("Connected to server at %s:%d\n", SERVER_IP, PORT);
// 发送数据
const char *message = "Hello, Server";
int numbytes = send(client_fd, message, strlen(message), 0);
if (numbytes == -1) {
perror("send failed");
close(client_fd);
exit(EXIT_FAILURE);
}
printf("Sent: %s\n", message);
// 接收数据
numbytes = recv(client_fd, buf, MAXDATASIZE, 0);
if (numbytes == -1) {
perror("recv failed");
close(client_fd);
exit(EXIT_FAILURE);
} else if (numbytes == 0) {
printf("Server disconnected\n");
close(client_fd);
exit(EXIT_SUCCESS);
}
buf[numbytes] = '\0';
printf("Received: %s\n", buf);
// 关闭套接字
close(client_fd);
return 0;
}
(4)处理方法
- **重连机制**:客户端可以实现重连机制,定期尝试重新连接到服务器。
- **数据重传**:如果数据发送失败,客户端可以尝试重新发送数据。
- **超时处理**:设置超时时间,如果在超时时间内没有收到服务器的响应,客户端可以认为连接已中断并采取相应的措施。
2. 服务器主机崩溃后重启
(1)情况描述
服务器主机崩溃后重新启动,此时服务器的套接字和连接信息将被重置。客户端的连接将被中断,但服务器重新启动后,客户端可以重新连接。
(2)对客户端的影响
- **连接中断**:客户端与服务器的连接将被中断,客户端无法继续发送或接收数据。
- **错误提示**:客户端可能会收到一个连接错误,例如 `Connection reset by peer` 或 `Connection timed out`。
- **数据丢失**:如果客户端在服务器崩溃时正在发送或接收数据,可能会导致数据丢失或不完整。
(3)处理方法
- **重连机制**:客户端可以实现重连机制,定期尝试重新连接到服务器。
- **数据重传**:如果数据发送失败,客户端可以尝试重新发送数据。
- **超时处理**:设置超时时间,如果在超时时间内没有收到服务器的响应,客户端可以认为连接已中断并采取相应的措施。
3. 服务器关闭
(1)情况描述
服务器正常关闭,可能是由于服务器程序正常退出或管理员手动关闭服务器。此时,服务器将主动关闭所有连接到它的客户端连接。
(2)对客户端的影响
- **连接关闭**:客户端与服务器的连接将被正常关闭,客户端无法继续发送或接收数据。
- **错误提示**:客户端可能会收到一个连接关闭的通知,例如 `Connection closed by server`。
- **数据完整性**:如果客户端在服务器关闭前已经发送或接收了所有数据,数据完整性可以得到保证。
(3)示例代码(客户端处理)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define SERVER_IP "127.0.0.1"
#define PORT 12345
#define MAXDATASIZE 100
int main() {
int client_fd;
struct sockaddr_in server_addr;
struct hostent *server;
char buf[MAXDATASIZE];
// 创建 TCP 套接字
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 获取服务器的主机信息
server = gethostbyname(SERVER_IP);
if (server == NULL) {
perror("gethostbyname failed");
exit(EXIT_FAILURE);
}
// 初始化服务器地址结构体
bzero((char *)&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
// 建立连接
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect failed");
close(client_fd);
exit(EXIT_FAILURE);
}
printf("Connected to server at %s:%d\n", SERVER_IP, PORT);
// 发送数据
const char *message = "Hello, Server";
int numbytes = send(client_fd, message, strlen(message), 0);
if (numbytes == -1) {
perror("send failed");
close(client_fd);
exit(EXIT_FAILURE);
}
printf("Sent: %s\n", message);
// 接收数据
numbytes = recv(client_fd, buf, MAXDATASIZE, 0);
if (numbytes == -1) {
perror("recv failed");
close(client_fd);
exit(EXIT_FAILURE);
} else if (numbytes == 0) {
printf("Server disconnected\n");
close(client_fd);
exit(EXIT_SUCCESS);
}
buf[numbytes] = '\0';
printf("Received: %s\n", buf);
// 关闭套接字
close(client_fd);
return 0;
}
(4)处理方法
- **重连机制**:客户端可以实现重连机制,定期尝试重新连接到服务器。
- **数据重传**:如果数据发送失败,客户端可以尝试重新发送数据。
- **超时处理**:设置超时时间,如果在超时时间内没有收到服务器的响应,客户端可以认为连接已中断并采取相应的措施。
4.总结
服务器的三种异常情况对客户端的影响主要体现在连接中断和数据丢失上。客户端可以通过实现重连机制、数据重传和超时处理来应对这些异常情况。