以往学习过 Windows 中的 TCP/UDP socket 通信,但是当我研究安卓中的 tcp 通信时,发现 C 语言通信的语法截然不同,然后才发现安卓系统上是 Linux 操作系统,所以也有必要去好好学习 Linux 系统中的 tcp/udp 通信
# socket API 接口
# socket
创建一个 socket 套接字
| #include <sys/types.h> |
| #include <sys/socket.h> |
| |
| @param domain: 通信协议簇 |
| @param type: 指定 socket 类型 |
| @param protocol: 指定协议,当 protocol 为 0 时,会自动选择 type 类型对应的默认协议 |
| @return: 创建成功返回一个 socket 套接字,创建失败返回 - 1 |
| */ |
| int socket(int domain, int type, int protocol); |
domain
名称 |
值 |
含义 |
AF_UNSPEC |
0 |
AF_UNIX |
1 |
Unix domain sockets |
AF_LOCAL |
1 |
POSIX name for AF_UNIX |
AF_INET |
2 |
Internet IP Protocol, IPv4 |
AF_AX25 |
3 |
Amateur Radio AX.25 |
AF_IPX |
4 |
Novell IPX |
AF_APPLETALK |
5 |
AppleTalk DDP |
AF_NETROM |
6 |
Amateur Radio NET/ROM |
AF_BRIDGE |
7 |
Multiprotocol bridge |
AF_ATMPVC |
8 |
ATM PVCs |
AF_X25 |
9 |
Reserved for X.25 project |
AF_INET6 |
10 |
IPv6 |
AF_ROSE |
11 |
Amateur Radio X.25 PLP |
AF_DECnet |
12 |
Reserved for DECnet project |
AF_NETBEUI |
13 |
Reserved for 802.2LLC project |
AF_SECURITY |
14 |
Security callback pseudo AF |
AF_KEY |
15 |
PF_KEY key management API |
AF_NETLINK |
16 |
AF_ROUTE |
AF_NETLINK |
Alias to emulate 4.4BSD |
AF_PACKET |
17 |
Packet family |
AF_ASH |
18 |
Ash |
AF_ECONET |
19 |
Acorn Econet |
AF_ATMSVC |
20 |
ATM SVCs |
AF_RDS |
21 |
RDS sockets |
AF_SNA |
22 |
Linux SNA Project (nutters!) |
AF_IRDA |
23 |
IRDA sockets |
AF_PPPOX |
24 |
PPPoX sockets |
AF_WANPIPE |
25 |
Wanpipe API Sockets |
AF_LLC |
26 |
Linux LLC |
AF_IB |
27 |
Native InfiniBand address |
AF_MPLS |
28 |
MPLS |
AF_CAN |
29 |
Controller Area Network |
AF_TIPC |
30 |
TIPC sockets |
AF_BLUETOOTH |
31 |
Bluetooth sockets |
AF_IUCV |
32 |
IUCV sockets |
AF_RXRPC |
33 |
RxRPC sockets |
AF_ISDN |
34 |
mISDN sockets |
AF_PHONET |
35 |
Phonet sockets |
AF_IEEE802154 |
36 |
IEEE802154 sockets |
AF_CAIF |
37 |
CAIF sockets |
AF_ALG |
38 |
Algorithm sockets |
AF_NFC |
39 |
NFC sockets |
AF_VSOCK |
40 |
vSockets |
AF_KCM |
41 |
Kernel Connection Multiplexor |
AF_QIPCRTR |
42 |
Qualcomm IPC Router |
type
名称 |
值 |
含义 |
对应的协议 |
SOCK_STREAM |
1 |
stream (connection) socket |
TCP |
SOCK_DGRAM |
2 |
datagram (conn.less) socket |
UDP |
SOCK_RAW |
3 |
raw socket |
ICMP |
SOCK_RDM |
4 |
reliably-delivered message |
|
SOCK_SEQPACKET |
5 |
sequential packet socket |
SCTP |
SOCK_DCCP |
6 |
Datagram Congestion Control Protocol socket |
DCCP |
SOCK_PACKET |
10 |
linux specific way of getting packets at the dev level. |
|
# bind
当用 socket
函数创建套接字后,我们需要使用 bind 函数来将指定的 IP 和端口分配给已经创建的 socket
| #include <sys/types.h> |
| #include <sys/socket.h> |
| |
| @param sockfd: socket 函数返回的 socket 套接字 |
| @param addr: 含有要绑定的 IP 和端口的地址结构指针 |
| @param addrlen: addr 的大小,一般使用 sizeof 来进行计算 |
| @return: 成功返回 0, 失败返回 - 1 |
| */ |
| int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
addr
定义的类型为 struct sockaddr_in
| struct sockaddr_in { |
| sa_family_t sin_family; |
| in_port_t sin_port; |
| struct in_addr sin_addr; |
| }; |
| |
| |
| struct in_addr { |
| uint32_t s_addr; |
| }; |
定义的类型为 sockaddr_in6
| struct sockaddr_in6 { |
| sa_family_t sin6_family; |
| in_port_t sin6_port; |
| uint32_t sin6_flowinfo; |
| struct in6_addr sin6_addr; |
| uint32_t sin6_scope_id; |
| }; |
| struct in6_addr { |
| unsigned char s6_addr[16]; |
| }; |
定义的类型为 sockaddr_un
| #define UNIX_PATH_MAX 108 |
| struct sockaddr_un { |
| sa_family_t sun_family; |
| char sun_path[UNIX_PATH_MAX]; |
| }; |
在将 ip 和 port 绑定到 socket 时,我们需要将主机字节序 (小端序) 转换成网络字节序 (大端序), 通常会使用到的函数有 htonl
, inet_addr
, htons
| htonl(INADDR_ANY); |
| inet_addr("127.0.0.1"); |
| htons(6666); |
# listen
服务端使用 listen 来建立一个监听客户端连接的队列
| #include <sys/types.h> |
| #include <sys/socket.h> |
| |
| |
| @param sockfd: 监听的 socket 描述符 |
| @param backlog: 建立的最大连接数 |
| @return: 成功返回 0,失败返回 -1,并设置 erron |
| */ |
| int listen(int sockfd, int backlog); |
# accept
服务端接受客户端的连接
| #include <sys/types.h> |
| #include <sys/socket.h> |
| |
| |
| @param sockfd: 监听的 socket 描述符 |
| @param addr: 保存连接的客户端的地址信息 |
| @param addrlen: addr 的大小,一般使用 sizeof 来进行计算 |
| @return: 成功返回客户端的 socket 文件描述字,失败返回 -1,设置 erron |
| */ |
| int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
# connect
客户端向服务端发起连接请求
| #include <sys/types.h> |
| #include <sys/socket.h> |
| |
| |
| @param sockfd: 监听的 socket 描述符 |
| @param addr: 保存连接的客户端的地址信息 |
| @param addrlen: addr 的大小,一般使用 sizeof 来进行计算 |
| @return: 成功返回客户端的 socket 文件描述字,失败返回 -1,设置 erron |
| */ |
| int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
# send, sendto
发送数据
| #include <sys/types.h> |
| #include <sys/socket.h> |
| |
| |
| @param sockfd: 接受数据的 socket |
| @param buf: 发送的数据 |
| @param len: 数据长度 |
| @param flags: 当这个参数为 0,该函数等价与 write |
| @return: 成功返回发送的字节数,失败返回 -1,并设置 erron |
| */ |
| 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); |
# recv, recvfrom
接收数据
| #include <sys/types.h> |
| #include <sys/socket.h> |
| |
| |
| @param sockfd: 接收的 socket fd |
| @param buf: 接收缓冲区 |
| @param len: 缓冲区长度 |
| @param flags: 当这个参数为 0,该函数等价与 read |
| @return: 成功返回接受的字节数,失败返回 -1,并设置 erron |
| */ |
| 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); |
# close
关闭由 accept
或者 connect
返回的 socket 描述字
| #include <unistd.h> |
| int close(int fd); |
# 使用 Socket 进行 TCP 通信
# TCP 服务端
| |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <string.h> |
| #include <arpa/inet.h> |
| |
| int main(void) { |
| int server_fd, client_fd; |
| struct sockaddr_in listen_addr; |
| listen_addr.sin_family = AF_INET; |
| listen_addr.sin_addr.s_addr = htonl(INADDR_ANY); |
| listen_addr.sin_port = htons(8888); |
| |
| |
| server_fd = socket(AF_INET, SOCK_STREAM, 0); |
| |
| |
| bind(server_fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)); |
| |
| |
| listen(server_fd, 10); |
| printf("TCP server is listening...\n"); |
| |
| char send_msg[] = "hello client"; |
| while(1) { |
| |
| client_fd = accept(server_fd, (struct sockaddr *)NULL, NULL); |
| |
| |
| send(client_fd, send_msg, sizeof(send_msg), 0); |
| printf("Write \"hello client\" to client ok.\n"); |
| |
| |
| close(client_fd); |
| } |
| |
| close(server_fd); |
| return 0; |
| } |
# TCP 客户端
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <string.h> |
| #include <arpa/inet.h> |
| #include <netinet/in.h> |
| #include <netdb.h> |
| |
| int main() { |
| int client_fd = 0; |
| struct sockaddr_in server_addr; |
| server_addr.sin_family = AF_INET; |
| server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| server_addr.sin_port = htons(8888); |
| |
| |
| client_fd = socket(AF_INET, SOCK_STREAM, 0); |
| |
| |
| connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); |
| |
| |
| char msg_buf[100] = { 0 }; |
| recv(client_fd, msg_buf, 100, 0); |
| printf("Client get server msg: %s\n", msg_buf); |
| |
| |
| close(client_fd); |
| return 0; |
| } |
# 使用 Socket 进行 UDP 通信
# UDP 服务端
| |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <string.h> |
| #include <arpa/inet.h> |
| |
| int main(void) { |
| int server_fd, client_fd; |
| struct sockaddr_in listen_addr; |
| listen_addr.sin_family = AF_INET; |
| listen_addr.sin_addr.s_addr = htonl(INADDR_ANY); |
| listen_addr.sin_port = htons(8888); |
| |
| |
| server_fd = socket(AF_INET, SOCK_DGRAM, 0); |
| |
| |
| bind(server_fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)); |
| |
| |
| struct sockaddr_in client_addr; |
| char msg_buf[100] = { 0 }; |
| recvfrom(server_fd, msg_buf, 100, 0, (struct sockaddr *)&client_addr, (socklen_t *)sizeof(client_addr)); |
| printf("Client get server msg: %s\n", msg_buf); |
| |
| |
| close(server_fd); |
| return 0; |
| } |
# UDP 客户端
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <string.h> |
| #include <arpa/inet.h> |
| #include <netinet/in.h> |
| #include <netdb.h> |
| |
| int main() { |
| int client_fd = 0; |
| struct sockaddr_in server_addr; |
| server_addr.sin_family = AF_INET; |
| server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| server_addr.sin_port = htons(8888); |
| |
| |
| client_fd = socket(AF_INET, SOCK_DGRAM, 0); |
| |
| |
| char send_msg[] = "hello server"; |
| sendto(client_fd, send_msg, sizeof(send_msg), 0, (struct sockaddr *)&server_addr, sizeof(server_addr)); |
| printf("Write \"hello server\" to server ok.\n"); |
| |
| |
| close(client_fd); |
| return 0; |
| } |
# 参考资料
- Linux 高级编程 - Socket 编程基础(TCP,UDP)
- socket 接口的网络协议无关性
- 一文搞懂 Linux 的 Socket 编程原理 (含实例解析)