socket编程总结
1. socket() 创建一个套接字
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: 通信域,又叫协议族,通常有下列这些:
协议族 | 含义 |
---|---|
AF_UNIX、AF_LOCAL、AF_FILE | UNIX域,又叫本地域或文件域。 |
AF_INET | IPv4协议。 |
AF_INET6 | IPv6协议。 |
AF_IPX | IPX协议。 |
AF_NETLINK | 用于内核和用户的Netlink通信。 |
AF_PACKET | 原始套接字,低层数据包接口。 |
... | ... |
type: socket类型,有下列这些:
socket类型 | 含义 |
---|---|
SOCK_STREAM | 流式套接字,特点是有连接、双向传输、传输可靠、有顺序无边界、支持带外数据。 |
SOCK_DGRAM | 报文套接字,特点是无连接、单向传输、传输不可靠、包大小有限、到达顺序不确定。 |
SOCK_SEQPACKET | 序列包套接字,特点是有连接,双向传输、传输可靠、有顺序,包大小固定。 |
SOCK_RAW | 提供原始网络协议访问。 |
SOCK_RDM | 提供一个不保证排序的可靠数据报层。 |
另外type支持按位或 SOCK_NONBLOCK
和 SOCK_CLOEXEC
。SOCK_NONBLOCK
可以设置 O_NONBLOCK
状态,见fcntl()
。SOCK_CLOEXEC
可以设置FD_CLOEXEC
状态,见open()
的O_CLOEXEC
。
protocol: 指定协议。确定domain
和type
后通常只有一个协议与之对应,此时protocol
设置为0即可。
2. socketpair() 创建一对互通的套接字
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
domain、type 和 protocol 与 socket() 的参数含义相同,这里的 domain 只能是 UNIX 域,sv[0] 和 sv[1] 是被创建的一对可互相通信的套接字。
3. bind() 将套接字与地址绑定
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd: 要绑定地址的套接字。
addr: 指向地址结构体,如下所示:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
但是此结构体的唯一作用是强制转换addr中传递的指针,以避免编译器警告。而真正使用的结构体,随协议族的不同而不同,举例如下:
UNIX域使用struct sockaddr_un
结构体地址。
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "abcdefghijklmnop");
bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un));
IPv4使用struct sockaddr_in
结构体地址。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in));
IPv6使用struct sockaddr_in6
结构体地址。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
struct sockaddr_in6 addr;
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(8080);
inet_pton(AF_INET6, "::1", &addr.sin6_addr);
bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in6));
addrlen: addr 所指结构体的大小。
bind() 的作用是让另一个套接字找到这个套接字,所以 bind() 只对 socket() 创建的套接字有意义,socketpair() 创建的一对套接字已经互通,不需要bind()。
4. listen() 将套接字设置为被动监听状态
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd: 要被设置为监听的套接字。
backlog: 监听队列最大长度。
listen() 仅用于面向连接的套接字,即 type 为 SOCK_STREAM 或 SOCK_SEQPACKET 的套接字。
5. accept() 在监听套接字上等待新连接
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd: 监听中的套接字。该套接字一定是由socket()创建,由bind()绑定了地址,由listen()设置为监听状态。
addr: 指向新的连接的对方的地址。
addrlen: addr 所指结构体的大小。
accept() 返回新的套接字,且 accept() 仅用于面向连接的套接字,即 type 为 SOCK_STREAM 或 SOCK_SEQPACKET 的套接字。
#include <sys/types.h>
#define _GNU_SOURCE
#include <sys/socket.h>
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
accept4() 比 accept() 多了一个参数 flages。
flags 等于 0
时,accept4() 等同于 accept()。
flags 包含 SOCK_NONBLOCK
时,accept4() 返回的新套接字包含 O_NONBLOCK
属性。
flags 包含 SOCK_CLOEXEC
时,accept4() 返回的新套接字包含 FD_CLOEXEC
属性。
6. connect() 发起连接
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd: 发起连接的套接字。
addr: 等待连接的地址。
addrlen: addr 所指结构体的大小。
如果 sockfd 是 SOCK_DGRAM 套接字,则 addr 是发送数据报时默认到达地址,也是接收数据报时唯一来源地址。
如果 sockfd 是 SOCK_STREAM 或 SOCK_SEQPACKET 套接字,则 connect() 让 sockfd 与 addr 建立连接。
7. send()、sendto()、sendmsg() 发送数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
8. recv()、recvfrom()、recvmsg() 接收数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
9. getsockopt() 获取套接字选项
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
10. setsockopt() 设置套接字选项
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
11. getpeername() 获取套接字对端地址
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
12. getsockname() 获取套接字本地地址
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
13. shutdown() 关闭套接字的连接
#include <sys/socket.h>
int shutdown(int sockfd, int how);
sockfd: 要被关闭的套接字。
how: 如何关闭套接字。关闭方式如下:
SHUT_RD
禁用进一步的接收操作。
SHUT_WR
禁用进一步的发送操作。
SHUT_RDWR
禁用进一步的发送和接收操作。
注意 shutdown() 仅仅关闭套接字的连接,没有释放套接字所占的文件描述符,进一步释放文件描述符使用 close()。
14. 常用代码片段
#include <unistd.h> // close()
#include <sys/types.h> //
#include <sys/socket.h> //
#include <sys/un.h> // unix域
#include <netinet/in.h> // IPv4 IPv6
// 创建unix域TCP协议的socket
int createSocket_unix_tcp(char *path){
// 创建数据流式socket
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(sockfd < 0){
return -1;
}
// 设置地址协议族为unix域,并设置path为虚拟path。
struct sockaddr_un addr;
int pathLen = strlen(path);
if(pathLen > sizeof(addr.sun_path)-2){
pathLen = sizeof(addr.sun_path)-2;
}
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
addr.sun_path[0] = 0;
memcpy(addr.sun_path+1, path, pathLen);
// 绑定地址
if(bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0){
close(sockfd);
return -2;
}
// 设置为监听
if(listen(sockfd, 4) < 0){
close(sockfd);
return -3;
}
return sockfd;
}
// 创建IPv4协议族TCP协议的socket
int createSocket_ipv4_tcp(char *ipv4, int port){
// 创建数据流式socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0){
return -1;
}
// 设置IPv4协议族地址
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ipv4);
// 绑定地址
if(bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0){
close(sockfd);
return -2;
}
// 设置为监听
if(listen(sockfd, 4) < 0){
close(sockfd);
return -3;
}
return sockfd;
}
// 创建IPv6协议族TCP协议的socket
int createSocket_ipv6_tcp(char *ipv6, int port){
// 创建数据流式socket
int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if(sockfd < 0){
return -1;
}
// 设置IPv6协议族地址
struct sockaddr_in6 addr;
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(port);
inet_pton(AF_INET6, ipv6, &addr.sin6_addr);
// 绑定地址
if(bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in6)) < 0){
close(sockfd);
return -2;
}
// 设置为监听
if(listen(sockfd, 4) < 0){
close(sockfd);
return -3;
}
return sockfd;
}
// 等待新的TCP连接, unix域.
int waitNewSocket_unix_tcp(int sockfd){
struct sockaddr_un addr;
socklen_t size = sizeof(struct sockaddr_un);
return accept(sockfd, (struct sockaddr*)&addr, &size);
}
// 等待新的TCP连接, IPv4.
int waitNewSocket_ipv4_tcp(int sockfd){
struct sockaddr_in addr;
socklen_t size = sizeof(struct sockaddr_in);
return accept(sockfd, (struct sockaddr*)&addr, &size);
}
// 等待新的TCP连接, IPv6.
int waitNewSocket_ipv6_tcp(int sockfd){
struct sockaddr_in6 addr;
socklen_t size = sizeof(struct sockaddr_in6);
return accept(sockfd, (struct sockaddr*)&addr, &size);
}
// 用法伪代码
void usage_unix_tcp(void){
char buffer[256];
int listenFd, newFd, size;
listenFd = createSocket_unix_tcp("abcd1234");
for(;;){
newFd = waitNewSocket_unix_tcp(listenFd);
size = recv(newFd, buffer, 256, 0);
send(newFd, buffer, size, 0);
close(newFd);
}
close(listenFd);
}
// 用法伪代码
void usage_ipv4_tcp(void){
char buffer[256];
int listenFd, newFd, size;
listenFd = createSocket_ipv4_tcp("0.0.0.0", 2345);
for(;;){
newFd = waitNewSocket_ipv4_tcp(listenFd);
size = recv(newFd, buffer, 256, 0);
send(newFd, buffer, size, 0);
close(newFd);
}
close(listenFd);
}
// 用法伪代码
void usage_ipv6_tcp(void){
char buffer[256];
int listenFd, newFd, size;
listenFd = createSocket_ipv6_tcp("0:0:0:0:0:0:0:0", 2345);
for(;;){
newFd = waitNewSocket_ipv6_tcp(listenFd);
size = recv(newFd, buffer, 256, 0);
send(newFd, buffer, size, 0);
close(newFd);
}
close(listenFd);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// 创建Unix域UDP协议的socket
int createSocket_unix_udp(char *path){
// 创建数据报式socket
int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if(sockfd < 0){
return -1;
}
// 设置地址协议族为unix域,并设置path为虚拟path。
struct sockaddr_un addr;
int pathLen = strlen(path);
if(pathLen > sizeof(addr.sun_path)-2){
pathLen = sizeof(addr.sun_path)-2;
}
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
addr.sun_path[0] = 0;
memcpy(addr.sun_path+1, path, pathLen);
// 绑定地址
if(bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0){
close(sockfd);
return -2;
}
return sockfd;
}
// 创建IPv4协议族UDP协议的socket
int createSocket_ipv4_udp(char *ipv4, int port){
// 创建数据流式socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0){
return -1;
}
// 设置IPv4协议族地址
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ipv4);
// 绑定地址
if(bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0){
close(sockfd);
return -2;
}
return sockfd;
}
// 创建IPv6协议族UDP协议的socket
int createSocket_ipv6_udp(char *ipv6, int port){
// 创建数据流式socket
int sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
if(sockfd < 0){
return -1;
}
// 设置IPv6协议族地址
struct sockaddr_in6 addr;
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(port);
inet_pton(AF_INET6, ipv6, &addr.sin6_addr);
// 绑定地址
if(bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in6)) < 0){
close(sockfd);
return -2;
}
return sockfd;
}
// 用法伪代码
void usage_unix_udp(void){
char buffer[256];
int sockfd, size;
sockfd = createSocket_unix_udp("abcd1234");
for(;;){
size = recv(sockfd, buffer, 256, 0);
}
close(sockfd);
}
// 用法伪代码
void usage_ipv4_udp(void){
char buffer[256];
int sockfd, size;
sockfd = createSocket_ipv4_udp("0.0.0.0", 2345);
for(;;){
size = recv(sockfd, buffer, 256, 0);
}
close(sockfd);
}
// 用法伪代码
void usage_ipv6_udp(void){
char buffer[256];
int sockfd, size;
sockfd = createSocket_ipv6_udp("0:0:0:0:0:0:0:0", 2345);
for(;;){
size = recv(sockfd, buffer, 256, 0);
}
close(sockfd);
}