套接字(Socket)
不仅可以用于同一台主机上的进程间通信,还可以用于跨网络的进程间通信。套接字可以使用不同的协议,如TCP或UDP。
套接字的基本概念:
- 流式套接字(Stream Sockets):提供有序、可靠、面向连接的通信服务,数据在传输过程中按字节流的方式处理,如 TCP。
- 数据报套接字(Datagram Sockets):提供无序、不可靠、面向数据报的通信服务,每个数据报是一个独立的、有界限的单元,如 UDP。
- 原始套接字(Raw Sockets):允许程序员直接处理底层的网络协议,如 ICMP。
- 序列套接字(Sequenced Sockets):提供有序、可靠的通信服务,但不面向字节流,每个消息都是独立的,如 SCTP。
套接字通信的关键流程:
服务端流程:
- 创建套接字 :使用
socket()
函数创建一个 TCP 套接字。 - 绑定地址 :使用
bind()
函数将套接字与特定的 IP 地址和端口号绑定。 - 监听连接 :使用
listen()
函数使套接字进入监听状态,等待客户端的连接请求。 - 接受连接 :使用
accept()
函数接受客户端的连接请求,创建一个新的通信套接字。 - 数据通信 :使用
send()
和recv()
函数与客户端进行数据通信。 - 关闭连接 :通信结束后,使用
close()
函数关闭套接字。
客户端流程:
- 创建套接字 :使用
socket()
函数创建一个 TCP 套接字。 - 连接服务器 :使用
connect()
函数连接到服务器指定的 IP 地址和端口号。 - 数据通信 :使用
send()
和recv()
函数与服务器进行数据通信。 - 关闭连接 :通信结束后,使用
close()
函数关闭套接字。
套接字的选项和控制:
- 套接字选项:可以通过
setsockopt()
和getsockopt()
系统调用来设置或获取套接字的行为。 - 非阻塞套接字:可以通过设置套接字选项为非阻塞模式,使得
send()
、recv()
和connect()
等操作不会使程序挂起。 - 套接字状态:可以使用
select()
、poll()
或epoll()
等系统调用来监控套接字的状态,如可读、可写或有错误。
套接字通信实验:
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#define PORT 8080
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[1024] = {0};
// 创建套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 绑定地址
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)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 5) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 接受连接
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 数据通信
printf("Server: Connected to client\n");
while (recv(client_fd, buffer, sizeof(buffer), 0) > 0) {
printf("Server: Received message from client\n");
send(client_fd, buffer, strlen(buffer), 0);
memset(buffer, 0, sizeof(buffer));
}
// 关闭连接
close(client_fd);
close(server_fd);
printf("Server quit\n");
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
int main() {
int sock_fd;
struct sockaddr_in server_addr;
char buffer[1024] = {0};
// 创建套接字
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 连接服务器
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect failed");
exit(EXIT_FAILURE);
}
// 数据通信
printf("Client: Connected to server\n");
while (1) {
memset(buffer, 0, sizeof(buffer));
printf("Client: Enter message to send to server (or 'quit' to exit): ");
fgets(buffer, sizeof(buffer), stdin);
if (strncmp(buffer, "quit", 4) == 0) {
break;
}
send(sock_fd, buffer, strlen(buffer), 0);
if (recv(sock_fd, buffer, sizeof(buffer), 0) <= 0) {
perror("recv failed");
break;
}
printf("Server response: %s\n", buffer);
}
// 关闭连接
close(sock_fd);
printf("Client quit\n");
return 0;
}
实验结果截图: