在网络编程结构下,在Server端和Client端都会有一个socket。通过将socket当作文件,我可以将我手中的数据写入socket:打开socket->写入数据可以读socket的数据:打开socket->读出数据。作为文件,socket当然有自己的文件描述符。
#include /* See NOTES */
#include
int socket(int domain, int type, int protocol);
该函数返回一个socket编号(文件描述符,唯一标识一个socket),是个int值(若失败就返回-1
)。
输入值(只关心前两个就行,第三个有点重复):
PF_xxxx
,设置地址时应该用AF_xxxx
。当然AF_INET
和PF_INET
的值是相同的,混用也不会有太大的问题。sockaddr
是通用的socket地址,此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息,但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构
sockaddr_in(在netinet/in.h中定义):
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
sin_family
指代协议族,在socket编程中只能是AF_INET
sin_port
存储端口号(使用网络字节顺序)
sin_addr
存储IP地址,使用in_addr
这个数据结构(in_addr.s_addr
就是32位IP地址),按照网络字节顺序存储IP地址
sin_zero
是为了让sockaddr
与sockaddr_in
两个数据结构保持大小相同而保留的空字节。
【Tips】这个地址结构根据地址创建socket时的地址协议族的不同,如ipv4对应的是:
struct sockaddr_in {
sa_family_t sin_family; /*地址族 AF_INET*/
in_port_t sin_port; /*端口号,网络字节序表示*/
struct in_addr sin_addr; /*v4的地址结构体*/
};
struct in_addr {
uint32_t s_addr; /*v4地址,网络字节序表示*/
};
ipv6对应的是:
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
struct in6_addr {
unsigned char s6_addr[16];
};
【注意此处"将sin_addr.s_addr设置为INADDR_ANY"的含义】
INADDR_ANY
转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。你只需绑定INADDR_ANY,管理一个套接字就行,不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。
bzero函数是c++ string.h中的函数。功能描述:置内存(字符串)前n个字节为零且包括‘\0’。
#include
void bzero(void *s, int n);//s为内存(字符串)指针,n 为需要清零的字节数。
现常用memset(&local, 0, sizeof(local));
的memset函数来代替!
包含的头文件为:"winsock2.h"
htonl()--"Host to Network Long"
ntohl()--"Network to Host Long"
htons()--"Host to Network Short"
ntohs()--"Network to Host Short"
htonl(host to net unsigned long)就是把本机字节顺序转化为网络字节顺序
网络字节顺序(大尾顺序)就是指一个数在内存中存储的时候“高对低,低对高”(即一个数的高位字节存放于低地址单元,低位字节存放在高地址单元中)。
但对于不同CPU主机,其主机字节顺序是不同的,因此为了不同的机器直接通信,需要进行统一的转换。
举例来说,数值0x2211使用两个字节储存:高位字节是0x22,低位字节是0x11。顺序为大端,逆序的为小端。之所以有小端字节序是因为计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。
这个函数用来给上面那个socket()函数返回的socket设置属性,作为服务端,可以设置重用地址和端口号。
int setsockopt(int sockfd , int level, int optname, void *optval, socklen_t *optlen);
SO_REUSEADDR
】:一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。即:允许端口被重复使用这个函数用来给socket绑定地址信息。
#include /* See NOTES */
#include
int bind (int sockfd, const struct sockaddr * addr, socklen_t addrlen);
struct sockaddr
的结构里并没有提供存放ip地址,端口号的属性,所以需要用struct sockaddr_in
来强制类型转换。#include /* See NOTES */
#include
int listen(int sockfd, int backlog);
注意这个函数调用的socket并不是连接使用的,如果我们这个socket用于通信了,那么接下来的客户连接怎么办,难道我们在绑定一个端口,那就冲突了。
IO多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux下实现IO多路复用的系统调用主要有select. poll和epoll。其包含在头文件:
#include
typedef union epoll_data {
void *ptr;//是给用户自由使用的,用于附带自定义消息
int fd;
__uint32_t u32;
__uint64_t u64;} epoll_data_t;
struct epoll_event {
__uint32_t events; /* epoll event */
epoll_data_t data; /* User data variable */
};
epoll_event.data.fd
|
就可以并选多个选项选项 | 说明 |
---|---|
EPOLLIN | 表示对应的文件描述符可以读(包括对端SOCKET正常关闭) |
EPOLLOUT | 表示对应的文件描述符可以写; |
EPOLLPRI | 表示对应的文件描述符有紧急的数可读(这里应该表示有带外数据到来) |
EPOLLERR | 表示对应的文件描述符发生错误; |
EPOLLRDHUP | 当socket接收到对方关闭连接时的请求之后触发,有可能是TCP连接被对方关闭,也有可能是对方关闭了写操作。(表示读关闭,内核不能再往内核缓冲区中增加新的内容。但是已经在内核缓冲区中的内容,用户态依然能够读取到。); |
EPOLLHUP | 表示对应的文件描述符被挂断(表示读写都关闭); |
EPOLLET | 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的 |
EPOLLONESHOT | 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里(以防多个线程相竞争) |
功能:该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围;
int epoll_create(int size)
用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数返回值——0成功,-1失败。
该函数用于轮询I/O事件的发生;
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数返回值——成功:返回发生的事件数;时间到时返回0;失败:-1
socklen_t是一种数据类型,它其实和int差不多,在32位机下,size_t和int的长度相同,都是32 bits,但在64位机下,size_t(32bits)和int(64 bits)的长度是不一样的,socket编程中的accept函数的第三个参数的长度必须和int的长度相同。于是便有了socklen_t类型。
其常指sockaddr_in
的长度的数据类型
函数执行之后,socket就会等待客户端的连接。当连接建立之后,返回一个用于通信的新的socket,这个新的socket用于客户端与服务端之间的通信。因此,该函数返回的是已经连接的socket描述字。但是,如何失败,那么返回-1。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockaddr_in
类型需要一步强制类型转换)【注意】accept()的第三个参数socklen_t *addrlen
和bind()的第三个参数socklen_t addrlen
不一样,accept需要一个指针(因此需要取地址符&
)
调用recv函数时:
recv
先等待 SOCKET s
的发送缓冲
中的数据被协议传送完毕,如果协议在传送s
的发送缓冲
中的数据时出现网络错误,那么recv
函数返回SOCKET_ERROR
;s
的发送缓冲区
中没有数据或者数据被协议成功发送完毕后,recv
先检查套接字s
的接收缓冲区;s
的接收缓冲区
中没有数据或者协议正在接收数据,那么recv
就一直等待,直到协议把数据接收完毕;recv
函数就把s
的接收缓冲区
中的数据copy到buf
中。(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv
函数仅仅是copy数据,真正的接收数据是协议来完成的), recv
函数返回其实际copy的字节数。如果recv
在copy时出错,那么它返回SOCKET_ERROR
;recv
函数在等待协议接收数据时网络中断了,那么它返回0。#include
int recv( SOCKET s, char *buf, int len, int flags);
//返回值:<0 出错,=0 连接关闭,>0 接收到的数据长度大小
当失败时,errno被设为以下的某个值 :
【特别注意(代码也有体现)】当返回值<0时并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
的情况下认为连接是正常的,继续接收,即进入下次循环即可。
在linux进行非阻塞的socket接收数据时经常出现Resource temporarily unavailable,errno代码为11(EAGAIN)。从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。这个错误不会破坏socket的同步,不用管它,下次循环接着recv就可以。一次你,对于非阻塞而言,这个不是错误!
例如,以 O_NONBLOCK的标志打开文件/socket/FIFO,如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返 回,read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。
linux 信号量相关函数都声明头文件 semaphore.h
头文件中,所以使用信号量之前需要先包含头文件。
而信号量的数据类型为结构sem_t
,它本质上是一个长整型的数。
该函数用于创建信号量:
int sem_init(sem_t *sem, int pshared, unsigned int value);
//返回值:创建成功返回0,失败返回-1
sem_wait 是一个阻塞的函数,测试所指定信号量的值,它的操作是原子的。若 s e m v a l u e > 0 sem_{value} > 0 semvalue>0,则该信号量值减去 1 并立即返回。若 s e m v a l u e = 0 sem_{value} = 0 semvalue=0,则阻塞直到 s e m v a l u e > 0 sem_{value} > 0 semvalue>0,此时立即减去 1,然后返回。
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
//操作成功返回0,失败则返回-1
下面的函数sem_trywait
是非阻塞的函数,它会尝试获取获取 s e m v a l u e sem_{value} semvalue值,如果 s e m v a l u e = 0 sem_{value} = 0 semvalue=0,不是阻塞住,而是直接返回一个错误 EAGAIN。
把指定的信号量 sem 的值加 1,唤醒正在等待该信号量的任意线程。
int sem_post(sem_t *sem);
//返回值:操作成功返回0,失败则返回-1
释放信号量自己占用的一切资源 (被注销的信号量sem
要求:没有线程在等待该信号量了)
int sem_destroy(sem_t * sem)
//成功则返回 0,失败返回 -1
进程内部的每一个线程都有唯一标识,叫线程ID。线程ID会返回给pthread_create()
的调用者,一个线程可以通过pthread_self()获取自己的线程ID。
#include
pthread_t pthread_self(void);
该函数的主要用途为:创建线程
#include
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg);
// 返回值:0为成功;若是正值则出现错误
该函数的主要用途为:将某个线程分离。
线程的分离状态决定一个线程以什么样的方式来终止自己。线程的默认属性是
非分离状态
,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()
函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程
不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。
#include
int pthread_detach(pthread_t thread);
//返回值:如果成功就返回0,否则返回一个正数
【注意】一旦线程处于分离状态,就不能使用pthread_join获取其状态,也无法返回“可连接”状态。
该函数的主要用途为:以阻塞的方式等待由thread标识的线程终止,如果线程已经终止,该函数会立即返回。
代码中如果没有
pthread_join
主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join
后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
#include
int pthread_join(pthread_t thread,void **retval);
//返回值:返回0表示成功,返回正数表示失败
joinable
(即上面提到的非可分离线程)的线程必须用pthread_join()
函数来释放线程所占用的资源,如果没有执行这个函数,那么线程的资源永远得不到释放。
该函数的主要用途为:终止调用线程,且其返回值可以通过调用pthread_join()来获取。调用pthread_exit()相当于在新线程函数start()中执行return,不同之处在于:
#include
void pthread_exit(void *retval);
下面系列函数都包含在头文件#include
中。
该函数的功能是初始化互斥锁。
pthread_mutex_init(pthread_mutex_t mutex,const pthread_mutexattr_t attr);
//成功返回0,失败返回errno
其中参数attr
指定了新建互斥锁的属性。如果参数attr为空,则使用默认的互斥锁属性,默认属性为快速互斥锁 。其共有四个类型:
PTHREAD_MUTEX_NORMAL
普通锁(默认)。当线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后先进先出原则获得锁。PTHREAD_MUTEX_ERRORCHECK
检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。PTHREAD_MUTEX_RECURSIVE
递归锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。PTHREAD_MUTEX_DEFAULT
适应锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。与pthread_mutex_init函数相配对的是pthread_mutex_destroy函数,当使用完互斥锁后将锁销毁。
pthread_mutex_destroy(pthread_mutex_t* mutex);
//使用完锁之后释放锁,常用于递归锁的时候
//成功返回0,失败返回errno
当pthread_mutex_lock()返回时,该互斥锁已被锁定。线程调用该函数让互斥锁上锁,如果该互斥锁已被另一个线程锁定和拥有,则调用该线程将阻塞,直到该互斥锁变为可用为止。
pthread_mutex_lock(pthread_mutex_t mutex);//加锁
pthread_mutex_trylock(*pthread_mutex_t *mutex);
//加锁,但是上面方法不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待
//成功返回0.失败返回错误信息
pthread_mutex_unlock是可以解除锁定 mutex 所指向的互斥锁的函数。
pthread_mutex_unlock(pthread_mutex_t *mutex);//释放锁
//成功返回0.失败返回错误信息
下面系列函数都包含在头文件#include
中。条件变量是线程同步的一种手段。条件变量用来自动阻塞一个线程,直到条件(predicate)满足被触发为止。通常情况下条件变量和互斥锁同时使用。
条件变量使我们可以睡眠等待某种条件出现。而创建和销毁条件边量需要用到pthread_cond_init函数和pthread_cond_destroy函数(类似于上面说的pthread_mutex_init和pthread_mutex_destroy)。
初始化条件变量的函数。
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
//函数成功返回0;任何其他返回值都表示错误。
//参数 2 条件变量的属性,一般传 NULL
结构pthread_condattr_t
是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用;如果选择为PTHREAD_PROCESS_SHARED则为多个进程间各线程公用。
销毁条件变量的函数。
int pthread_cond_destroy(pthread_cond_t *cond);
//函数成功返回0;任何其他返回值都表示错误。
需要注意的是只有在没有线程在该条件变量上等待时,才可以注销条件变量,否则会返回EBUSY。
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
需要注意的是:被阻塞的线程可以被pthread_cond_signal
函数,pthread_cond_broadcast
函数唤醒,也可能在被信号中断后被唤醒。也就是说pthread_cond_wait
函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。因此常常结合while
使用
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
//函数成功返回0;任何其他返回值都表示错误。
//函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cond参数指向的条件变量上。
该函数调用时需要传入 mutex参数(加锁的互斥锁) ,函数执行时,先把调用线程放入条件变量的请求队列,然后将互斥锁mutex解锁,当函数成功返回为0时,互斥锁会再次被锁上. 也就是说函数内部会有一次解锁和加锁操作.
函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
// cond是指向pthread_cond_t结构的指针,mutex是互斥锁的标识符,abstime为指向timespec结构体的指针。
计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEDOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
激活一个等待该条件的线程
int pthread_cond_signal(pthread_cond_t *cond);
//函数成功返回0;任何其他返回值都表示错误。
唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER
类型的,系统将根据线程的优先级唤醒线程。
函数以广播的方式唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cond被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。
int pthread_cond_broadcast(pthread_cond_t *cond);
//函数成功返回0;任何其他返回值都表示错误。
由于pthread_cond_broadcast
函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast
函数。
要记得先包含头文件#include
(找不到mysql/mysql.h头文件的时候,需要安装一个库文件:sudo apt install libmysqlclient-dev
)
连接数据库前,必须先创建MYSQL变量,此变量在很多Mysql API函数会用到。它包含了一些连接信息等数据。
typedef struct st_mysql {
NET net; /* Communication parameters 通讯参数,网络相关*/
unsigned char connector_fd; /* ConnectorFd for SSL 加密套接字协议层*/
char *host,*user,*passwd,*unix_socket,*server_version,
*host_info,*info,*db;//数据库用户名,密码,主机名,Unix套接字,版本,主机信息
unsigned int port,client_flag,server_capabilities;
unsigned int protocol_version;
unsigned int field_count;
unsigned int server_status;
unsigned long thread_id; /* Id for connection in server */
my_ulonglong affected_rows;
my_ulonglong insert_id; /* id if insert on table with NEXTNR */
my_ulonglong extra_info; /* Used by mysqlshow */
unsigned long packet_length;
enum mysql_status status;
MYSQL_FIELD *fields;
MEM_ROOT field_alloc;
my_bool free_me; /* If free in mysql_close */
my_bool reconnect; /* set to 1 if automatic reconnect */
struct st_mysql_options options;
char scramble_buff[9];
struct charset_info_st *charset;
unsigned int server_language;
} MYSQL;
该结构体中包含了查询结果集,也就是从数据库中查询到的数据。这个结构代表返回行的一个查询的(SELECT, SHOW, DESCRIBE, EXPLAIN)
的结果。
返回的数据称为“数据集”
,在C的API里对应的就是MYSQL_RES,从数据库读取数据,最后就是从MYSQL_RES中读取数据。
typedef struct st_mysql_res {
my_ulonglong row_count;
MYSQL_FIELD *fields;
MYSQL_DATA *data;
MYSQL_ROWS *data_cursor;
unsigned long *lengths; /* column lengths of current row */
MYSQL *handle; /* for unbuffered reads */
const struct st_mysql_methods *methods;
MYSQL_ROW row; /* If unbuffered read */
MYSQL_ROW current_row; /* buffer to current row */
MEM_ROOT field_alloc;
unsigned int field_count, current_field;
my_bool eof; /* Used by mysql_fetch_row */
/* mysql_stmt_close() had to cancel this result */
my_bool unbuffered_fetch_cancelled;
void *extension;
} MYSQL_RES;
这是一个行数据的类型安全(type-safe)的表示。当前它实现为一个计数字节的字符串数组。
typedef char **MYSQL_ROW;
1、首先要包含mysql的头文件,并链接mysql动态库。
2、创建MYSQL变量。如:MYSQL mysql;
3、mysql_init()
初始化MYSQL变量
4、使用mysql_real_connect()
建立一个到mysql数据库的连接
5、使用mysql_query()
执行查询语句
6、使用result = mysql_store_result(mysql)
,通过返回的MYSQL_RES变量result
获取查询结果数据。
7、(可选)使用mysql_num_fields(result)
获取查询的列数,mysql_num_rows(result)
获取结果集的行数
8、调用mysql_fetch_row(result)
函数读取结果集数据。
9、使用mysql_free_result(result)
释放结果集所占内存,防止内存泄漏。
10、使用mysql_close(conn)
关闭连接
函数用途:分配或初始化与mysql_real_connect()相适应的MYSQL对象
MYSQL *mysql_init(MYSQL *mysql)
【返回】初始化的MYSQL*句柄。如果无足够内存以分配新的对象,返回NULL。 错误,在内存不足的情况下,返回NULL。
连接数据库引擎,通过函数mysql_real_connect()尝试与运行在主机上的MySQL数据库引擎建立连接。
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag)
【返回】如果连接成功,返回值与第1个参数的值相同。如果连接失败,返回NULL
查询数据库中的某一个表内容,通过函数mysql_query()来实现。(不会返回内容,需要通过下面的mysql_store_result()
函数来获取内容后,通过mysql_fetch_row()
函数来显示出来)
int mysql_query(MYSQL *mysql, const char *query)
//query为执行的SQL语句对应的字符长串(Null终结的字符串)
【返回】如果查询成功,返回0。如果出现错误,返回非0值。
注意,mysql_query()
不能用于包含二进制数据的查询,应使用mysql_real_query()
取而代之。
显示查询数据库中数据表的内容,mysql_store_result()将mysql_query()查询的全部结果读取到客户端,分配1个MYSQL_RES
结构,并将结果置于该结构中。
MYSQL_RES *mysql_store_result(MYSQL *mysql)
【返回】具有多个结果的MYSQL_RES结果集合。如果出现错误,返回NULL。
MYSQL_ROW mysql_fetch_row(MYSQL_RES* result)
【返回】下一行的MYSQL_ROW
结构。如果没有更多要检索的行或出现了错误,返回NULL。
其中,行内值的数目由mysql_num_fields(result)
给出。也就是流程中提到的第七步。
释放由mysql_store_result()
、mysql_use_result()
、mysql_list_dbs()
等为结果集分配的内存。完成对结果集的操作后,必须调用mysql_free_result()
释放结果集使用的内存。
void mysql_free_result(MYSQL_RES *result)
关闭前面打开的连接。如果句柄是由mysql_init()
或mysql_connect()
自动分配的,mysql_close()
还将解除分配由mysql指向的连接句柄。
void mysql_close(MYSQL *mysql)
fcntl系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性。(经常用这个fcntl函数改变非阻塞)
#include
#include
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);
fcntl函数功能依据cmd的值的不同而不同。参数对应功能如下:
命令名 | 描述 |
---|---|
F_DUPFD | 复制由fd指向的文件描述符,调用成功后返回新的文件描述符,与旧的文件描述符共同指向同一个文件 |
F_GETFD | 获得文件描述符标志 |
F_SETFD | 设置文件描述符标志(设置为第三个参数arg的最后一位) |
F_GETFL | 获取文件状态标志(用于改变非阻塞) |
F_SETFL | 设置文件状态标志(用于改变非阻塞) |
F_GETLK | 获取文件锁 |
F_SETLK | 设置文件锁 |
F_SETLKW | 类似F_SETLK,但等待返回 |
F_GETOWN | 获取当前接收SIGIO和SIGURG信号的进程ID和进程组ID |
F_SETOWN | 设置当前接收SIGIO和SIGURG信号的进程ID和进程组ID |
【返回值】fcntl()的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。如:
stat()函数用于取得指定文件的文件属性,并将文件属性存储在结构体stat里,这里仅对其中用到的成员进行介绍。
#include
#include
#include
//获取文件属性,存储在statbuf中
int stat(const char *pathname, struct stat *statbuf);
struct stat
{
mode_t st_mode; /* 文件类型和权限 */
off_t st_size; /* 文件大小,字节数*/
};
用于将一个文件或其他对象映射到内存,提高文件的访问速度。
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
//如同malloc之后需要free一样,mmap调用创建的映射区使用完毕之后,需要调用munmap去释放。
函数的输入参数含义如下:
定义了一个向量元素,通常,这个结构用作一个多元素的数组。
struct iovec {
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
};
writev函数用于在一次函数调用中写多个非连续缓冲区,有时也将这该函数称为聚集写。
#include
2ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
若成功则返回已写的字节数,若出错则返回-1(并正确设置errno)。writev以顺序iov[0],iov[1]至iov[iovcnt-1]从缓冲区中聚集输出数据。
writev返回输出的字节总数,通常,它应等于所有缓冲区长度之和。
特别注意: 循环调用writev时,需要重新处理iovec中的指针和长度,该函数不会对这两个成员做任何处理。writev的返回值为已写的字节数,但这个返回值“实用性”并不高,因为参数传入的是iovec数组,计量单位是iovcnt,而不是字节数,我们仍然需要通过遍历iovec来计算新的基址,另外写入数据的“结束点”可能位于一个iovec的中间某个位置,因此需要调整临界iovec的io_base和io_len。
VA_LIST 是在C语言中解决变参问题的一组宏,变参问题是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。
【用法】
VA_LIST
型的变量 ,这个变量是指向参数的指针 ,通过指针运算来调整访问的对象;VA_START
宏初始化变量刚定义的 VA_LIST
变量 ,实际上 就是用 VA_LIST
去指向函数的最后一个具名的参数;VA_ARG
宏返回可变的参数,VA_ARG
的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用 VA_ARG
获取各个参数);因为栈地址是从高到低延伸的,所以加上你要的参数类型大小,就意味着栈顶指针指向你所要的参数,便可通过 底层 pop 得到。VA_END
宏结束可变参数的获取,即清空 va_list 。【问题】
当linux中的 api函数发生异常时,一般会将errno变量(需include errno.h)赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因,在实际编程中用这一招解决了不少原本看来莫名其妙的问题。
如:
#define EAGAIN 11 /* Try again */重试
#define EINTR 4 / Interrupted system call / 中断的系统调用
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。
#include
#include
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。
当套接字发送缓冲区变满时,send通常会阻塞,除非套接字设置为非阻塞模式,当缓冲区变满时,返回EAGAIN或者EWOULDBLOCK错误,此时可以调用select函数来监视何时可以发送数据。
退出当前运行的程序,并将参数value返回给主调进程
exit(0);//表示程序正常退出;除了0之外,其他参数均代表程序异常退出,如下面
exit(1);
exit(-1);
exit
其实和return
是一样使用的,但其中的区别如下:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
参数解释如下:
检查或修改与指定信号相关联的处理动作(可同时两种操作)
#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//返回值,0 表示成功,-1 表示有错误发生。
用来将参数set信号集初始化,然后把所有的信号加入到此信号集里。
#include
int sigfillset(sigset_t *set);
信号集是为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux系统中引入了信号集(信号的集合)。
#define SIGALRM 14 //由alarm系统调用产生timer时钟信号
#define SIGTERM 15 //终端发送的终止信号
设置信号传送闹钟,即用来设置信号SIGALRM在经过参数seconds秒数后发送给目前的进程。
#include ;
unsigned int alarm(unsigned int seconds);
如果未设置信号SIGALRM的处理函数,那么alarm()默认处理终止进程
在linux下,使用socketpair函数能够创建一对套接字进行通信,项目中使用管道通信。
#include
#include
int socketpair(int domain, int type, int protocol, int sv[2]);
//返回结果, 0为创建成功,-1为创建失败
参考文章如下:
Mysql接口API相关函数详细使用说明
linux编程:pthread
多线程之信号量
互斥锁
recv详解
fcntl函数的用法总结
一文让你搞懂 C语言可变参数 VA_LIST原理详解