多年前写的笔记,同步上传下。
一、基础socket函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <sys/socket.h> int socket (int domain, int type, int protocol) ; int bind (int sockfd, struct sockaddr *myaddr, socklen_t addrlen) ; int listen (int sockfd, int backlog) ; int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;int connect (int sockfd, struct sockaddr *serv_addr, socklen_t addrlen) ;
1.1常用TCP连接方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #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 ; int serv_sock=socket(PF_INET, SOCK_STREAM, 0 ); int ret = bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof (serv_addr); ret = listen(serv_sock, 5 ); 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选项 1 2 3 4 5 6 #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地址转换函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <arpa/inet.h> struct sockaddr_in { sa_family_t sin_family; uint16_t sin_port; struct in_addr sin_addr; char sin_zero[8 ]; } struct in_addr { in_addr_t s_addr; } unsigned short htons (unsigned short ) ;unsigned short ntohs (unsigned short ) ;unsigned long htonl (unsigned long ) ;unsigned long ntohl (unsigned long ) ;in_addr_t inet_addr (const char * string) ; int inet_aton (const char * string, struct in_addr* addr) ; char * inet_ntoa (struct in_addr adr) ; int inet_pton (int af, const char * src, void * dst) ;const char * inet_ntop (int af, const void * src, char * dst, socklen_c cnt) ;
2.1套接字创建过程中常见的网络地址信息初始化方法: 步骤:
声明一个sockaddr_in(用于IPV4)
将struct全部置为0
sin_family
Sin_addr.s_addr (将点分十进制IP转为32位整数)
Sin_port (将字符串转为整数,并转为大端序)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <arpa/inet.h> #include <string.h> #include <stdlib.h> char *ip = "192.168.0.109" ; char *port = "9190" ; struct sockaddr_in addr ;memset (&addr, 0 , sizeof (addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ip); addr.sin_port = htons(atoi(port));
三、基础IO函数 1 2 3 4 5 6 7 8 #include <sys/types.h> #include <sys.stat.h> #include <fcntl.h> int open (const char *path, int flag) ;
注意包含的头文件区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <unistd.h> int close (int fd) ;ssize_t read (int fd, void * buf, size_t nbytes) ; ssize_t write (int fd, const void * buf, size_t nbytes) ;
sock编程接口提供了几个专门用于socket数据读写的系统调用,它们增加了对数据读写的控制,比如传输带外数据。
1 2 3 4 5 6 #include <sys/types.h> #include <sys/socket.h> ssize_t recv (int sockfd, void * f, size_t len, int flags) ;ssize_t send (int sockfd, const void * buf, size_t len, int flags) ;
四、IO复用基础函数 4.1 EPOLL函数 三步走,注册内核事件表->向事件表中添加要监听的文件描述符->调用epoll_wait监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <sys/epoll.h> int epoll_create (int size) ;int epoll_ctl (int epfd, int op, int fd, struct epoll_event* event) ;int epoll_wait (int epfd, struct epoll_event* events, int maxevents, int timeout) ;
其中涉及的epoll_event结构体
1 2 3 4 5 6 7 8 9 10 11 12 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的常规用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <sys/epoll.h> #define MAX_EVENT_NUMBER 1024 epoll_event events[MAX_EVENT_NUMBER]; int epollfd = epoll_create(5 );assert(epollfd!=-1 ); 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 ); int i;for (i=0 ; i<ret; i++){ int sockfd = events[i].data.fd; }
4.2 POLL函数用法 poll函数与select函数相似,也是指定时间轮询一定数量的文件描述符。
1 2 3 4 5 6 7 8 9 10 11 12 #include <poll.h> int poll (struct pollfd* fds, nfds_t nfds, int timeout) ;struct pollfd { int fd; short events; short revents; } typedef unsigned long int nfds_t ;
4.2.1 poll的常规用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #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 ; 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; } }
五、高级IO函数 5.1 pipe函数 创建管道
1 2 3 4 #include <unistd.h> int pipe (int fd[2 ]) ;
创建双向管道
5.2 spilice函数 splice函数用于在两个文件描述符间移动数据,零拷贝操作(CPU不需要先将数据从某处内存复制到另一个特定区域)。
1 2 3 4 5 6 7 #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) ;
注意: splice函数中的两个文件描述符必须有一个是管道文件描述符。
当文件描述符为管道文件描述符时,偏移量必须设置为NULL。
5.3 fcntl函数 fcntl函数提供了对文件描述符的各种控制操作。
1 2 3 4 #include <fcntl.h> int fcntl (int fd, int cmd, ...) ;
常用来设置非阻塞套接字。
1 2 3 4 5 6 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 线程创建 1 2 3 4 5 6 7 8 #include <pthread.h> int pthread_create (pthread_t * restrict thread, const pthread_attr_t * restrict attr, void *(* start_routine)(void *), void * restrict arg) ;
restrict 是 C99 引入的一种类型限定符,它告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。
6.1.2 线程销毁 1 2 3 4 5 6 #include <pthread.h> int pthread_join (pthread_t thread, void ** status) ;
注意:pthread_join会阻塞主函数,直到子线程结束。
1 2 3 4 5 #include <pthread.h> int pthread_detach (pthread_t thread) ;
== 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 互斥锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <pthread.h> int pthread_mutex_init (pthread_mutex_t * mutex, const pthread_mutexattr_t * attr) ;int pthread_mutex_destory (pthread_mutex_t * mutex) ;int pthread_mutex_lock (pthread_mutex_t * mutex) ; int pthread_mutex_unlock (pthread_mutex_t * mutex) ;
6.2.2 信号量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <semaphore.h> int sem_init (sem_t * sem, int pshared, unsigned int value) ;int sem_destory (sem_t * sem) ;int sem_wait (sem_t * sem) ; int sem_post (sem_t * sem) ;
6.2.3 条件变量 xx
七、进程相关函数 xx
八、信号处理 目标进程在收到一个信号时,需要定义一个信号处理函数,原型如下
1 2 3 #include <signal.h> typedef void (*__sighandler_t ) (int ) ;
Linux可用信号定义在 bits/signum.h头文件中,常用的信号有
SIGINT:输入CTRL+C
SIGALARM
SIGPIPE:向读端被关闭的管道写数据或socket连接中写数据
SIGCHLD:子进程终止
SIGTERM
SIGURG:socket收到紧急数据
SIGIO
8.1 信号函数 为一个信号设置处理函数,可以利用以下的系统调用:
1 2 3 4 5 6 #include <signal.h> _sighandler_t signal (int sig, _sighandler_t _handler) ;
或者更robust的系统调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <signal.h> int sigaction (int sig, const struct sigaction* act, struct sigaction* oact) ;struct sigaction { _sighandler_t sa_handler; _sigset_t sa_mask; int sa_flags; }; int sigfillset (sigset_t * set ) ;
8.2 统一事件源 将信号和IO统一处理,统一监听。典型的处理方案:
信号处理函数收到信号时,仅将信号传递给主循环
主循环统一监听套接字,判断为信号时再处理
如何将信号传递给主循环呢?
利用管道:信号处理函数往管道写端写入信号值,主循环监听管道读端。