网络编程函数记录

    学习网络编程时候用到好多函数,初识之后有了个印象,然而每次再相遇还是得面向搜索引擎编程,所以打算记录一下。因为自己太菜了,所以有些地方难免表达错误,尽量改进提高。

概述

1. 网络编程基本API

1.1 socket

    网络编程肯定得先有个套接字,调用socket函数创建套接字。

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//成功时返回文件套接字,失败返回-1

    domain表示用哪个地址族,比如 AF_INET (表示IPv4)、AF_INET (表示IPv6)。
    type参数指明套接字类型,运输层协议用TCP的话就写个SOCK_STREAM,表示固定长度、有序、可靠、面向连接的字节流。如果用UDP,就写个SOCK_DGRAM,表示固定长度的无连接不可靠的报文传递。
    proctocol参数为协议类型,因为前面 type 选了SOCK_STREAMSOCK_DGRAM,这里直接写个 0 表示默认就行了。

注:这里就不把全部的可用选项贴出来了。

    这里要提一下可以把套接字看成文件(其实就是!),文件可以搞的操作,像创建、删除、输入、输出,对套接字也可以玩。其实我个人为了方便,把网络编程中的通信理解成就是在对套接字这个文件进行输入输出操作罢了,不过同时要注意文件描述符这个东西。

1.2 bind

    用bind函数将套接字与地址关联。

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
//成功时返回0,失败时返回-1

    sockfdsocket函数返回的套接字文件描述符;
    addr是指向套接字地址结构的指针;
    len是套接字地址的长度;

    有必要说明套接字地址的结构,Linux下通用的套接字地址结构体的定义为:

#include <bits/socket.h>
struct sockaddr{
	sa_family_t sa_family;
	char sa_data[14];
}

    其中sa_family为协议族类型的变量,sa_data存放套接字地址值。

    直接用通用的结构体设置IP地址和端口号不方便,一般用sockaddr_in(IPv4)和sockaddr_in6(IPv6)进行地址设置,然后在实际使用时强制转化成通用结构。

#include <arpa/inet.h>
struct sockaddr_in{
	sa_family_t sin_family;//地址族,AF_INET
	uint16_t sin_port;//端口号
	struct in_addr sin_addr;//IPv4地址结构体
}
struct in_addr{
	uint32_t s_addr;//IPv4地址
}
struct sockaddr_in6{
	sa_family_t sin6_family;//地址族,AF_INET
	uint16_t sin6_port;//端口号
	uint32_t sin6_flowinfo;//流信息,应设置为0
	struct in6_addr sin6_addr;//IPv6地址结构体
	uint32_t sin6_scope_id;//SCOPE ID (我不懂)
}
struct in6_addr{
	uint8_t s6_addr[16];//IPv6地址
}

    注意只有在服务器端才要给接收客户请求的套接字关联地址。在客户端,对于客户端套接字,就没必要用bind关联客户端地址,在用connect函数建立连接时系统自动分配了。之所以这样搞,是因为服务器端口应公共周知,连接时需要用确定的端口号。客户端套接字端口号自动分配就可以了,自己指定反而容易冲突。

1.3 listen

    服务器端进行了套接字与地址关联的关联后,调用listen函数后,服务器端就可以监听套接字。

#include <sys/socket.h>
int listen(int sockfd, int backlog);
//成功时返回0,失败时返回-1

    sockfd即为被监听的套接字,调用listen函数后,该套接字就能接收连接请求。

1.4 accept

    调用listen函数之后,服务器端使用accept函数接受连接请求并建立连接。

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr , socklen_t *addrlen);//若成功返回非负描述符,若出错,则返回-1
//成功时返回套接字文件描述符,失败时返回-1

    sockfd为监听套接字;
    addr用于获取发出连接请求的客户端套接字地址;
    addrlen用于指出上述地址的长度。

    服务器端调用该函数返回新的套接字文件描述符,该套接字连接到客户端,服务器端借助该套接字与客户端通信。

