套接字
文章目录
-
- 套接字
-
- [I socket基础](#I socket基础)
- [II Unix domain Socket](#II Unix domain Socket)
-
- [1、Unix domain Socket地址结构](#1、Unix domain Socket地址结构)
- [2、Unix domain 流Socket](#2、Unix domain 流Socket)
- [3、Unix domain 数据报socket](#3、Unix domain 数据报socket)
I socket基础
Socket(套接字)是网络通信的基石,它提供了进程间通信的端点。通过Socket,不同主机或同一主机上的不同进程可以进行数据交换。Socket本质上是一个编程接口(API),它封装了底层网络协议的细节,使开发者能够更方便地进行网络编程。
1、通信domain
socket通过通信domain确定通信范围,使用的协议族等,目前常用的domain包括:

2、通用socket地址结构体
- socket API使用通用地址结构体
sockaddr
来表示不同协议族的地址:
c
#include <sys/socket.h>
struct sockaddr {
sa_family_t sa_family; // address family(AF_xxx)
char sa_data[]; // socket address
};
-
sa_family
字段指定地址族(如AF_INET、AF_INET6等) -
sa_data
字段包含实际的地址信息,其格式和长度由地址族决定 -
通常使用协议特定的地址结构体(如
sockaddr_in
),在调用函数时强制转换为sockaddr
类型
3、socket类型
-
socket主要分为两大类型,流socket(SOCK_STREAM)和数据报socket(SOCK_DGRAM);
-
流socket提供了一个可靠的双向字节流通信信道,服务端发送的数据会完整无误的到达客户端,数据可以在两个socket之间任意方向上传输,数据不错在消息边界
-
数据报socket中数据以数据报的形式进行交换,数据的传输是不可靠的,数据的到达可能是无序的,重复的,甚至会丢失
-
流socket的正常工作需要两个socket相互连接,数据报socket的正常工作不需要连接另一个socket
-
数据报socket使用UDP协议,流socket使用TCP协议
4、创建和关闭socket
- 系统调用socket()用于创建一个socket:
c
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
/*return file descriptor on success, or -1 on error*/
- 参数domain是通信域,指定socket使用的协议族,可选:
AF_INET(IPv4协议族);AF_INET6(IPv6协议族);AF_UNIX(UNIX域本地通信)
- 参数type指定socket类型,常见值:
SOCK_STREAM(TCP流式socket);SOCK_DGRAM(UDP数据报socket)
- 参数protocol通常设为0,表示使用默认协议:
type为SOCK_STREAM时,默认TCP;type为SOCK_DGRAM时,默认UDP
- 可以通过close()关闭一个socket:
c
#include <unistd.h>
int close(int sockfd);
/*return 0 on success or -1 on error*/
- 参数sockfd是需要关闭的socket描述符(socket()成功时的返回值)
5、将socket绑定到地址
- 系统调用bind()用于将socket绑定到一个地址上面,绑定后客户端可以通过该地址访问服务端:
c
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*return 0 on success, or -1 on error*/
-
参数sockfd是系统调用socket()中返回的文件描述符
-
参数addr指向要绑定的地址结构体,其类型取决于socket的domain,可以是:
socketaddr_u(AF_UNIX); socketaddr_in(AF_INET); socketaddr_in6(AF_INET6)
- 参数addrlen指定地址结构体的长度
6、流socket
(1)流socket服务端-客户端模型
流socket采用TCP协议,分为客户端和服务端:
-
服务端流程 :
通过socket()创建socket;通过bind()绑定到地址;通过listen()将socket设置为被动监听状态;当有客户端请求连接时通过accept()接受客户端连接;通过recv()和send()和客户端完成数据交换;通信完成后通过close()关闭socket -
客户端流程 :
通过socket()创建socket;准备服务端地址信息(IP和端口);通过connect()连接服务端;连接成功后通过send()发送数据;通过recv()接收服务端响应;通信完成后通过close()关闭socket

(2)系统调用listen()
系统调用listen()将socket标记为被动,准备接受来自客户端的连接请求:
c
#include <sys/socket.h>
int listen(int sockfd, int backlog);
/*return 0 on success, or -1 on error*/
参数sockfd是已绑定的socket文件描述符;backlog指定等待连接队列的最大长度
(3)系统调用accept()
系统调用accept()用于接受来自绑定地址的客户端的连接请求:
c
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*return new socket fd on success, or -1 on error*/
参数sockfd是监听socket的文件描述符;addr是监听socket的地址信息;addrlen是参数addr的大小
(4)系统调用connect()
系统调用connect()用于发起与服务端的连接:
c
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*return 0 on success, or -1 on error*/
参数sockfd是要连接的socket文件描述符;addr指定要连接的服务器地址;addrlen地址长度
(5)系统调用recv()
系统调用recv()用于从已连接的socket接收数据:
c
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
/*return number of bytes received, 0 on peer shutdown, or -1 on error*/
参数sockfd是已连接的socket文件描述符;buf指向接收缓冲区;len指定缓冲区大小;flags控制接收行为(通常设为0)
(6)系统调用send()
系统调用send()用于向已连接的socket发送数据:
c
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
/*return number of bytes sent, or -1 on error*/
参数sockfd是已连接的socket文件描述符;buf指向发送数据缓冲区;len指定发送数据长度;flags控制发送行为(通常设为0)
7、数据报socket
(1)数据报socket模型
数据报socket使用无连接的UDP协议进行通信:
-
服务端流程 :
通过socket()创建socket;通过bind()绑定到地址;通过recvfrom()接收客户端数据;通过sendto()发送响应数据;通信完成后通过close()关闭socket -
客户端流程 :
通过socket()创建socket;准备服务端地址信息;通过sendto()发送数据;通过recvfrom()接收服务端响应;通信完成后通过close()关闭socket

(2)系统调用recvfrom()
系统调用recvfrom()用于从数据报socket接收数据并获取发送方地址:
c
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
/*return number of bytes received, or -1 on error*/
参数sockfd是socket文件描述符;buf指向接收缓冲区;len指定缓冲区大小;flags控制接收行为;src_addr返回发送方地址;addrlen指定地址结构体长度
(3)系统调用sendto()
系统调用sendto()用于向指定地址的数据报socket发送数据:
c
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
/*return number of bytes sent, or -1 on error*/
参数sockfd是socket文件描述符;buf指向发送数据;len指定数据长度;flags控制发送行为;dest_addr指定目标地址;addrlen指定地址结构体长度
II Unix domain Socket
1、Unix domain Socket地址结构
Unix domain socket使用文件系统路径名作为地址,结构体定义如下:
c
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* 路径名 */
};
sun_family固定为AF_UNIX;sun_path指定socket文件路径(最大107字符+NULL终止符)
2、Unix domain 流Socket
- 服务端流程 :
socket(AF_UNIX, SOCK_STREAM, 0)→bind()→listen()→accept()→recv()/send()→close()
示例:
c
/* Create a stream socket server */
int main(){
/* Create a stream socket */
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(sfd == -1){
perror("socket");
return -1;
}
/* Remove any existing socket file */
unlink(SOCKET_ADDR_STREAM);
/* Set up server address structure */
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_ADDR_STREAM, strlen(SOCKET_ADDR_STREAM)+1);
/* Bind socket to address */
int err = bind(sfd, (struct sockaddr *)&addr, sizeof(addr.sun_path));
if(err == -1 ){
perror("bind");
close(sfd);
return -1;
}
/* Listen for incoming connections */
err = listen(sfd, SOMAXCONN);
if(err == -1){
perror("listen");
close(sfd);
return -1;
}
/* Client address structure and buffer */
struct sockaddr_un addr_cli;
socklen_t len_cli = sizeof(addr_cli);
char buff[1024];
ssize_t len;
/* Main server loop */
while(1){
/* Accept incoming connection */
int cfd = accept(sfd, (struct sockaddr *)&addr_cli, &len_cli);
if(cfd == -1){
perror("accept");
close(sfd);
return -1;
}
/* Receive data from client */
memset(buff, 0, sizeof(buff));
len = recv(cfd, buff, sizeof(buff), 0);
if(len == -1){
perror("recv");
close(sfd);
close(cfd);
return -1;
}
/* Print received message */
printf("recevied %zd bytes : \n %s \n", len, buff);
/* Check for exit command */
if(strcmp(buff,"exit") == 0){
printf("server closed, see you next time !\n");
break;
}
}
/* Clean up */
close(sfd);
unlink(SOCKET_ADDR_STREAM);
return 0;
}
- 客户端流程 :
socket(AF_UNIX, SOCK_STREAM, 0)→connect()→send()/recv()→close()
示例:
c
/* Create a stream socket client */
int socket_stream_client(int argc, char *argv[]){
/* Check command line arguments */
if(argc < 2 || argv[1] == NULL){
printf("Usage:%s <message send to server>\n", argv[0]);
return -1;
}
/* Create stream socket */
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(sfd == -1){
perror("socket");
return -1;
}
/* Set up server address structure */
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_ADDR_STREAM, strlen(SOCKET_ADDR_STREAM)+1);
/* Connect to server */
int err = connect(sfd, (struct sockaddr *)&addr, sizeof(addr.sun_path));
if(err == -1){
perror("connect");
close(sfd);
return -1;
}
/* Send message to server */
ssize_t len = send(sfd, argv[1], strlen(argv[1]), 0);
if(len == -1){
perror("send");
return -1;
}
/* Clean up */
close(sfd);
return 0;
}
3、Unix domain 数据报socket
- 服务端流程 :
socket(AF_UNIX, SOCK_DGRAM, 0)→bind()→recvfrom()/sendto()→close()
示例:
c
/* Create a datagram socket server */
int socket_dgram_server(){
/* Create a datagram socket */
int sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if(sfd == -1){
perror("socket");
return -1;
}
/* Remove any existing socket file */
unlink(SOCKET_ADDR_DGRAM);
/* Set up server address structure */
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_ADDR_DGRAM, strlen(SOCKET_ADDR_DGRAM)+1);
/* Bind socket to address */
int err = bind(sfd, (struct sockaddr *)&addr, sizeof(addr.sun_path));
if(err == -1){
perror("bind");
close(sfd);
return -1;
}
/* Buffer for received data */
char buff[1024];
/* Client address structure */
struct sockaddr_un cli_addr;
socklen_t len = sizeof(cli_addr);
/* Main server loop */
while(1){
memset(buff, 0, sizeof(buff));
/* Receive data from client */
ssize_t num_bytes = recvfrom(sfd, buff, sizeof(buff), 0,
(struct sockaddr *)&cli_addr, &len);
if(num_bytes == -1){
perror("recvfrom");
close(sfd);
return -1;
}
/* Print received message */
printf("received %zd bytes: %s\n", num_bytes, buff);
/* Check for exit command */
if(strcmp(buff, "exit") == 0){
printf("server closed, see you next time!\n");
break;
}
}
/* Clean up */
close(sfd);
unlink(SOCKET_ADDR_DGRAM);
return 0;
}
- 客户端流程 :
socket(AF_UNIX, SOCK_DGRAM, 0)→sendto()/recvfrom()→close()
示例:
c
/* Create a datagram socket client */
int socket_dgram_client(int argc, char *argv[]){
/* Check command line arguments */
if(argc < 2 || argv[1] == NULL){
printf("Usage:%s <message send to server>\n", argv[0]);
return -1;
}
/* Create datagram socket */
int sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if(sfd == -1){
perror("socket");
return -1;
}
/* Set up server address structure */
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_ADDR_DGRAM, strlen(SOCKET_ADDR_DGRAM)+1);
/* Send message to server */
ssize_t len = sendto(sfd, argv[1], strlen(argv[1]), 0,
(struct sockaddr *)&addr, sizeof(addr.sun_path));
if(len == -1){
perror("sendto");
close(sfd);
return -1;
}
/* Clean up */
close(sfd);
return 0;
}