提示:这里可以添加本文要记录的大概内容:
概念: 大端法: 高位存在低地址, 地位存在高地址
小端法: 高位存在高地址, 低位存在低地址
在TCP/IP 协议规定, 网络数据流对应采用大端字节序
网络字节序和主机字节序的转换
#include
uint32_t htonl(uint32_t hostlong); //本地转网络(ip地址)
uint16_t htons(uint16_t hostshort); //本地端口 转 网络端口
uint32_t ntohl(uint32_t netlong); //网络ip 转本地ip
uint16_t ntohs(uint16_t netshort); //网络端口 转 本地端口
如果主机是小端字节序, 这些函数将参数做相应的大小端转换然后返回, 如果主机是大端字节序, 这些函数不做转换, 将参数原封不动的返回
支持 ipv4 的in_addr, 与 ipv6 in6_addr;
#include
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src,char *dst,socklen_t size);
sockaddr 数据结构
struct sockaddr{
sa_familu_t sa_family;
char sa_data[14];
};
sockaddr_in 结构体
struct sockaddr_in{
_kernel_sa_familu_t sin_family; /*地址结构类型*/
_be16 sin_port; //端口号
struct inaddr sin_addr; //ip地址
/*Pad to size of 'struct sockaddr'.*/
unsigned char _pad[__SOCK_SIZE__ - sizeof(short int] -
sizeof(unsigned short int )-sizeof(struct in_addr);
};
struct in_addr{ //Internet address.
_be32 s_addr;
};
#include /*see notes*/
#include
int socket(int domain, int type, int protocol);
domain:
AF_INET 这是大多数用来产生socket 的协议,使用TCP或UDP来传输, 用IPV4的地址
AF_INET6 用IPV6的地址生产socket 协议
AF_UNIX 本地协议,使用在Unix 和Linux 系统上, 一般都是当客户端和服务器在同一台机器上得到时候使用type:
SOCK_STREAM 这个协议是按照顺序的, 可靠的,数据完整的基于字节流的链接.这是一个使用最多的socket 类型, 这个socket是使用TCP进行传输
SOCK_GREAM 这个协议是无连接的, 固定长度的传输调用.该协议是不可靠的,使用UDP来进行它的链接protocol:
传0表示使用默认协议
返回值:
成功:返回指向新创建的socket 的文件描述符;
失败: 返回-1,设置 errno
#include
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//其中第三个参数:
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr))'
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //任意IP地址
servaddr.sin_port=htons(6666);
sockfd:
socket 文件描述符
addr:
构造出IP地址加端口号
addrlen:
sizeof(addr) 长度
返回值:
成功返回0; 失败返回-1, 设置errno
#include
#include
int listen(int sockfd.int backlog);
sockfd:
socket 文件描述符
backlog:
排队建立3次握手和刚刚建立3次握手队列的链接数
返回值:
成功返回0 ,失败返回-1 设置errno
accept 函数
#include
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:
socket 文件描述符
addr:
传出参数,返回链接客户端地址信息,含IP地址,和端口号
addrlen:
传入传出参数(值-结果).传入sizeof(addr)大小, 函数返回时返回真正接受到地址结构体的大小 --客户端addr 实际大小
返回值:
成功返回一个新的socket文件描述符,用于和客户端通信, 失败返回-1, 设置errno
#include
#include
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
socket 文件描述符
addr:
传出参数,指定服务器端地址信息, 含IP地址和端口号
addrlen:
传入参数,传入sizeof(addr)大小- 服务器地址结构的大小
返回值:
成功返回0. 失败返回-1,设置errno
将原来函数进行封装,在调用时 如果函数错误,直接报错
//wrap.c
#include
#include
#include
void perr_exit(const char *s){
perror(s);
exit(1);
}
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr){
int n;
again:
if((n==accept(fd,sa,salenptr)<0){
if((errno==ECONNABORTED)||(errno==EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
int Bind(int fd, const struct sockaddr *sa, socklen_t salen){
int n;
if((n=bind(fd, sa, salen)<0))
perr_exit("bind error");
return n;
}
int Connect(int fd, const struct sockaddr *sa, socklen_t salen){
int n;
if((n=connect(fd,sa,salen))<0){
perr_exit("connect error");
}
return n;
}
int Listen(int fd, int backlog){
int n;
if((n=listen(fd,backlog))<0){
perr_eixt("listen error");
}
return n;
}
int Socket(int family, int type, int protocol){
int n;
if((n=socket(family, type,protocol))<0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd, void *ptr,size_t nbytes){
ssize_t n;
again:
if((n=read(fd,ptr,nbytes))=-1){
if(errno==EINTR)
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes){
ssize_t n;
again:
if((n=write(fd,ptr,nbytes)==-1){
if(errno==EINTR)
goto again;
else
return -1;
}
return n;
}
int close(int fd)
{
int n;
if((n=close(fd)==-1)
perr_exit("close error");
return n;
}
ssize_t Readn(int fd, void *vptr, size_t n){
szie_t nlefd;
ssize_t nread;
nleft=n;
while(nleft>0){
if((nread=read(fd,ptr,nleft)<0){
if(errno==EINTR)
nread=0;
else
return -1;
}else if(nread==0)
break;
nleft -=nread;// 要读的字节数- 已读入的字节数
ptr+=nread; //移动读指针
}
return n-nleft; //要读入的字节数-已读入的字节数.
}
ssize_t Writen(int fd, const void* vptr, size_t n)//写入n个字节
{
size_t nleft;
ssize_t nwriten;
const char *ptr;
ptr=vptr;
nleft=n;
while(nleft>0){
if((nwriten=write(fd,ptr,nleft))<=0){
if(nwriten<0&&errno==EINTR)
nwriten=0;
else
return -1;
}
nleft-=nwriten;
ptr+=nwriten;
}
return n; //写入n个字节完成
}
static ssize_t my_read(int fd, char *ptr){
static int read_cnt; //count
static char *read_ptr;
static char read_buf[100];
if(read_cnt<=0){
again:
if((read_cnt=read(fd,read_buf,sizeof(read_buf)))<0){
if(errno==EINTR)
goto again;
return -1;
}else if(read_cnt==0)
return 0;
read_ptr=read_buf;
}
read_cnt--;
*ptr=*read_ptr++;
return 1;
}
ssize_t Readline(int fd, void *vptr, size_t maxlen){
ssize_t n, rc;
char c, *ptr;
ptr =vptr;
for(n=1; n<maxlen;n++){
if((rc=my_read(fd,&c))==1){
*ptr ++=c;
if(c=='\n') //读一行
break;
}else if(rc==0){
*ptr=0;// 字符串最后一个元素为0;
return n-1; //读了多少字节
}
}
*ptr=0;
return n;
}
wrap.h
#ifndef _WRAP_H_
#define _WRAP_H_void
perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr,size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void* vptr, size_t n);
static ssize_t my_read(int fd, char *ptr);
#endif
函数名 | 功能 |
---|---|
epoll_create(int size) | 创建epoll 监听树 |
epoll_ctl(int epfd,int op, int fd. struct epoll_event *event) | 控制某个epoll监控的文件描述符上的事件: 注册,修改,删除 |
epoll_wait(int epfd, struct peoll_event *events, int maxevents, int timeout) | 等待所监控文件描述符上有事件的产生 |
suze: 监听数目
监听文件描述符的个数,与内存大小有关
epfd:
为epoll_creat的句柄 --具体是那一课epoll树
op:
表示动作,用3个宏来表示:
EPOLL_CTL_ADD (注册新的fd到epfd)
EPOLL_CTL_MOD (修改已经注册的fd的监听事件)
EPOLL_CTL_DEL (从epfd删除一个fd)evnet:
告诉内核需要监听的事件
struct epoll_event{
_uint32_t events; /*Epoll events*/
epoll_data_t data; /*User data variable*/
};
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
EPOLLIN: 表示对应的文件描述符可以读(包括对端socket 正常关闭)
EPOLLOUT: 表示对应的文件描述符可以写
EPOLLERR: 表示对应的文件描述符发生错误
events:
用来存内核得到事件的集合
maxevents:
告知内核这个events有多大, 这个maxevents的值不能大于创建epoll_create() 时的size,
timeout:
超时时间
-1: 阻塞
0: 立刻返回,非阻塞
0: 指定毫秒返回值:
成功返回有多少文件描述符就绪, 事件到时返回0. 出错时返回-1;
完整代码:
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024
int main(int argc, char *argv[]){
//定義變量
int i, j, maxi;
int listenfd, connfd, sockfd;
int nready, efd, res;
ssize_t n;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
int client[OPEN_MAX];
struct sockaddr_in cliaddr,servaddr;
struct epoll_event tep, ep[OPEN_MAX]
//開始調用函數
listenfd= Socket(AF_INET, SOCK_STREAM, 0);
bzero (&servaddr, sizeof(seraddr));
servaddr.sin_family=AF_INET; //ipv4
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
Bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
Listen(listenfd, 20);
for(i=0; i<OPEN_MAX; i++)
client[i]=-1;
maxi=-1;
efd=epoll_create(OPEN_MAX);
if(efd=-1){
perr_exit("epool_create");
}
tep.events=EPOLLIN;
tep.data.fd=listenfd;
res=epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
if(res==-1){
perr_exit("epoll_ctl");
}
while(1){
nready=epoll_wait(efd, ep, OPEN_MAX, -1); //阻塞監聽
if(nready==-1){
perr_exit("epoll_wait");
}
for(i=0;i<nready;i++){
if(!(ep[i].events & EPOLLIN))
continue;
if(ep[i].data.fd==listenfd){
clilen=sizeof(cliaddr);
connfd=Accept(listenfd, (struct sockaddr*)&cliaddrm &clilen);
printf("received from %s at PORT %d \n",
inet_ntop(AF_INE,&cliaddr.sin_addr,str,sizeof(str)),
ntols(cliaddr.sin_port));
for(j=0;j<OPEN_MAX;j++){
if(client[j]<0){
client[j]=connfd; //save descriptor
break;
}
}
if(j == OPEN_MAX)
perr_exit("too many clients");
if(j>maxi)
maxi=j; //max index in client[] array
tep.events=EPOLLIN;
tep.data.fd=connfd;
res=epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
if(res==-1){
perr_exit("epoll_ctl");
}else{
sockfd= ep[i].data.fd;
n=Read(sockfd, buf, MAXLINE);
if(n==0){
for(j=0;j<=maxi;j++){
if(client[j]==sockfd){
client[j]=-1;
break;
}
}
res=epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
if(res==-1){
perr_exit("epoll_ctl");
}
close(sockfd);
printf("client[%d] closed connection \n",j);
}else{
for(j=0; j<n;j++){
buf[j]=toupper(buf[j]);
}
Writen(sockfd, buf, n);
}
}
}
}
}
close(listenfd);
close(fd);
return 0;
}
*ssize_t read(int fd, void buf, size_t count);
参数:
fd:文件描述符
buf: 读取数据的缓冲区
count: 缓冲区大小返回值:
返回值>0; 实际读到的字节数 buf=1024
两种情况:
返回值==buf;
返回值返回值==0;
数据读完(读到文件, 管道,socket 末尾—对端关闭)
返回值==-1; 异常
1. errno==EINTR 被信号中断, 重启/quit
2. errno==EAGAIN;(EWOULLDBLOCK) 非阻塞方式读取, 并没有数据
3. errno==ECONNRESET; 说明连接被重置,需要close(),移除监听队列
4. 其他值,出现错误. ------perror_exit
*ssize_t write(int fd, const void buf, size_t count);
参数:
fd:文件描述符
buf: 待写数据的缓冲区
count: 缓冲区大小返回值:
成功: 写入字节数
失败: -1, 设置返回值==buf;
返回值
相比read和write一次只能下发一个IO请求,并将数据读写到一个指定的缓冲区,readv和writev可以一次性指定多个缓冲区进行读写。
writev与readv
#include
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
其中 const struct iovec *iov 設置多個緩衝區
struct iovec{
void *iov_base; /*starting address of buffer*/
size_t iov_len; //size of buffer;
}
writev和readv 都是以 iov[0],iov[1],…,iov[n-1] 的順序输出或者读入的, 返回值是输出|读入的字节总数长度之和.
writev 与 readv 详情
学习路径: 《c++程序与设计》,《数据结构》,《操作系统》,《计算机网络》,linux 基础操作, 系统编程, 网络编程;