# 基本套接字编程
# IPv4 套接字地址结构
| #include <netinet/in.h> |
| |
| struct sockaddr_in{ |
| uint8_t sin_len; |
| sa_family_t sin_family; |
| struct in_addr sin_addr; |
| in_port_t sin_port; |
| char sin_zero[sizeof (struct sockaddr) - sizeof (sa_family) - sizeof (in_port_t) - sizeof (struct in_addr)]; |
| }; |
# IPv6 套接字地址结构
# 字节序转化函数
- 内存中储存数据的方式有两种:大端字节序和小端字节序;两种格式都有系统使用,目前并没有统一的标准;我们把某个给定系统所用的字节序称为主机字节序;
- 数据在网络中的传输方式称为网络字节序,通常采用大端存储的方式.
- 由于一些历史原因和
POSIX
规范的规定,套接字地址结构中某些字段必须按照网络字节序的方式进行维护.
因此,在进行网络通信之前,套接字地址结构中的端口号、地址需要以网络字节序的方式存储.
# 端口
字节序转化函数
| #include <netinet/in.h> |
| |
| uint16_t htons(uint16_t host_port); |
| uint16_t ntohs(uint16_t net_port); |
| |
| uint32_t htonl(uint32_t host_addr); |
| uint32_t ntohl(uint32_t net_addr); |
# 地址
字节序转化函数
- IPV4 专用
| #include <arpa/inet.h> |
| |
| int inet_aton(const char *strptr, struct in_addr *addrptr); |
| |
| char *inet_ntoa(struct in_addr *addrptr); |
- IPV4/IPV6 通用(建议使用)
| #include <arpa/inet.h> |
| |
| int inet_pton(int family, const char *strptr, void *addrptr); |
| |
| const char* inet_ntop(int family, const void *addrptr, char *strptr, int len); |
inet_ntop
中的 len
参数指代目标存储区 ( strptr
) 大小,以免函数溢出调用者的缓冲区。如若 len 太小不足以容纳表达式格式结果 (包含末尾的 \0
),则会返回 NULL
. 同时 strptr
参数不能为 NULL
,调用者必须目标存储区分配相应的空间. len
的设置可以调用如下的宏定义:
| #include <netinet/in.h> |
| #define INET_ADDRSTRLEN 16 |
| #define INET6_ADDRSTRLEN 32 |
# socket
函数
| #include <sys/socket.h> |
| int socket(int family, int type, int protocol); |
| |
| Description: |
| domain: 协议域 |
| - AF_INET:IPV4 protocal |
| - AF_INET6:IPV6 protocal |
| - AF_LOCAL:Unix 域协议 |
| - AF_ROUTE: 路由套接字 |
| - AF_KEY: 秘钥套接字 |
| type: 套接字类型 |
| - SOCK_STREAM: 字节流套接字 |
| - SOCK_DGRAM: 数据报套接字 |
| - SOCK_SEQPACKET: 允许分组套接字 |
| - SOCK_RAW: 原始套接字 |
| protocal: 协议类型,或者设置为 0(会选择 domain 和 type 组合的系统默认值,但是并非所有的组合都是有效的) |
| - IPPROTO_TCP:TCP 传输协议 |
| - IPPROTO_UPD:UDP 传输协议 |
| - IPPROTO_SCTP:SCTP 传输协议 |
| Return Value: |
| socket 函数成功则会返回一个小的非负整数值,它与文件描述符类似,称为套接字描述符 ssocket descriptor(sockfd)。失败则返回 - 1 |
| */ |
family
:协议族AF_INET
:IPv4 协议AF_INET6
:IPv6 协议
type
字段和 protocol
# 套接字编程
| |
| typedef unsigned short int sa_family_t; |
| struct sockaddr{ |
| sa_family_t sa_famliy; |
| char sa_data[14]; |
| }; |
| |
| Synopsis: |
| #include <sys/socket.h> |
| Additional: |
| sockaddr 是通用的套接字地址结构,是早期的版本,同时与各种套接字函数都是适配的;当套接字作为一个参数参入套接字函数时,往往是以引用形式(也就是以指向该地址的指针)来传递。 |
| 当使用便于赋值和使用的新型地址结构 sockaddr_in 时,使用套接字函数,传入套接字地址时,需要做一个强制类型转换变成通用的套接字地址结构的指针。 |
| struct sockaddr_in serv; |
| (struct socketaddr *)& serv; |
| */ |
| typedef unsigned short uint16_t; |
| typedef unsigned int uint32_t; |
| typedef uint16_t in_port_t; |
# IPV6 套接字地址结构
# 值 - 结果参数
当往套接字函数传递一个套接字地址结构时,该结构总是以引用的形式来传递,也就是传递指向该结构的一个指针;其有两种传递方式
- 从进程到内核传递套接字地址的函数有:connect、bind 和 sendto。这些函数的一个参数是指向某个套接字地址结构的指针,另外一个参数是指向该结构的整形大小;(即指针和指针所指内容的大小都传递给了内核,于是内核找到到底需要从进程复制多少数据进来)
- 从内核到进程传递套接字地址的函数有:accept、recvfrom、getsockname 和 getpeername。这些函数的
# 字符操纵函数
| #include <strings.h> |
| void bzero(); |
| void bcopy(); |
| int bcmp(); |
| #include <string.h> |
| void *memsset(); |
| void *memcpy(); |
| int memcmp(); |
# TCP 套接字编程
# socket 函数
# connect 函数
| typedef unsigned int socklen_t; |
| int connect(int sockfd, const struct sockaddr *serveraddr, socklen_t addrlen); |
| |
| Synopsis: |
| #include <sys/socket.h> |
| Description: |
| sockfd: 客户端用于通信的套接字描述符; |
| serveraddr: 服务器监听套接字 |
| addrlen: 服务器套接字地址结构长度 |
| 客户在调用函数 connect 之前,不必非得调用 bind 函数;因为如果也需要的话,内核会确定源 IP 地址,并选择一个临时端口作为源端口 |
| Return Value: |
| 若成功则返回 0,若出错则返回 - 1。若 connect 失败则该套接字不再可用,必须关闭。 |
| */ |
# bind 函数
| int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); |
| |
| Synopsis: |
| #include <sys/socket.h> |
| Description: |
| 把一个本地协议地址赋予一个套接字 |
| sockfd: |
| myaddr: |
| socklen_t: |
| Return Value: |
| |
| */ |
# listen 函数
| int listen(int sockfd, int backlog); |
| |
| Synopsis: |
| #include <sys/socket.h> |
| Description: |
| sockfd: 套接字描述符;当 socket 函数创建一个套接字时,它被假设为一个主动套接字,listen 函数把一个未连接的套接字转化为一个被动套接字,指示内核应该接受指向该套接字的连接请求;(调用 listen 导致套接字从 CLOSE 状态转换到 LISTEN 状态) |
| backlog: 内核应该为相应套接字设置的最大排队连接请求 |
| Return Value: |
| 若成功则返回 0,若失败返回 - 1; |
| */ |
内核会为任何一个给定的监听套接字维护两个队列:
- 未完成连接队列(incomplete connection queue)
- 已完成连接队列(completed connection queue)
# accept 函数
| int accept(int sockfd, struct sockaddr *clientaddr, socklen_t addrlen); |
| |
| Synopsis: |
| #include <sys/socket.h> |
| Description: |
| accept 函数由 TCP 服务器调用,用于从已完成连接队列对头返回下一个已完成连接的客户端请求。如果已完成连接队列为空,那么进程被置于休眠状态。 |
| sockfd: 监听套接字描述符 |
| clientaddr: 客户进程地址结构 |
| addrlen: 客户进程协议地址长度 |
| Return Value: |
| 若 accept 成功,那么其返回值是由内核自动生成的一个全新的套接字描述符,代表与所返回的客户的 TCP 连接。若服务器对客户协议地址不感兴趣,可以把 clientaddr 和 addrlen 设置为空指针。返回的已连接套接字在每次断开连接后需要关闭。若失败则返回 - 1; |
| */ |
# recv 和 send 函数
recv
和 send
函数类似于标准的 read
和 write
函数,不过需要一个额外的参数。
| typedef long int ssize_t; |
| typedef long unsigned int size_t; |
| ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags); |
| ssize_t send(int socket, void *buff, size_t nbytes, int flags); |
| |
| Synopsis: |
| #include <sys/socket.h> |
| Description: |
| 前三个参数等同于 read 和 write 的三个参数。flags 参数的值或为 0,或为如下字段: |
| - MSG_DONTROUTE |
| - MSG_DONTWAIT |
| - MSG_OOB |
| - MSG_PEEK |
| - MSG_WAITALL |
| Return Value: |
| |
| */ |
# close 函数
| int close(int sockfd); |
| |
| Synopsis: |
| #include <unistd.h> |
| Description: |
| sockfd: 待关闭的套接字描述符 |
| Return Value: |
| 若关闭成功返回 0,若关闭失败返回 - 1; |
| */ |
# 并发服务器
当服务器需要连接多个客户端时,可以使用迭代的方式,但是当服务一个客户请求可能花费较长时间时,我们并不希望整个服务器被单个客户长期占用,而是希望同时服务多个客户,这时需要在并发的服务器。
# TCP 客户 / 服务器程序实例
<img src="C:\Users\Value\AppData\Roaming\Typora\typora-user-images\image-20230522145338069.png" alt="image-20230522145338069" style="zoom:90%;" />
| |
| |
| |
| |
| |
| |
| |
| |
| #include <cstdio> |
| #include <iostream> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/socket.h> |
| #include <stdlib.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| int main(){ |
| |
| int listen_socket_fd = socket(AF_INET, SOCK_STREAM, 0); |
| |
| struct sockaddr_in listen_socket; |
| listen_socket.sin_family = AF_INET; |
| listen_socket.sin_port = htons(9999); |
| inet_pton(AF_INET, "192.168.159.130", &listen_socket.sin_addr.s_addr); |
| |
| int ret = bind(listen_socket_fd, (struct sockaddr*) &listen_socket, sizeof listen_socket); |
| if(ret == -1){ |
| perror("bind error!"); |
| return -1; |
| } |
| |
| ret = listen(listen_socket_fd, 10); |
| if(ret == -1){ |
| perror("listen error!"); |
| return -1; |
| } |
| printf("客户端启动中...\n"); |
| |
| struct sockaddr_in client_socket; |
| unsigned int addrlen = sizeof client_socket; |
| int client_socket_fd = accept(listen_socket_fd, (struct sockaddr*) &client_socket, &addrlen); |
| if(client_socket_fd == -1){ |
| perror("accept error"); |
| return -1; |
| } |
| char ip[32]; |
| printf("客户端IP:%s, 端口:%d\n", |
| inet_ntop(AF_INET, &client_socket.sin_addr.s_addr, ip, sizeof ip), |
| ntohs(client_socket.sin_port)); |
| |
| while(true){ |
| |
| char buff[255]; |
| int len = recv(client_socket_fd, buff, sizeof buff, 0); |
| if(len > 0){ |
| printf("received client messages:%s\n", buff); |
| |
| send(client_socket_fd, buff, sizeof buff, 0); |
| }else if(len == 0){ |
| printf("客户端断开了连接..."); |
| break; |
| }else{ |
| perror("recv error!"); |
| break; |
| } |
| } |
| close(client_socket_fd); |
| close(listen_socket_fd); |
| return 0; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| #include <cstdio> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| int main(){ |
| |
| int client_socket_fd = socket(AF_INET, SOCK_STREAM, 0); |
| if(client_socket_fd == -1){ |
| perror("socket error!\n"); |
| return -1; |
| } |
| |
| struct sockaddr_in server_addr; |
| server_addr.sin_family = AF_INET; |
| server_addr.sin_port = htons(9999); |
| inet_pton(AF_INET, "192.168.159.130", &server_addr.sin_addr.s_addr); |
| |
| int ret = connect(client_socket_fd, (struct sockaddr*) &server_addr, sizeof server_addr); |
| if(ret == -1){ |
| perror("connect error!\n"); |
| return -1; |
| } |
| |
| int count = 1; |
| while(true){ |
| printf("连接成功!\n"); |
| char buff[255]; |
| |
| sprintf(buff, "你好,我是客户端!发送给你%d\n",count ++ ); |
| send(client_socket_fd, buff, strlen(buff) + 1, 0); |
| |
| memset(buff, 0, sizeof buff); |
| int len = recv(client_socket_fd, buff, sizeof buff, 0); |
| if(len > 0){ |
| printf("受到数据:%s\n", buff); |
| }else if(len == 0){ |
| printf("服务器断开了连接...\n"); |
| break; |
| }else{ |
| perror("recv error!\n"); |
| break; |
| } |
| sleep(1); |
| } |
| close(client_socket_fd); |
| return 0; |
| } |
# UDP 套接字编程
# recvfrom 和 sendto 函数
| ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen); |
| |
| Synopsis: |
| #include <sys/socket.h> |
| Description: |
| sockfd: 用于通信的套接字描述符 |
| buff: 指向读入缓冲区的指针 |
| nbytes: |
| flags: |
| from: 指向数据报发送者的协议地址的套接字地址结构 |
| addrlen: 套接字地址结构大小 |
| |
| */ |
| ssize_t sendto(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen); |
| |
| Synopsis: |
| #include <sys/socket.h> |
| Description: |
| sockfd: 用于通信的 |
| buff: |
| nbytes: |
| flags: |
| to: 指向数据报接受者的协议地址(IP 地址以端口号)的套接字地址结构 |
| addrlen: 套接字地址结构大小 |
| */ |
# 高级 I/O 函数
# 套接字超时
# readv 和 writev 函数
这两个函数类似于 read
和 write
,不过 readv
和 writev
允许单个系统嗲用读入或写入一个或多个缓冲区。
# recvmsg 和 sendmsg 函数
| ssize_t recvmsg(int sockfd, struct sockaddr msghdr *msg, int flags); |
| ssize_t sendmsg(int sockfd, struct sockaddr msghdr *msg, int flags); |
| struct msghdr{ |
| void *msg_name; |
| socklen_t msg_namelen; |
| struct iovec *msg_iov; |
| size_t msg_iovlen; |
| void *msg_control; |
| size_t msg_controllen; |
| !! The type should be socklen_t but the |
| definition of the kernel is incompatible |
| with this. */ |
| int msg_flags; |
| }; |
| |
| Synopsis: |
| #include <sys/socket.h> |
| Description: |
| |
| |
| Return Value: |
| */ |
# 辅助数据