一、服务端(server)
1.创建监听套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
socket()函数用来创建套接字
- domain:指出协议族(如AF_INET指定IPV4)
- type:指定协议类型,
- SOCK_STREAM指定字节流协议(如TCP)
- SOCK_DGRAM指定数据报协议(如UDP)
- protocol:指定具体协议,通常传递0
- 返回值:成功返回非负的套接字, 失败返回-1
2.绑定地址和端口号
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd参数为成功调用socket函数返回的套接字。
- addr 参数用于指定本地端口和地址。
- addlen参数指出addr结构的字节数
- 返回值:
- 成功返回0。
- 否则返回 -1 ,并将错误码存放于全局变量errno之中
3.通知内核监听套接字
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- sockfd参数为成功调用socket函数返回的套接字,并已经成功调用bind。
- backlog参数告诉套接字在忙于处理上一个请求时还可以接受多少个进入的请求,换句话说,这决定了挂起连接的队列的大小。
- 返回值:
- 成功返回0,
- 失败返回 -1, 并将错误码存放于全局变量errno之中
4.等待接收客户端请求
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd:监听文件描述符
- addr:通用地址结构体变量(保存请求客户端的IP+PORT)
- addrlen:保存结构体长度变量地址
- 返回值:
- 成功返回新的套接字,专门和对应客户端通信
- 失败返回-1
ps:阻塞函数,没有客户端请求就一直阻塞等待
二、客户端(client)
1.创建监听套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
socket()函数用来创建套接字
- domain:指出协议族(如AF_INET指定IPV4)
- type:指定协议类型,
- SOCK_STREAM指定字节流协议(如TCP)
- SOCK_DGRAM指定数据报协议(如UDP)
- protocol:指定具体协议,通常传递0
- 返回值:成功返回非负的套接字, 失败返回-1
2.请求连接
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
- sockfd:socket函数返回的socket描述符
- serv_addr:服务器的地址
- addrlen:sockaddr结构体的长度
三、辅助内容
1.本机端口号和网络端口相互转换
uint16_t htons(uint16_t hostshort);//本机 ---->网络
uint16_t ntohs(uint16_t netshort); //网络 ---->本机
2.点分十进制IP 地址 和 网络地址相互转换
int inet_pton(int af, const char *src, void *dst);
- 函数功能: 将点分十进制地址 转成网络地址
- 参数说明:
- af:地址族
- IPV4: AF_INET
- IPV6: AF_INET6
- src: ip地址内存的起始地址
- dst: 存放网络地址内存的起始地址
- af:地址族
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
-
函数功能:将网络地址 转成点分十进制地址 (字符串存储)
-
参数说明:
- af: 地址族
- src: 网络地址内存起始地址
- dst: 存放点分十进制地址内存的起始地址
- size : 内存的大小
-
地址族结构体:通过该结构体打包IP和端口号
-
原始结构体类型(已经不使用!):
struct sockaddr{ sa_family_t sa_family; /* 地址族 */ char sa_data[14]; /* 地址值,实际可能更长 */ };
-
改进后的结构体:
struct in_addr{ in_addr_t s_addr; }; struct sockaddr_in{ uint16_t sin_family; //地址族 IPv4: AF_INET uint16_t sin_port; // 网络字节序端口号 struct in_addr sin_addr; // 网络字节序的IP地址 char sin_zero[8]; };
四、端口复用
加在创建监听套接字之后
/*端口复用*/
int opt = 1;
setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
五、I/O复用
[1]select
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
-
参数说明:
-
nfds: 最大文件描述符+1
-
readfds: 监听读集合地址
-
writefds: 监听写集合地址 NULL
-
exceptfds: 监听异常集合地址 NULL
-
timeout :设置超时时间
struct timeval {
long tv_sec; /* seconds */ *
long tv_usec; /* microseconds */
};
-
-
返回值:
- 大于0:返回准备好的文件描述符个数
- =0: 超时时间到了
- -1: 错误
void FD_CLR(int fd, fd_set *set); /*将fd从set集合中移除*/
int FD_ISSET(int fd, fd_set *set); /*判断fd是否在set集合中*/
void FD_SET(int fd, fd_set *set); /*将fd加入set集合中*/
void FD_ZERO(fd_set *set); /*清空set集合*/
[2]epoll
-
创建集合空间
#include <sys/epoll.h> int epoll_create(int size);
参数:
- size:指定文件描述符个数
返回值:
- 成功:返回与集合关联的文件描述符
- 失败:返回-1
-
管理集合中的文件描述符
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数说明:
-
epfd:和集合关联的文件描述符
-
op:命令参数
- EPOLL_CTL_ADD :往集合中添加文件描述符
- EPOLL_CTL_MOD :修改集合中文件描述符的信息
- EPOLL_CTL_DEL :删除集合中指定的文件描述符
-
fd:操作的文件描述符
-
event:
-
如果是删除操作,该参数忽略,直接传NULL
-
如果是添加操作, 通过该参数告诉内核监听指定文件描述符的指定时间
-
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; events: EPOLLIN(读事件)
-
-
-
监听集合中的文件描述符
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
参数说明:
-
epfd:和集合关联的文件描述符
-
events:存放准备好文件描述符信息数组的起始地址
-
maxenents:数组最大元素个数
-
timeout:设置超时时间(ms)
- 大于0 : 指定超时时间
- =0 : 非阻塞函数
- -1 : 永久阻塞
-
返回值:
-
大于0 : 准备好的文件描述符个数
-
=0 :超时时间到了
-
-1 :出错
-
-