1.5 connect

    bindlisten是服务器端为接收连接请求做准备,最后通过accept接受连接请求。而客户端则是通过connect函数发起连接。

#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
//返回:若成功则为0,若出错则为-1

    sockfd为客户端套接字文件描述符;
    addr为服务器监听的套接字地址;
    addrlen用于指定上述地址的长度。

    客户端调用该函数使客户端套接字与服务器端套接字建立连接后,之后就可以借助sockfd来与服务器端通信。注意在客户端sockfd没有绑定到一个套接字地址,调用connect函数后会绑定一个默认地址。

1.5 close和shutdown

    通信结束后要关闭连接,套接字像是文件,可以用close来关闭连接。

#include<unistd.h>
int close(int fd);//fd为文件描述符
//成功返回0,失败返回-1

    注意该函数的作用只是将对应的文件描述符的引用计数减 1。减到 0 才算是真的关闭,也就是说多进程的时候要用好几次close
    此外还可以调用shutdown函数关闭连接,该函数还能实现连接的半关闭,另外该函数还能立即终止连接,并非将所谓的引用计数减 1。

#inlcude<sys/socket.h>
int shutdown(int sockfd,int howto)
//成功时返回0,失败时返回-1

    sockfd为待关闭的套接字文件描述符;
    howto指定采取何种关闭方式。可选值有:SHUT_RD关闭输入;SHUT_WR关闭输出和SHUT_RDWR:同时关闭输入输出。

2. I/O复用

2.1 select

    通过select函数,我们等待所感兴趣的文件描述符将对应的事件准备就绪。这话很难描述,其实就是比如有个客户要给我们服务器传来信息,而我们要读取这些信息,那么在建立连接后,服务器得到新的文件描述符,就要守着这个文件描述符,看它是否准备好被我们进行读操作。(应该是这样理解吧)

#include <sys/select.h>
int select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
//成功时返回就绪的文件描述符数目,失败返回-1

    maxfdpl表示所感兴趣的文件描述符总数;
    readsetwritesetexceptset,分别指向我们所感兴趣的可读、可写、处于异常条件的文件描述符集合。这三个指针指向的对象均为 fd_set 结构体类型。该结构体中有一个数组,数组的每个位标记一个文件描述符。fd_set能容纳的文件描述符数量受限(1024)。对于fd_set结构,有以下几个接口可以对其进行操作。

#include <sys/select.h>
FD_ZERO(fd_set *fdset);//清除fdset的所有位
FD_SET(int fd,fd_set *fdset);//设置fdset的位fd
FD_CLR(int fd,fd_set *fdset);//清除fdset的位fd
int FD_ISSET(int fd, fd_set *fdset); //测试fdset的位fd是否被设置

    在声明了一个文件描述符集后先用FD_ZERO把这个文件描述符集置0。在调用select函数前,要用FD_SET对我们感兴趣的文件描述符的位进行设置。从select返回时用FD_ISSET测试某个位是否打开,就是判断对应的文件描述符是否就绪。

    timeout指定超时时长,设置为NULL表示一直阻塞。对timeval 结构的参数进行设置可以设置超时时长。timeval 结构体定义如下。

struct timeval{
    long tv_sec;//秒
    long tv_usec;//微秒
}

    通过向select函数传入上述参数,函数返回时我们就可以知道我们所感兴趣的哪些文件描述符已经准备就绪,例如某个文件描述符已经可读了。之后就能调用对应的I/O函数进行通信,比如对可读的文件描述符进行读取。

2.2 poll

    poll函数功能与select函数相似。

#include <poll.h>
int poll (struct pollfd *fds, unsigned long nfds, int timeout);

    fds是一个指向结构体数组的指针,每个元素都是一个pollfd结构体,该结构体定义如下:

