在Linux中有两种操作可以终止socket间的进程通信:close
和shutdown
。但这两种函数在使用时有着不同的行为和效果。在网络编程中,正确地选择和使用这些操作至关重要,因为它们直接影响着通信的结束和资源的释放。本文将介绍close
和shutdown
函数,然后举一个实际的例子来说明shutdown
的使用。
文章目录
- 1 close
- 2 shutdown
- 3 实例:shutdown的使用
- 3.1 代码修改
- 3.2 实验结果
- 3.3 完整代码
1 close
close
函数将终止一个套接字的连接(如果已连接上),然后关闭文件描述符,释放相关的资源。close
将关闭数据传输的两个方向,之后进程将无法读/写入套接字。
int close(int sockfd);
- 参数:
sockfd
:套接字文件描述符。
- 返回值:
- 成功:0
- 失败:-1,错误号存储于
errno
中。
注意,close
只是将套接字引用计数减一,当引用计数变为零时,真正关闭套接字。
- 套接字引用计数: 在Linux中,每个打开的套接字都有一个引用计数。引用计数跟踪着对套接字的引用次数,即有多少个文件描述符指向同一个套接字。
close
函数的作用: 当调用close
函数关闭一个套接字时,实际上只是将套接字的引用计数减一。这意味着释放了一个对套接字的引用,但套接字仍然存在。
这种机制允许多个进程或线程共享同一个套接字,这个在利用fork实现服务端与多个客户端建立连接中有出现过,使用fork
创建子进程的时候,父进程的上下文被拷贝,所以在子进程中的一开始就要close
父进程的socket,否则父进程close
自身套接字时,套接字并不能真正的被回收。
2 shutdown
shutdown
函数用于关闭一个已连接的套接字或禁止在套接字上的发送或接收数据。该函数可以在全双工(TCP)套接字上单独关闭读取或写入,也可以同时关闭两者。
int shutdown(int sockfd, int how);
- 参数:
sockfd
:套接字文件描述符。how
:关闭方式,可以取值为:SHUT_RD
:关闭读取SHUT_WR
:关闭写入SHUT_RDWR
:同时关闭读取和写入
- 返回值:
- 成功:0
- 失败:-1,错误号存储于
errno
中。
3 实例:shutdown的使用
参考代码上一节的代码IO复用模型之select原理及例子:客户端接收stdin
的输入然后发给服务端,服务端收到这个消息后再回显给客户端。
3.1 代码修改
目的:在客户端发送消息给服务端后,等待服务端返回回显数据后,客户端就退出程序。
1、客户端
首先我们希望在标准输入收到EOF时,客户端关闭写方向和标准输入的接收,等待服务端的最后一次回复,收到后打印出来。
2、服务端
服务端在收到消息后,延时5s,此时在客户端输入EOF,这时客户端关闭掉了写方向。等到延时结束后看看send
的回显能不能被客户端收到。
3.2 实验结果
这样就实现了客户端发送最后一个消息后,等待服务端回复完后就自动退出程序。
3.3 完整代码
服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024
int main() {
int serverSocket, clientSockets[MAX_CLIENTS], maxSockets, activity, i, valread;
int opt = 1;
struct sockaddr_in address;
fd_set readfds;
char buffer[BUFFER_SIZE];
// Create server socket
if ((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Set socket options
if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("Setsockopt failed");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8888);
// Bind the socket
if (bind(serverSocket, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(serverSocket, MAX_CLIENTS) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port 8888\n");
maxSockets = serverSocket;
memset(clientSockets, 0, sizeof(clientSockets));
while (1) {
FD_ZERO(&readfds);
FD_SET(serverSocket, &readfds);
for (i = 0; i < MAX_CLIENTS; i++) {
int clientSocket = clientSockets[i];
if (clientSocket > 0) {
FD_SET(clientSocket, &readfds);
if (clientSocket > maxSockets) {
maxSockets = clientSocket;
}
}
}
activity = select(maxSockets + 1, &readfds, NULL, NULL, NULL);
if (FD_ISSET(serverSocket, &readfds)) {
// Handle new connection
int newSocket;
socklen_t addrlen = sizeof(address);
if ((newSocket = accept(serverSocket, (struct sockaddr*)&address, &addrlen)) < 0) {
perror("Accept failed");
exit(EXIT_FAILURE);
}
printf("New connection, socket fd is %d, ip is : %s, port : %d\n", newSocket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
for (i = 0; i < MAX_CLIENTS; i++) {
if (clientSockets[i] == 0) {
clientSockets[i] = newSocket;
break;
}
}
}
for (i = 0; i < MAX_CLIENTS; i++) {
int clientSocket = clientSockets[i];
if (FD_ISSET(clientSocket, &readfds)) {
// Handle data from client
valread = read(clientSocket, buffer, BUFFER_SIZE);
if (valread == 0) {
// Client disconnected
printf("Host disconnected, socket fd is %d\n", clientSocket);
close(clientSocket);
clientSockets[i] = 0;
} else {
// Echo received message back to client
buffer[valread] = '\0';
printf("Received: %s", buffer);
sleep(5);
printf("sleep over\n");
send(clientSocket, buffer, strlen(buffer), 0);
}
}
}
}
return 0;
}
客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUFFER_SIZE 1024
int main() {
int clientSocket;
struct sockaddr_in serverAddress;
fd_set readfds;
char buffer[BUFFER_SIZE];
// Create client socket
if ((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(8888);
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
// Connect to server
if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) < 0) {
perror("Connection Failed");
exit(EXIT_FAILURE);
}
printf("Connected to server\n");
int stdinOn = 1;
while (1) {
FD_ZERO(&readfds);
if(stdinOn)
FD_SET(STDIN_FILENO, &readfds);
FD_SET(clientSocket, &readfds);
select(clientSocket + 1, &readfds, NULL, NULL, NULL);
if (FD_ISSET(STDIN_FILENO, &readfds)) {
// Read from stdin and send to server
if(fgets(buffer, BUFFER_SIZE, stdin) == NULL)
{
printf("shutdown\n");
shutdown(clientSocket, SHUT_WR);
stdinOn = 0;
}else
{
send(clientSocket, buffer, strlen(buffer), 0);
}
}
if (FD_ISSET(clientSocket, &readfds)) {
// Read from server and print
memset(buffer, 0, sizeof(buffer));
int len = recv(clientSocket, buffer, BUFFER_SIZE, 0);
if(len == 0)
{
printf("server close\n");
break;
}
printf("Server: %s", buffer);
}
}
return 0;
}