网络编程API
一、基础socket函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// 功能:创建套接字。
// 参数:domain:采取的协议族,一般为 PF_INET;
// type:数据传输方式,一般为 SOCK_STREAM;
// protocol:一般设为 0 即可。
// 返回值:成功时返回文件描述符,失败时返回 -1
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
// 功能:为套接字分配地址信息。
// 参数:sockfd:要分配地址信息的套接字文件描述符;
// myaddr:存有地址信息的结构体变量指针;
// addrlen:第二个参数的长度。
// 返回值:成功时返回 0,失败时返回 -1
int listen(int sockfd, int backlog);
// 功能:将套接字转换为可接收连接的状态。
// 参数:sock:希望进入等待连接请求状态的套接字文件描述符;
// backlog:连接请求等待队列的长度,最多使 backlog 个连接请求进入队列。
// 返回值:成功时返回 0,失败时返回 -1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 功能:受理连接请求等待队列中待处理的连接请求。
// 参数:sock:服务器套接字的文件描述符;
// addr:用于保存发起连接请求的客户端地址信息;
// addrlen:第二个参数的长度。
// 返回值:成功时返回创建的套接字文件描述符,失败时返回 -1
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
// 功能:请求连接。
// 参数:sock:客户端套接字的文件描述符;
// serv_addr:保存目标服务器端地址信息的结构体指针;
// addrlen:第二个参数serv_addr的长度(单位是字节)
// 返回值:成功时返回 0,失败时返回 -1
1.1常用TCP连接方法
#include <stdio.h>
#include <sys/socket.h>
int main(int argc, char *argv[]){
if(argc!=3){
printf("Usage : %s <ip> <port>\n", argv[0]);
exit(1);
}
const char* ip = argv[1];
char* port = argv[2];
struct sockaddr_in serv_addr;
/* addr网络地址信息初始化 */
int serv_sock=socket(PF_INET, SOCK_STREAM, 0);
//assert(serv_sock >= 0);
int ret = bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr);
//assert(ret != -1 );
ret = listen(serv_sock, 5);
//assert(ret != -1 );
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int connfd = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
assert(connfd>0);
printf("a client has been connected!\n");
}
1.2 socket选项
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int option_name, void* option_value,
socklen_t* restrict option_len);
int setsockopt(int sockfd, int level, int option_name, void* option_value,
socklen_t* restrict option_len);
二、网络地址信息初始化
结构体sockaddr_in以及IP地址转换函数
#include <arpa/inet.h>
struct sockaddr_in {
sa_family_t sin_family; // 地址族
uint16_t sin_port; // 16 位端口号
struct in_addr sin_addr; // 表示 32 位 IP 地址的结构体
char sin_zero[8]; // 不使用
}
struct in_addr {
in_addr_t s_addr; // 32 位 IP 地址,实际位为 uint32_t 类型
}
/* 本地字节序与主机字节序的转换 */
//short 类型,用于端口号的转换
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
//long 类型,用于 IP 地址的转换
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
/* IP地址字符串与整数的转换 */
in_addr_t inet_addr(const char* string);
// 功能:将字符串形式的 IP 地址转换为 32 位整型数据并返回。
// 返回值:成功时返回 32 位大端序整型值,失败时返回 INADDR_NONE。
int inet_aton(const char* string, struct in_addr* addr);
// 功能:将字符串形式的 IP 地址转换为 32 位网络字节序整数并存储到 addr 中。
// 返回值:成功时返回 1,失败时返回 0
char* inet_ntoa(struct in_addr adr);
// 功能:将网络字节序的整数型 IP 地址转换为字符串形式
// 返回值:成功时返回转换的字符串地址值,失败时返回 -1
/* 更新的IP字符串与整数的转换函数 */
int inet_pton(int af, const char* src, void* dst);
// 功能:将字符串表示的IP地址src转换成网络字节序整数表示,存入dst内。af表示地址族,AF_INET
// 返回值:成功时返回 1,失败时返回 0
const char* inet_ntop(int af, const void* src, char* dst, socklen_c cnt);
// 与inet_pton功能相反,最后一个参数cnt表示目标存储单元的大小
// 返回值:成功时返回 1,失败时返回 0
2.1套接字创建过程中常见的网络地址信息初始化方法:
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
char *ip = "192.168.0.109"; // 声明 IP 地址字符串
char *port = "9190"; // 声明端口号字符串
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); // memset #include<string.h>
//bzero(&addr, sizeof(addr)); //bero #include<string.h>
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip);
//inet_pton(AF_INET, ip, &addr.sin_addr);
/* serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); */
addr.sin_port = htons(atoi(port)); //atoi #include <stdlib.h>
三、基础IO函数
#include <sys/types.h> //for size_t, pid_t etc
#include <sys.stat.h>
#include <fcntl.h>
int open(const char *path, int flag);
// 功能:按 flag 指定的模式打开文件。
// 参数:path:文件名的地址;
// flag:文件打开的模式。
// 返回值:成功时返回文件描述符,失败时返回 -1
注意包含的头文件区别
#include<unistd.h>
int close(int fd);
// 功能:关闭 fd 对应的文件或套接字。当关闭一个套接字时会向对方发送 EOF。
// 参数:fd:文件或套接字的文件描述符。
// 返回值:成功时返回 0,失败时返回 -1
ssize_t read(int fd, void* buf, size_t nbytes);
// 功能:从文件 fd 读取数据。read 函数会阻塞,直到读取到数据或 EOF 才返回。
// 参数:fd:文件描述符;buf:保存要接收的数据;nbytes:要接收的最大字节数。
// 返回值:成功时返回接收的字节数(遇到文件尾则返回 0),失败时返回 -1
ssize_t write(int fd, const void* buf, size_t nbytes);
// 功能:向文件 fd 输出数据。
// 参数:fd:文件描述符;buf:要传输的数据;nbytes:要传输的字节数。
// 返回值:成功时返回写入的字节数,失败时返回 -1
sock编程接口提供了几个专门用于socket数据读写的系统调用,它们增加了对数据读写的控制,比如传输带外数据。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void* f, size_t len, int flags);
//成功返回实际读写数据的长度,出错返回-1,返回0代表通信对方关闭连接。
ssize_t send(int sockfd, const void* buf, size_t len, int flags);
//buf是读缓冲区。失败返回-1
四、IO复用基础函数
4.1 EPOLL函数
三步走,注册内核事件表->向事件表中添加要监听的文件描述符->调用epoll_wait监听
#include <sys/epoll.h>
int epoll_create(int size);
// 功能:申请 size 大小的 epoll 内核事件表
// 参数:size:申请的 epoll 例程的大小
// 返回值:成功时返回 epoll 文件描述符,失败时返回 -1。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
// 功能:在 epoll 例程内部注册要监视的文件描述符
// 参数:epfd:epoll 例程的文件描述符;
// op:指定监视对象的添加、删除或更改等操作。EPOLL_CTL_ADD||EPOLL_CTL_DEL
// fd:需要注册的监视对象文件描述符。
// event:监视对象的事件类型 EPOLLIN|EPOLLET
// 返回值:成功时返回 0,失败时返回 -1
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
// 功能:监视发生事件的文件描述符
// 参数:epfd:epoll 例程的文件描述符;
// events:保存发生事件的文件描述符集合的结构体地址;
// maxevents:最多监听的事件数,必须大于 0;
// timeout:超时时间,以 ms 为单位。如果为 -1,则一直等待到事件发生。
// 返回值:成功时返回发生事件的文件描述符数量,失败时返回 -1。
其中涉及的epoll_event结构体
struct epoll_event
{
__uint32_t events;
epoll_data_t data;
}
typedef union epoll_data // 注意:epoll_data 是一个联合不是结构体
{
void* ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;
4.1.1 epoll的常规用法
#include <sys/epoll.h>
#define MAX_EVENT_NUMBER 1024
epoll_event events[MAX_EVENT_NUMBER]; //保存监听结果
int epollfd = epoll_create(5);
assert(epollfd!=-1);
/*add socket to epollfd*/
epoll_event event;
event.data.fd = socket;
event.events = EPOLLIN;
epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, &events);
/*监听返回结果*/
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
//-1表示阻塞调用,直到事件发生
int i;
for(i=0; i<ret; i++){
int sockfd = events[i].data.fd;
/*直接对就绪的sockfd进行处理*/
}
4.2 POLL函数用法
poll函数与select函数相似,也是指定时间轮询一定数量的文件描述符。
#include <poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
//返回值:成功返回就绪的文件描述符的总数,失败返回-1
//timeout参数以毫秒为单位,若为-1则阻塞调用直至事件发生
struct pollfd{
int fd;
short events; //注册的事件 POLLIN|POLLRDHUP
short revents; //实际发生的事件,由内核填充
}
typedef unsigned long int nfds_t;
4.2.1 poll的常规用法
#include <poll.h>
#define MAX_EVENT_NUMBER 64
pollfd fds[MAX_EVENT_NUMBER];
fds[0].fd = sockfd;
fds[0].events = POLLIN;
fds[0].revents = 0;
fds[1].fd = sockfd2;
fds[1].events = POLLIN;
fds[1].revents = 0;
//....until MAX_EVENT_NUMBER
/*调用poll监听*/
int ret = poll(fds, MAX_EVENT_NUMBER, -1); //阻塞调用
int i;
for(i=0; i<MAX_EVENT_NUMBER; i++) //必须遍历所有文件描述符找到就绪者
{
if(fds[i].revents & EPOLLIN)
{
int sockfd = fds[i].fd;
/*处理sockfd上的时间*/
}
}
五、高级IO函数
5.1 pipe函数
创建管道
#include <unistd.h>
int pipe(int fd[2]);
//功能:创建管道,fd[1]是写端,fd[0]是读端
//返回值:成功返回0,失败返回-1
创建双向管道
int sockpair()
5.2 spilice函数
splice函数用于在两个文件描述符间移动数据,零拷贝操作(CPU不需要先将数据从某处内存复制到另一个特定区域)。
#include <fcntl.h>
ssize_t splice(int fd_in, loff_t* off_in, int fd_out, loff_t* off_out,
size_t len, unsigned int flags);
//功能:从fd_in移动数据到fd_out,移动数据的长度是len
//参数:off_in 是输入数据流的偏移,flags控制数据的移动
//flags:SPLICE_F_MOVE|SPLICE_F_MORE
//返回值:成功返回移动的字节数量,失败返回-1并设置errno
**注意:**splice函数中的两个文件描述符必须有一个是管道文件描述符。
当文件描述符为管道文件描述符时,偏移量必须设置为NULL。
5.3 fcntl函数
fcntl函数提供了对文件描述符的各种控制操作。
#include <fcntl.h>
int fcntl(int fd, int cmd, ...);
//该函数时可变参数。
//cmd常见F_GETFL, F_SETFL
常用来设置非阻塞套接字。
int setnonblocking(int fd){
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option|O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
六、线程相关函数
6.1 线程
6.1.1 线程创建
#include <pthread.h>
int pthread_create(pthread_t* restrict thread, const pthread_attr_t* restrict attr,
void*(* start_routine)(void *), void* restrict arg);
// 功能:创建线程并设置该线程的入口函数,然后运行该线程。
// 参数:thread:用于保存新创建的线程 ID;
// attr:用于传递线程属性,为 NULL 时创建默认属性的线程;
// start_routine:相当于线程的 main 函数; arg:传递 start_routine 函数的参数信息。
// 返回值:成功时返回 0,失败时返回其他值。
restrict 是 C99 引入的一种类型限定符,它告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。
6.1.2 线程销毁
#include <pthread.h>
int pthread_join(pthread_t thread, void** status);
// 功能:等待线程 thread 的终止,并获取线程 main 函数的返回值。
// 参数:thread:要等待的线程 ID;
// status:用于保存线程的 main 函数返回值的指针变量的地址值。
// 返回值:成功时返回 0,失败时返回其他值。
注意:pthread_join会阻塞主函数,直到子线程结束。
#include <pthread.h>
int pthread_detach(pthread_t thread);
// 功能:分离线程,将线程的状态转换为 unjoinable 状态。
// 参数:thread:需要分离的线程 ID;
// 返回值:成功时返回 0,失败时返回其他值。
pthread_detach与join区别?
销毁线程的两种方法
Linux 并不会自动销毁由线程创建的内存空间,要使用如下两种方法来明确销毁线程:
- 调用 pthread_join 函数。此函数不仅会等待指定的线程终止,还会引导线程销毁。
- 调用 pthread_detach 函数。此函数会将主线程与指定的子线程分离,分离后的子线程执行结束时,资源会自动回收。
理解:pthread 有 joinable 和 unjoinable 两种状态:
- joinable 状态:默认状态。当线程函数执行结束时或 pthread_exit 时不会释放线程所占用堆栈和线程描述符等资源。只有当调用了 pthread_join 之后这些资源才会被释放。
- unjoinable 状态:线程占用的资源会在线程函数退出时或 pthread_exit 时自动释放。pthread_detach() 函数就是分离线程,即将线程状态转换为 unjoinable 状态,以保证资源的释放。
此外 unjoinable 属性也可以在 pthread_create 时指定。
6.2 线程同步
6.2.1 互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr);
// 功能:创建互斥量。
// 参数:mutex:用于保存操作系统创建的互斥量;
// attr:设置即将创建的互斥量属性,不需要指定属性时设为 NULL。
// 返回值:成功时返回 0,失败时返回其他值。
int pthread_mutex_destory(pthread_mutex_t* mutex);
// 功能:销毁互斥量。
// 参数:mutex:保存将要销毁的互斥量;
// 返回值:成功时返回 0,失败时返回其他值。
int pthread_mutex_lock(pthread_mutex_t* mutex);
// 功能:加锁;返回值:成功时返回 0,失败时返回其他值。
int pthread_mutex_unlock(pthread_mutex_t* mutex);
// 功能:解锁;返回值:成功时返回 0,失败时返回其他值。
6.2.2 信号量
#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);
// 功能:创建信号量
// 参数:sem:用于保存创建的信号量;
// pshared:取 0 时,创建只允许一个进程内部使用的信号量,取其他值时,创建可由多个进程共享的信号量;
// value:要创建的信号量的初始值;
// 返回值:成功时返回 0,失败时返回其他值。
int sem_destory(sem_t* sem);
// 功能:销毁信号量
// 参数:sem:保存将要销毁的信号量;
// 返回值:成功时返回 0,失败时返回其他值。
int sem_wait(sem_t* sem);
// 功能:将信号量值减 1;返回值:成功时返回 0,失败时返回其他值。
int sem_post(sem_t* sem);
// 功能:将信号量值加 1;返回值:成功时返回 0,失败时返回其他值。
6.2.3 条件变量
xx
七、进程相关函数
八、信号处理
目标进程在收到一个信号时,需要定义一个信号处理函数,原型如下
#include <signal.h>
typedef void (*__sighandler_t) (int);
//信号处理函数__sighandler_t只有一个整形参数
Linux可用信号定义在 bits/signum.h头文件中,常用的信号有
- SIGINT:输入CTRL+C
- SIGALARM
- SIGPIPE:向读端被关闭的管道写数据或socket连接中写数据
- SIGCHLD:子进程终止
- SIGTERM
- SIGURG:socket收到紧急数据
- SIGIO
8.1 信号函数
为一个信号设置处理函数,可以利用以下的系统调用:
#include <signal.h>
_sighandler_t signal (int sig, _sighandler_t _handler);
// 功能:为信号sig注册处理函数_handler
// 参数:sig为信号,_handler为函数指针
// 返回值:前一次调用signal的传入的函数指针,第一次调用时返回默认的函数处理指针SIG_DEF
// 出错返回SIG_ERR,设置errno
或者更robust的系统调用:
#include <signal.h>
int sigaction(int sig, const struct sigaction* act, struct sigaction* oact);
// 功能:为信号sig注册处理动作act
// 参数:sig为信号,act为新的信号处理方式,oact为旧的信号处理函数
// 返回值:成功时返回0,失败时返回-1
/*同名结构体sigaciton定义如下*/
struct sigaction{
_sighandler_t sa_handler;
_sigset_t sa_mask;
int sa_flags;
};
// 该结构体中sa_handler设置信号处理函数
// sa_mask设置信号屏蔽的掩码
// sa_flag设置程序收到信号时的行为
int sigfillset(sigset_t * set);
// sigfillset()用来将参数set信号集初始化,然后把所有的信号加入到此信号集里
// 即将所有的信号标志位置为1
// 返回值 执行成功则返回0,如果有错误则返回-1。
8.2 统一事件源
将信号和IO统一处理,统一监听。典型的处理方案:
- 信号处理函数收到信号时,仅将信号传递给主循环
- 主循环统一监听套接字,判断为信号时再处理
如何将信号传递给主循环呢?利用管道:信号处理函数往管道写端写入信号值,主循环监听管道读端。