BSD套接字编程是Unix和Linux网络编程的基础,它允许程序通过网络进行通信。BSD套接字API提供了多种系统调用,支持基于TCP和UDP协议的网络通信。以下是对套接字编程中常用系统调用的介绍,包括如何创建、绑定、监听、连接、发送、接收以及关闭套接字。
我们主要讲解以下系统调用:
socket()
bind()
listen()
accept()
connect()
send()
/recv()
close()
1. socket()
socket()
用于创建一个套接字,套接字是网络通信的基础。
int socket(int domain, int type, int protocol);
- domain(协议域/协议族):指定使用的协议族,如
AF_INET
(IPv4)或AF_INET6
(IPv6)。 - type(套接字类型):
SOCK_STREAM
:流式套接字,通常用于TCP。SOCK_DGRAM
:数据报套接字,通常用于UDP。
- protocol(协议):通常为 0,表示默认协议。
返回值:成功时返回套接字的文件描述符,失败时返回 -1。
示例:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
2. bind()
bind()
将套接字绑定到一个本地地址(IP地址和端口号)。对于服务器端程序,通常会在bind()
之后调用listen()
等待连接。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:
socket()
函数返回的套接字文件描述符。 - addr:包含本地地址和端口号的
struct sockaddr_in
结构体。 - addrlen:地址结构体的大小。
返回值:成功时返回0,失败时返回-1。
示例:
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // 绑定到本地IP地址
address.sin_port = htons(8080); // 绑定到端口8080
if (bind(sockfd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
3. listen()
listen()
用于将套接字设置为被动模式,等待客户端连接。
int listen(int sockfd, int backlog);
- sockfd:套接字文件描述符。
- backlog:队列中允许等待的最大连接数。
返回值:成功时返回0,失败时返回-1。
示例:
if (listen(sockfd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
4. accept()
accept()
用于从已连接的队列中提取下一个连接,返回一个新的套接字,用于与客户端进行通信。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd:监听套接字。
- addr:客户端的地址信息。
- addrlen:地址结构体的大小。
返回值:成功时返回新连接的套接字文件描述符,失败时返回-1。
示例:
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int new_sock = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if (new_sock < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
5. connect()
connect()
用于客户端主动连接服务器。此调用适用于客户端程序。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:由
socket()
创建的套接字。 - addr:服务器的地址信息。
- addrlen:地址结构体的大小。
返回值:成功时返回0,失败时返回-1。
示例:
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
// 转换IP地址
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
printf("Invalid address/ Address not supported \n");
return -1;
}
if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("Connection Failed \n");
return -1;
}
6. send() / recv()
用于发送和接收数据。
send()
send()
用于向已连接的套接字发送数据。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- sockfd:连接的套接字。
- buf:要发送的数据。
- len:发送的数据长度。
- flags:一般为0,表示默认行为。
返回值:成功时返回发送的字节数,失败时返回-1。
示例:
const char *message = "Hello, Server!";
send(sockfd, message, strlen(message), 0);
recv()
recv()
用于从已连接的套接字接收数据。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- sockfd:连接的套接字。
- buf:接收数据的缓冲区。
- len:缓冲区的大小。
- flags:一般为0。
返回值:成功时返回接收的字节数,失败时返回-1。
示例:
char buffer[1024] = {0};
int bytes_read = recv(sockfd, buffer, sizeof(buffer), 0);
printf("Received: %s\n", buffer);
7. close()
close()
用于关闭套接字,释放与之关联的资源。
int close(int sockfd);
- sockfd:要关闭的套接字文件描述符。
返回值:成功时返回0,失败时返回-1。
示例:
close(sockfd);
完整示例:TCP服务器和客户端
服务器端:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[1024] = {0};
const char *hello = "Hello from server";
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 绑定地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
// 接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
// 接收数据
int bytes_read = recv(new_socket, buffer, 1024, 0);
printf("Message received: %s\n", buffer);
// 发送数据
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 关闭套接字
close(new_socket);
close(server_fd);
return 0;
}
客户端:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[1024] = {0};
const char *hello = "Hello from client";
// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("Socket creation error\n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
// 转换IP地址
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
printf("Invalid address/ Address not supported\n");
return -1;
}
//
连接服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("Connection Failed\n");
return -1;
}
// 发送数据
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 接收数据
int bytes_read = recv(sock, buffer, 1024, 0);
printf("Message received: %s\n", buffer);
// 关闭套接字
close(sock);
return 0;
}
总结:
- socket() 创建套接字。
- bind() 绑定IP地址和端口。
- listen() 设置套接字为监听状态。
- accept() 接受客户端的连接。
- connect() 用于客户端连接服务器。
- send()/recv() 用于发送和接收数据。
- close() 关闭套接字。