网络编程API

网络编程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 并不会自动销毁由线程创建的内存空间,要使用如下两种方法来明确销毁线程:

  1. 调用 pthread_join 函数。此函数不仅会等待指定的线程终止,还会引导线程销毁。
  2. 调用 pthread_detach 函数。此函数会将主线程与指定的子线程分离,分离后的子线程执行结束时,资源会自动回收。

理解:pthread 有 joinable 和 unjoinable 两种状态:

  1. joinable 状态:默认状态。当线程函数执行结束时或 pthread_exit 时不会释放线程所占用堆栈和线程描述符等资源。只有当调用了 pthread_join 之后这些资源才会被释放。
  2. 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统一处理,统一监听。典型的处理方案:

  1. 信号处理函数收到信号时,仅将信号传递给主循环
  2. 主循环统一监听套接字,判断为信号时再处理

如何将信号传递给主循环呢?利用管道:信号处理函数往管道写端写入信号值,主循环监听管道读端。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值