以往学习过 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
  • IPv4

定义的类型为 struct sockaddr_in

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};
 
/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};
  • IPv6

定义的类型为 sockaddr_in6

struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
}; 
struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};
  • Unix

定义的类型为 sockaddr_un

#define UNIX_PATH_MAX    108
struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char sun_path[UNIX_PATH_MAX];         /* pathname */ 
};

在将 ip 和 port 绑定到 socket 时,我们需要将主机字节序 (小端序) 转换成网络字节序 (大端序), 通常会使用到的函数有 htonl , inet_addr , htons

htonl(INADDR_ANY);//Host to Network Long,INADDR_ANY 通常为 0, 相当于 inet_addr ("0.0.0.0"), 即监听来自所有 IP 的连接
inet_addr("127.0.0.1");// 将点分十进制 IP 地址转换成网络字节序 IP 地址
htons(6666);//Host to Network Short, 用来转换 port

# 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);
/* sendto 功能是将数据发送到指定的地址 dest_addr,其他参数基本相同 */
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);
/* recvfrom 从指定的地址 src_addr 接收数据,其他参数与 recv 类似 */
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 通信

sock_tcp

# TCP 服务端

// tcp-server.c
#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;// 使用 IPv4 地址
    listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 表示不管是哪个网卡接收到数据,只要目的端口是 SERV_PORT,就会被服务端接收到
    listen_addr.sin_port = htons(8888);
    
	//create socket
	server_fd = socket(AF_INET, SOCK_STREAM, 0);
    
	//bind listen_addr to server_fd
	bind(server_fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr));
	
    //listen server_fd, max listen client num = 10
	listen(server_fd, 10);
	printf("TCP server is listening...\n");
	
	char send_msg[] = "hello client";
	while(1) {
        //accept client connect
		client_fd = accept(server_fd, (struct sockaddr *)NULL, NULL);
        
		//send data
		send(client_fd, send_msg, sizeof(send_msg), 0);
		printf("Write \"hello client\" to client ok.\n");
		
        //close client fd
		close(client_fd);
	}
	//close server fd
	close(server_fd);
	return 0;
}

# TCP 客户端

//tcp-client.c
#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);
    
	//create socket
	client_fd = socket(AF_INET, SOCK_STREAM, 0);
    
	//connect server
	connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    
	//recv msg from server
	char msg_buf[100] = { 0 };
	recv(client_fd, msg_buf, 100, 0);
	printf("Client get server msg: %s\n", msg_buf);
    
	//close fd
	close(client_fd);
	return 0;
}

# 使用 Socket 进行 UDP 通信

sock_udp

# UDP 服务端

// udp-server.c
#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;// 使用 IPv4 地址
    listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 表示不管是哪个网卡接收到数据,只要目的端口是 SERV_PORT,就会被服务端接收到
    listen_addr.sin_port = htons(8888);
    
	//create socket
	server_fd = socket(AF_INET, SOCK_DGRAM, 0);
    
	//bind server_addr to server_fd
	bind(server_fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr));
	
    //recv msg from client
    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
	close(server_fd);
	return 0;
}

# UDP 客户端

//udp-client.c
#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);
    
	//create socket
	client_fd = socket(AF_INET, SOCK_DGRAM, 0);
    
	//send data
    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 fd
	close(client_fd);
	return 0;
}

# 参考资料

  • Linux 高级编程 - Socket 编程基础(TCP,UDP)
  • socket 接口的网络协议无关性
  • 一文搞懂 Linux 的 Socket 编程原理 (含实例解析)
更新于 阅读次数