struct pollfd {
    int     fd;//文件描述符
    short   events;//注册的事件
    short   revents;//返回的事件
};

    可以看到select函数要求分别传入可读、可写、异常的文件描述符集合,而poll函数则是传入指向pollfd结构体的数组的指针,每个结构保存感兴趣的文件描述符和对应的事件,此外内核还能告诉我们返回的事件,可以表示的事件类型如下表所示。

事件能否作为events能否作为revents描述
POLLIN普通或优先级数据可读
POLLRDNORM普通数据可读
POLLRDBAND优先级数据可读
POLLRDI高优先级数据可读
POLLOUT普通数据可写
POLLWRNORM同上
POLLWRBAND优先级数据可写
POLLERR×已出错
POLLHUP×已挂断
POLLHUP×描述符不是一个打开的文件

    nfds参数指定fds集合的大小;
    timeout指定超时时长。-1表示一直等待,即阻塞;0表示不等待立即返回;设置大于0的值表示等待对应的时间,单位为毫秒。

2.3 epoll

    epoll机制为Linux特有。其与selectpoll在使用上有较大差别,epoll机制使用一系列的函数完成类似的任务。使用epoll机制首先需要调用epoll_create函数创建一个文件描述符,用来标识一个事件表。epoll把感兴趣的文件描述符及对应事件注册到了这个表中。

#include <sys/epoll.h>
int epoll_create(int size);

    size指定事件表的大小。

    epoll_ctl函数用于对内核事件表进行操作。

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    epfd:为epoll_create返回的标识事件表的文件描述符,函数对这个事件表进行操作。
    op:指定操作类型,EPOLL_CTL_ADD表示注册,EPOLL_CTL_MOD表示修改,EPOLL_CTL_DEL表示删除;
    fd:表示要操作的文件描述符;
    event:指定对应的事件,能描述的事件类型与poll的相同。

struct epoll_event {
	__uint32_t events; //epoll事件
	epoll_data_t data; //用户数据
};
typedef union epoll_data {
	void *ptr;
	int fd;
	__uint32_t u32;
	__uint64_t u64;
} epoll_data_t;

    epoll机制使用epoll_wait函数等待感兴趣的文件描述符发生对应的事件。

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
//成功时返回就绪的文件描述符数目,失败时返回-1

    epfd为标识事件表的文件描述符;
    events从内核得到的事件集合;
    maxevents最大事件数量;
    timeout超时时间,含义与poll的相同。

    epoll机制与前面的方法的不同之处还在于,能将对应的就绪事件复制到events参数所指向的数组中。这样一来,就不用花时间寻找就绪事件了。此外,epoll没有文件描述符数量的限制。
    注意这里的表述有点变了,前面都是在讲文件描述符集合,现在变成了事件集合,其实都是一样的。文件描述符和所关心的事件是成对出现的,二者的数量是一样的。

注:LT模式和ET模式

水平触发(Level Trigger,LT)模式:默认的工作模式,即EPOLLIN。当epoll_wait检测到fd上有事件发生就会通知应用程序该事件的发生,若事件没有及时处理,在下次调用epoll_wait时还会通知事件的发生,比如没有读完或者写完,还会通知。即如果没有处理完那么就还会触发,所以不需要循环读写。

边缘触发(Edge Trigger,ET)模式:可通过往fd注册EPOLLET事件将工作模式改为ET模式,当epoll_wait检测到fd上有事件发生就会通知应用程序该事件的发生,若事件没有及时处理,则epoll_wait不会再通知事件的发生。即使没有读写完,也不会通知了,因此必须循环读写,把事件处理完。与LT模式相比,ET模式减少了事件重复触发的次数,提高了效率,但注意在ET模式下必须保证及时将事件处理完毕。另外要注意在ET模式下应使用非阻塞I/O。如果用阻塞I/O,循环读写完所有数据后,没有后续事件了,就会一直处于阻塞状态。使用非阻塞I/O,读写完后 errno 返回 EAGAIN,通过判断这个条件成立,就可以跳出循环停止读写了。

参考资料:
[1]《Linux高性能服务器编程》
[2]《Unix环境高级编程》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值