套接字通信基础
概述
套接字通信是在Linux系统中实现进程间通信(IPC)的一种方式,它可以在同一主机或不同主机上的进程之间传递数据。在本文中,我们将介绍套接字通信的基本概念、实现方法和常见应用场景,并提供相应的代码示例,以便初学者可以快速掌握该技术。
套接字通信基础
套接字类型
在Linux系统中,有两种类型的套接字:流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)。
流式套接字提供一个面向连接的、可靠的、基于字节流的双向数据传输通道。它通过TCP协议实现,支持数据传输的顺序和完整性保证。
数据报套接字提供无连接的、不可靠的、基于数据报的双向数据传输通道。它通过UDP协议实现,适用于数据量较小的通信场景。
地址族
套接字通信还需要使用地址族来指定网络地址和端口号。常用的地址族有IPv4(AF_INET)和IPv6(AF_INET6)。
socket模型创建流程分析
套接字创建socket()
套接字的创建可以通过socket()系统调用完成。其基本语法如下:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数说明:
domain:地址族,可以是AF_INET或AF_INET6,AF_UNIX。
type:套接字类型,可以是SOCK_STREAM或SOCK_DGRAM。
protocol:协议,可以是IPPROTO_TCP或IPPROTO_UDP。如果指定为0,系统会根据type自动选择默认协议。
成功创建套接字后,返回一个非负整数的文件描述符,
失败时返回-1。
套接字绑定bind()
套接字的绑定可以通过bind()系统调用完成。其基本语法如下:
`
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
sockfd:套接字文件描述符。
addr:指向要绑定的本地地址的结构体指针,类型为sockaddr_in或sockaddr_in6。
addrlen:本地地址结构体的长度,通常是sizeof(sockaddr_in)或sizeof(sockaddr_in6)。
套接字监听listen()
如果需要在流式套接字上接受连接请求,需要先将套接字设置为监听状态。这可以通过listen()系统调用完成。listen可以设置同时与服务器建立连接的上限数。(同时进行3次握手的客户端数量)
其基本语法如下:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数说明:
sockfd:套接字文件描述符。
backlog:指定等待连接的队列长度。
套接字接受连接请求accept
在流式套接字上监听到连接请求后,可以使用accept()系统调用接受连接请求,创建一个新的套接字用于和客户端进行通信。故一次通信至少有三个套接字,创建完后accept返回继续监听请求(深藏功与名)
其基本语法如下:
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
sockfd:套接字文件描述符。
addr:指向存放客户端地址的结构体指针,类型为sockaddr_in或sockaddr_in6。是一个传出参数
addrlen:客户端地址结构体的长度,通常是sizeof(sockaddr_in)或sizeof(sockaddr_in6)。传入传出。 (入:addr的大小。 出:客户端addr实际大小。)
成功接受连接请求后,返回一个新的文件描述符,用于和客户端进行通信。失败时返回-1。
套接字连接
在流式套接字上向指定的目标主机发起连接请求,可以使用connect()系统调用。其基本语法如下:
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
sockfd:套接字文件描述符。
addr:指向要连接的远程主机地址的结构体指针,类型为sockaddr_in或sockaddr_in6。
addrlen:远程主机地址结构体的长度,通常是sizeof(sockaddr_in)或sizeof(sockaddr_in6)。
成功建立连接后,返回0,失败时返回-1。
套接字数据传输
套接字的数据传输可以使用send()和recv()系统调用完成。其基本语法如下:
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数说明:
sockfd:套接字文件描述符。
buf:指向数据缓冲区的指针。
len:数据长度。
flags:传输标志,可以是0或MSG_DONTWAIT等。
成功发送或接收数据后,返回实际传输的数据长度,失败时返回-1。
套接字通信实例
下面是一个简单的套接字通信实例,使用TCP协议进行数据传输。
服务器端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
char buffer[BUFFER_SIZE];
int len;
// 创建套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 绑定本地地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// 监听连接请求
if (listen(server_fd, 5) ==-1) {
perror("listen");
exit(EXIT_FAILURE);
}
// 等待连接请求
printf("Server is listening on port %d...\n", PORT);
socklen_t client_addr_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_fd == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
// 读取客户端发来的数据
memset(buffer, 0, sizeof(buffer));
len = recv(client_fd, buffer, sizeof(buffer), 0);
if (len == -1) {
perror("recv");
exit(EXIT_FAILURE);
}
printf("Received message from client: %s\n", buffer);
// 向客户端发送数据
strcpy(buffer, "Hello from server!");
len = send(client_fd, buffer, strlen(buffer), 0);
if (len == -1) {
perror("send");
exit(EXIT_FAILURE);
}
printf("Sent message to client: %s\n", buffer);
// 关闭套接字
close(client_fd);
close(server_fd);
return 0;
}
小知识:如果只写了服务端代码,可以用以下命令模拟客户端进行测试
nc 127.0.0.1 $(端口号)
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1"
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
int client_fd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
int len;
// 创建套接字
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 连接服务器
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
server_addr.sin_port = htons(PORT);
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect");
exit(EXIT_FAILURE);
}
// 向服务器发送数据
strcpy(buffer, "Hello from client!");
len = send(client_fd, buffer, strlen(buffer), 0);
if (len == -1) {
perror("send");
exit(EXIT_FAILURE);
}
printf("Sent message to server: %s\n", buffer);
// 读取服务器返回的数据
memset(buffer, 0, sizeof(buffer));
len = recv(client_fd, buffer, sizeof(buffer), 0);
if (len == -1) {
perror("recv");
exit(EXIT_FAILURE);
}
printf("Received message from server: %s\n", buffer);
// 关闭套接字
close(client_fd);
return 0;
}
在上述代码中,服务器端首先创建一个套接字,然后绑定到本地地址,并监听连接请求。当有客户端发起连接请求后,服务器端调用accept()系统调用接受连接请求,并创建一个新的套接字与客户端进行通信。
客户端首先创建一个套接字,然后连接到服务器端,向服务器端发送数据,接收服务器端的返回数据,并关闭套接字。
这个实例中了解了套接字通信的基本原理和实现,下面来介绍一些套接字通信的应用场景。
应用场景
网络编程
套接字通信是网络编程中最基础的通信方式之一,网络编程可以实现客户端与服务器之间的通信,也可以实现两个客户端之间的通信。
比如一个聊天室的应用程序,可以用套接字通信实现多个客户端之间的实时通信。
进程间通信
套接字通信也可以用于进程间通信,不同进程之间可以通过套接字来交换数据。
比如,两个进程之间可以通过套接字通信实现父进程和子进程之间的数据传输,或者两个独立的进程之间通过套接字通信实现数据交换。
跨平台通信
套接字通信可以跨越不同的操作系统和编程语言来实现通信,因为套接字通信是基于标准的网络协议实现的。
这意味着,在不同的操作系统和编程语言中,只要遵循相同的网络协议,就可以使用套接字通信进行通信。
总结
本文介绍了套接字通信的基本原理和实现方法,包括套接字通信的类型、套接字通信的流程、以及套接字通信的代码实现。
套接字通信是网络编程中最基础的通信方式之一,也可以用于进程间通信和跨平台通信等多种场景。通过套接字通信,不同的客户端和服务器之间可以进行实时通信和数据交换,为不同的应用场景提供了强大的支持。