Linux网络编程
TCP/IP四层模型
-
包括应用层(Telent,FTP(文件传输协议),e-mail),传输层(TCP,UDP),网络层(IP,ICMP,IGMP),链路层(设备驱动程序及接口)
-
以太网帧格式:
- 其中的 源地址 和 目的地址 是指网卡的硬件地址(也叫MAC地址),长度是48位,是在网卡出厂时固化的。可在shell 中使用ifconfig命令查看,"HWaddr 00:15:F2:14:9E:3F”部分就是硬件地址。协议字段有三种值,分别对应 IP、ARP、RARP 。帧尾是CRC校验码。
- ARP(地址解析协议) 负责把设备
IP
地址翻译为MAC
地址 RARP
(逆地址解析协议),当一个设备启动并连接到网络,会连接RARP服务器,并告诉他自己的mac地址,RARP服务器根据mac地址在自己的库中寻找合适的ip地址,并返回给调用端。
网络字节序和本地字节序转换
- (pc本地存储)通常采用小端法
- (网络存储)通常采用大端法
htonl
本地存储转化为网络 对应ip (l)32位htons
同时,但对应端口 (s) 16位ntohl
网络转化为本地 32位ntohs
16位
IP地址转换函数
正常转换流程:192.168.1.11 --> string --> atoi --> int --> hton1 --〉网络字节序
int inet_pton(int af,const char* src,void* dst) //本地字节序(string ip)--》 网络字节序
af:AF_INEF(ipv4),AF_INET6(ipv6)
src: 传入参数,ip地址(点分十进制);
dst: 传出,转换后的网络字节序的ip地址
返回值:
成功 1;
失败 -1;
返回0 表示src表示的不是一个有效的ip地址
const char* inet_ntop(int af,const void*src,char* dst,socklen_t size) //网络字节序--》本地字节序(string ip)
参数:af AF_INET ,AF_INET6
src 网络字节序ip地址
dst 本地字节序ip地址
size dst的大小
成功 返回 dst
失败 NULL
socket编程
sockaddr地址结构
struct sockaddr_in addr;
addr.sin_family = AF_INET/AF_INET6;
addr.sin_port = htons(9528);
addr.sin_addr.s_addr = htonl(INADDR_ANY); //表示取出系统有效的任意ip地址,二进制形式
bind(fd,(struct sockaddr*)&addr,size);
socket函数
#include<sys/socket.h>
int socket (int domain , int type , int protocol); // 创建一个套接字
domain: AF_INET/AF_INET6/AF_UNIX
type: SOCK_STREAM/SOCK_DGRAM //决定套接字的类型 第一个对应提供面向连接的可靠的基于字节流的服务 第二个 对应提供无连接的不可靠的基于数据报的服务
protocol : 0 // 协议 0表示默认协议
返回值:
成功 新套接字所对应的文件描述符
失败 -1,errno
int bind(int sockfd , const struct sockaddr*addr , socklen_t addrlen); //给socket绑定一个 地址结构(ip+port)
sockfd:socket 函数返回值
struct sockaddr_in addr
addr.sin_family =AF_INET
addr.sin_prot = htons(232)
addr.sin_add.s_addr = htonl(INADDR_ANY)
addr: (struct sockaddr* )&addr;
addrlen: sizeof(addr) //地址结构的大小
返回值:
成功 0
失败 -1 errno
int accept(int sockfd , struct sockaddr*addr , socklen_t *addrlen); //阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符
sockfd:socket 函数返回值;
addr:传出参数,成功与服务器建立连接的那个客户端的地址结构(IP+port)
socklen_t clir_addr_len =sizeof(addr);
addrlen :传入传出 &clir_addr_len
入 addr的大小 出 客户端addr的实际大小
返回值:
成功 能与服务器进行数据通信的socket 对应的文件描述符
失败 -1 errno
int connect(int sockfd,const struct sockaddr*addr ,socklen_t addrlen); //使用现有的socket与服务器建立连接
sockfd:socket函数的返回值
addr 传入参数,服务器的地址结构
addrlen 服务器的地址结构大小
返回值:
成功 0;
失败 -1 errno
如果不使用bind绑定客户端地址结构 采用“隐式绑定”
TCP通信流程分析
server:
1,socket()创建socket
2,bind()绑定服务区地址结构
3,listen() 设置监听上限 //listen( lfd , 128)
4,accept() 阻塞监听客户端连接
5,read(fd) 读socket获取客户端数据
6,...
7,close()
client:
1,socket();
2,connect()与服务建立连接;
3,...
4,close();
TCP状态时序图
-
1,主动发起连接请求端:
CLOSE
– 发送SYN
–SEND_SYN
– 接收ACK、SYN
–SEND_SYN
– 发送ACK
–ESTABLISHED
(数据通信态) -
2,主动关闭连接请求端:
ESTABLISHED
(数据通信态)–发送FIN
–FIN_WAIT_1
--接收ACK
—FIN_WAIT_2
(半关闭) – 接收对端发送
FIN
–FIN_WAIT_2(半关闭)
–回发ACK
–TIME_VAIT
(只有主动关闭连接方,会经历该状态)–等2MSL时长--CLOSE
-
3,被动接受连接请求端 :
CLOSE
--``LISTEN--接收
SYN--
LISTEM--发送
ACK、SYN--
STM_RCVD--接收ACK--
ESTABLISHED`(数据通信态) -
4, 被动关闭连接请求端:
ESTABLISHED
(数据通信态)–接收``FIN--
ESTABLISHED(数据通信态)--发送
ACK--
CLOSE_WAIT(说明对端【主动关闭连接端】处于半关闭状态)--发送
FIN--
LAST_ACK--接收ACK --
CLOSE`
端口复用
int opt =1; //设置端口复用
setsockopt( lfd , SOL_SOCKET , SO_REUSEADDR , (void*)&OPT , szieof(opt));
多路I/O转接服务器
select函数
-
原理: 借助内核,select来监听
-
void FD_ZERO(fd_set*set); //清空一个文件描述符集合 fd_set rset; FD_ZERO(&rset); void FD_SET(int fd,fd_set*set) //将待监听的文件描述符,添加到监听集合中 FD_SET(3,&rset); void FD_CLR(int fd,fd_set*set) //将一个文件描述符从监听集合中移除 FD_CLR(4,&rset); void FD_ISSET(int fd,fd_set*set) //判断一个文件描述符是否在监听集合中 返回值 : 在1,不在0; FD_ISSET(4,&rset);
-
int select(int nfds , fd_set*readfds , fd_set*writefds , fd_set*exceptfds , struct timeval *timeout); nfds:监听的所有文件描述符中,最大值加1 readfds: 读文件描述符监听集合。 传入传出参数 writefds: 写文件描述符监听集合 传入传出参数 excepfds: 异常文件描述符监听集合 传入传出参数 timeout : >0 设置监听超时时长 NULL 阻塞监听 0 : 非阻塞监听,轮询 struct timeval { long tv_sec /* seconds*/ long tv_usec /* microseconds*/ } 返回值: 成功 表示特定文件描述符集合中 已就绪的文件描述符的数量; 失败 -1 errno;
-
实战:
-
1. #include <stdio.h> 2. #include <stdlib.h> 3. #include <unistd.h> 4. #include <string.h> 5. #include <arpa/inet.h> 6. #include <ctype.h> 7. 8. #include "wrap.h" 9. 10. #define SERV_PORT 6666 11. 12. int main(int argc, char *argv[]) 13. { 14. int i, j, n, nready; 15. 16. int maxfd = 0; 17. 18. int listenfd, connfd; 19. 20. char buf[BUFSIZ]; /* #define INET_ADDRSTRLEN 16 */ 21. 22. struct sockaddr_in clie_addr, serv_addr; 23. socklen_t clie_addr_len; 24. 25. listenfd = Socket(AF_INET, SOCK_STREAM, 0); 26. int opt = 1; 27. setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 28. bzero(&serv_addr, sizeof(serv_addr)); 29. serv_addr.sin_family= AF_INET; 30. serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 31. serv_addr.sin_port= htons(SERV_PORT); 32. Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); 33. Listen(listenfd, 128); 36. fd_set rset, allset; /* rset 读事件文件描述符集合 allset 用来暂存*/ 38. maxfd = listenfd; 40. FD_ZERO(&allset); 41. FD_SET(listenfd, &allset); /* 构造 select 监控文件描述符集 */ 43. while (1) { 44. rset = allset; /* 每次循环时都从新设置 select监控信号集 */ 45. nready = select(maxfd+1, &rset, NULL, NULL, NULL); 46. if (nready < 0) 47. perr_exit("select error"); 48. 49. if (FD_ISSET(listenfd, &rset)) { /* 说明有新的客户端链接请求 */ 50. 51. clie_addr_len = sizeof(clie_addr); 52. connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len); /* Accept 不会阻塞 */ 53. 54. FD_SET(connfd, &allset); /* 向监控文件描述符集合 allset 添加新的文件描述符 connfd */ 55. 56. if (maxfd < connfd) 57. maxfd = connfd; 58. 59. if (0 == --nready) /* 只有 listenfd 有事件, 后续的 for 不需执行 */ 60. continue; 61. } 62. 63. for (i = listenfd+1; i <= maxfd; i++) { /* 检测哪个 clients 有数据就绪 */ 64. 65. if (FD_ISSET(i, &rset)) { 66. 67. if ((n = Read(i, buf, sizeof(buf))) == 0) { /* 当 client 关闭链接时, 服务器端也关闭对应链接 */ 68. Close(i); 69. FD_CLR(i, &allset); /* 解除 select 对此文 件描述符的监控 */ 70. 71. } else if (n > 0) { 72. 73. for (j = 0; j < n; j++) 74. buf[j] = toupper(buf[j]); 75. Write(i, buf, n); 76. } 77. } 78. } 79. } 80. 81. Close(listenfd); 82. 83. return 0; 84. }
-
select函数的优缺点:
- 缺点,监听上限受文件描述符影响 最大位1024
- 优点 跨平台
poll函数
-
int poll(struct pollfd*fds, nfds_t nfds, int timeout); 参数: struct pollfd{ int fd; 带监听的文件描述符 short events 带监听的文件描述符对应的监听事件 short revents 传入时 0 如果满足对应事件的话 传出为 非0 } nfds 实际有效的监听个数 timeout 超时时长 -1 阻塞等待 0 不阻塞 >0 超时时长 返回值: 返回满足对应监听事件的文件描述符总个数
-
缺点:不能跨平台,无法直接定位到满足监听的文件的描述符
-
优点: 自带数组结构, 拓展监听上限
read函数返回值
- 大于0 实际读到的字节数
- 等于 0 socket 中 表示对端关闭 , close()
- -1
- 如果 errno == EINTR 被异常终止,需要重启
- 如果 errno == EAGIN 或 EWOULDBLOCK 以非阻塞方式读数据 。需要再次读
- 如果 errno == ECONNRESET 说明连接被重置, 需要 close 移除监听队列
epoll函数
-
epoll
是Linux
下多路复用lo接口``select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPu利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核lo事件异步唤醒而加入
Ready`队列的描述符集合就行了。 -
int epoll_create(int size); size :创建的红黑树的监听节点数量(仅供内核参考) 返回值: 成功:指向新创建的红黑树根节点的fd; 失败: -1 errno int epoll_ctl(int epfd,int op,int fd, struct epoll_event* event); epfd:epoll_create函数的返回值; op:对该监听红黑树所做的操作 EPOLL_CTL_ADD 添加fd到监听红黑树 EPOLL_CTL_MOD 修改fd在监听红黑树上的监听事件 EPOLL_CTL_DEL 将一个fd从监听红黑树上去掉 fd: 待监听的fd event 本质 struct epoll_event; 结构体地址 events: EPOLLIN/EPOLLOUT/EPOLLERR data: 联合体 int fd; 对应监听事件fd void* ptr uint32_t u32; uint64_t u64; 返回值: 成功:0 失败:-1 int epoll_wait(int epfd,epoll_event*events,int maxevents,int timeout) //阻塞jiant epfd epoll_create函数返回值 events 传出参数 数组 满足监听条件的fd结构体 maxevents 数组 元素的总个数 timeout -1 阻塞 0 buzuse 大于0 超时时间 返回值 : 》0 满足监听的总个数 ,可以用作循环上限 0: 没有fd满足监听事件 -1 errno
-
epoll实现的多路io转接
-
1. #include <stdio.h> 2. #include <unistd.h> 3. #include <stdlib.h> 4. #include <string.h> 5. #include <arpa/inet.h> 6. #include <sys/epoll.h> 7. #include <errno.h> 8. #include <ctype.h> 9. 10. #include "wrap.h" 11. 12. #define MAXLINE 8192 13. #define SERV_PORT 8000 14. 15. #define OPEN_MAX 5000 16. 17. int main(int argc, char *argv[]) 18. { 19. int i, listenfd, connfd, sockfd; 20. int n, num = 0; 21. ssize_t nready, efd, res; 22. char buf[MAXLINE], str[INET_ADDRSTRLEN]; 23. socklen_t clilen; 24. 25. struct sockaddr_in cliaddr, servaddr; 26. struct epoll_event tep, ep[OPEN_MAX]; //tep: epoll_ctl 参数 ep[] : epoll_wait 参数 27. 28. listenfd = Socket(AF_INET, SOCK_STREAM, 0); 29. int opt = 1; 30. setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口复用 31. bzero(&servaddr, sizeof(servaddr)); 32. servaddr.sin_family = AF_INET; 33. servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 34. servaddr.sin_port = htons(SERV_PORT); 35. Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); 36. Listen(listenfd, 20); 37. 38. efd = epoll_create(OPEN_MAX); //创建 epoll 模型, efd 指向红黑树根节点 39. if (efd == -1) 40. perr_exit("epoll_create error"); 41. 42. tep.events = EPOLLIN; 43. tep.data.fd = listenfd; //指定 lfd 的监听时间为"读" 44. 45. res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); //将 lfd 及对应的结构体设置到树上,efd可找到该树 46. if (res == -1) 47. perr_exit("epoll_ctl error"); 48. 49. for ( ; ; ) { 50. /*epoll 为 server 阻塞监听事件, ep 为 struct epoll_event 类型数组, OPEN_MAX 为数组容量, -1表永久阻塞*/ 51. nready = epoll_wait(efd, ep, OPEN_MAX, -1); 52. if (nready == -1) 53. perr_exit("epoll_wait error"); 54. 55. for (i = 0; i < nready; i++) { 56. if (!(ep[i].events & EPOLLIN)) //如果不是"读"事件, 继续循环 57. continue; 58. 59. if (ep[i].data.fd == listenfd) { //判断满足事件的 fd 是不是 lfd 60. clilen = sizeof(cliaddr); 61. connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); //接受链接 62. 63. printf("received from %s at PORT %d\n", 64. inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str) ), 65. ntohs(cliaddr.sin_port)); 66. printf("cfd %d---client %d\n", connfd, ++num); 67. 68. tep.events = EPOLLIN; tep.data.fd = connfd; 69. res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); //加入红黑树 70. if (res == -1) 71. perr_exit("epoll_ctl error"); 72. 73. } else { //不是 lfd, 74. sockfd = ep[i].data.fd; 75. n = Read(sockfd, buf, MAXLINE); 76. 77. if (n == 0) { //读到 0,说明客户端关闭链接 78. res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); //将该文件描述符从红黑树摘除 79. if (res == -1) 80. perr_exit("epoll_ctl error"); 81. Close(sockfd); //关闭与该客户端的链接 82. printf("client[%d] closed connection\n", sockfd); 83. 84. } else if (n < 0) { //出错 85. perror("read n < 0 error: "); 86. res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); //摘除节点 87. Close(sockfd); 88. 89. } else { //实际读到了字 节数 90. for (i = 0; i < n; i++) 91. buf[i] = toupper(buf[i]); //转大写,写回给客户端 92. 93. Write(STDOUT_FILENO, buf, n); 94. Writen(sockfd, buf, n); 95. } 96. } 97. } 98. } 99. Close(listenfd); 100. Close(efd); 101. 102. return 0; 103.}
ET和LT模式
- ET 边缘触发,只有数据到来时才触发,不管缓冲区是否还有数据
- 缓冲区未读尽的数据不会导致epoll_wait()返回,新的事件满足,才会触发
- LT 水平触发, 只要有数据就会触发
- 缓冲区未读尽的数据会导致
epoll_wait
返回
- 缓冲区未读尽的数据会导致
epoll反应堆
-
概述
- epoll ET模式+ 非阻塞,轮询+void* ptr
-
原来: socket 、 bind 、 listen – epoll_create 创 建 监 听 红 黑 树 – 返 回 epfd –
epoll_ctl() 向树上添加一个监听 fd – while(1)–
– epoll_wait 监听 – 对应监听 fd 有事件产生 – 返回 监听满足数组。 – 判断返回数
组元素 – lfd 满足 – Accept – cfd 满足
– read() — 小->大 – write 回去。
反应堆:不但要监听 cfd 的读事件、还要监听 cfd 的写事件。
socket 、 bind 、 listen – epoll_create 创 建 监 听 红 黑 树 – 返 回 epfd –
epoll_ctl() 向树上添加一个监听 fd – while(1)–
– epoll_wait 监听 – 对应监听 fd 有事件产生 – 返回 监听满足数组。 – 判断返回数
组元素 – lfd 满足 – Accept – cfd 满足
– read() — 小 -> 大 – cfd 从 监 听 红 黑 树 上 摘 下 – EPOLLOUT – 回 调 函 数 –
epoll_ctl() – EPOLL_CTL_ADD 重新放到红黑上监听写事件
– 等待 epoll_wait 返回 – 说明 cfd 可写 – write 回去 – cfd 从监听红黑树上摘下
– EPOLLIN
– epoll_ctl() – EPOLL_CTL_ADD 重新放到红黑上监听读事件 – epoll_wait 监听 -
反应堆的理解:加入 IO 转接之后,有了事件,server 才去处理,这里反应堆也是这样,由于网
络环境复杂,服务器处理数据之后,可能并不能直接写回去,比如遇到网络繁忙或者对方缓冲区已
经满了这种情况,就不能直接写回给客户端。反应堆就是在处理数据之后,监听写事件,能写会客
户端了,才去做写回操作。写回之后,再改为监听读事件。如此循环。 -
main逻辑 : 创建套接字——》 初始化连接 ——》 超时验证 ——》 监听 ——》 处理读事件和写事件
线程池描述结构体
struct threadpool_t{
pthread_mutext_t lock; //用于锁住本结构体
pthread_mutext_t thread_counter //记录忙状态线程个数的锁 ————》busy_thr_num
pthread_cond_t queue_not_full //当任务队列满时,添加任务的线程阻塞,等待此条件变量
pthread_cond_t queue_not_empty //任务队列不为空时,通知等待任务的流程
pthread_t *threads //存放线程池中每个线程tid的数组;
pthread_t adjust_tid //存管理线程tid
threadpool_task_t *task_queue //任务队列(数组首地址)
int min_thr_num; //线程池中最小线程数
int max_thr_num; //线程池中最大线程数
int live_thr_num; //线程池中当前存活的线程数
int busy_thr_num; //忙状态线程数量
int wait_exit_thr_num; //要销毁的线程数量
int queue_front; //队头下标
int queue_rear; //队尾下标
int queue_size; //队伍中实际任务数
int queue_max_size; //队伍中容量上限
int shutdown; //标志位,线程池使用状态 true或false
}
- mian结构
- 创建线程池
- 向线程池中添加任务,借助回调处理任务
- 销毁线程
- pthreadpool_create()函数
- 创建线程池结构体指针
- 初始化线程池结构体
- 创建N个任务线程
- 创建一个管理者线程
- 失败时销毁开辟的所有空间
- 子线程回调函数
- threadpool_thread()
- 进入子线程回调函数
- 接收参数
void* arg
——>pool
结构体 - 加锁 ——》lock 整个结构体锁
- 判断条件变量 ——》wait
- 管理者线程
- adjust_thread()
- 循环10s 执行一次
- 进入管理者线程回调函数
- 接收参数
void* arg
——>pool
结构体 - 获取管理者线程要用到的变量 —>
task_num live_num busy_num
- 根据既定算法 使用上述三个变量 判断是否应该创建 销毁线程池中指定步长的线程
UDP实现的server和clinet
lfd= socket(AF_INET ,SOCK_DGRAM,0)
listen
可有可无- read(cfd,buf,szieof) ——>替换为 recvfrom函数 (涵盖accept传出地址结构)
- write 被替换为 sendto()
recvfrom
ssize_t recvfrom(int sockfd,void *buf,size_t len,int flags,struct scokaddr*src_addr,socklen_t *addrlen);
参数:
sockfd 套接字
buf 缓冲区
len 缓冲区大小
flags 0
src_addr 传出参数 对端地址结构
addrlen 传入传出。
返回值
成功接受数据字节数
失败 -1 errno
sento函数
ssize_t sendto(int sockfd,const void*buf,size_t len,int flags,const struct sockaddr*dest_addr,socklen_t addrlen);
参数:
sockfd 套接字
buf 存储数据缓冲区
len 数据长度
flags 0
src_addr: 传入 目的地址结构
addrlen 地址结构长度
成功返回读出数据字节数
失败 -1
udp实现的并发服务器和客户端
#include<string.h>
#include<stdio.h>
#include<unstd.h>
#include<arpa/inet.h>
#include<ctype.h>
9.int main(void)
10. {
11. struct sockaddr_in serv_addr, clie_addr;
12. socklen_t clie_addr_len;
13. int sockfd;
14. char buf[BUFSIZ];
15. char str[INET_ADDRSTRLEN];
16. int i, n;
17.
18. sockfd = socket(AF_INET, SOCK_DGRAM, 0);
19.
20. bzero(&serv_addr, sizeof(serv_addr));
21. serv_addr.sin_family = AF_INET;
22. serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
23. serv_addr.sin_port = htons(SERV_PORT);
24.
25. bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
26.
27. printf("Accepting connections ...\n");
28. while (1) {
29. clie_addr_len = sizeof(clie_addr);
30. n = recvfrom(sockfd, buf, BUFSIZ,0, (struct sockaddr *)&clie_addr, &clie_addr_len);
31. if (n == -1)
32. perror("recvfrom error");
33.
34. printf("received from %s at PORT %d\n",
35. inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
36. ntohs(clie_addr.sin_port));
37.
38. for (i = 0; i < n; i++)
39. buf[i] = toupper(buf[i]);
40.
41. n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&clie_addr, sizeof(clie_addr));
42. if (n == -1)
43. perror("sendto error");
44. }
45.
46. close(sockfd);
47.
48. return 0;
49. }
-
客户端
-
1. #include <stdio.h> 2. #include <string.h> 3. #include <unistd.h> 4. #include <arpa/inet.h> 5. #include <ctype.h> 6. 7. #define SERV_PORT 8000 8. 9. int main(int argc, char *argv[]) 10. { 11. struct sockaddr_in servaddr; 12. int sockfd, n; 13. char buf[BUFSIZ]; 14. 15. sockfd = socket(AF_INET, SOCK_DGRAM, 0); 16. 17. bzero(&servaddr, sizeof(servaddr)); 18. servaddr.sin_family = AF_INET; 19. inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); 20. servaddr.sin_port = htons(SERV_PORT); 21. 22. bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 23. 24. while (fgets(buf, BUFSIZ, stdin) != NULL) { 25. n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servad dr)); 26. if (n == -1) 27. perror("sendto error"); 28. 29. n = recvfrom(sockfd, buf, BUFSIZ, 0, NULL, 0); //NULL:不关心对端信息 30. if (n == -1) 31. perror("recvfrom error"); 32. 33. write(STDOUT_FILENO, buf, n); 34. } 35. 36. close(sockfd); 37. 38. return 0; 39. }
本地套接字和网络套接字
-
对比网络编程 TCP C/S 模型, 注意以下几点:
- 1,
int socket (int domain , int type , int protocol );
参 数domain
:AF_INET --> AF_UNIX/AF_LOCAL
2,type:
SOCK_STREAM/SOCK_DGRAM
都可以。
3,地址结构:
sockaddr_in
-->sockaddr_un
struct sockaddr_in srv_addr
; -->struct sockaddr_un srv_adrr;
srv_addr.sin_family = AF_INET;
-->srv_addr.sun_family = AF_UNIX;
srv_addr.sin_port = htons(8888);
—->strcpy(srv_addr.sun_path, "srv.socket")
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
–—->len = offsetof(struct sockaddr_un, sun_path) + strlen("srv.socket");
bind(fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
-->bind(fd, (struct sockaddr *)&srv_addr, len);
4,
bind()
函数调用成功,会创建一个 socket。因此为保证 bind 成功,通常我们在 bind 之前,可以使用
unlink("srv.socket");
5, 客户端不能依赖 “隐式绑定”。并且应该在通信建立过程中,创建且初始化 2 个地址结构:
1) client_addr --> bind()
2) server_addr --> connect();
- 1,
本地套接字间通信代码
- 服务器端
1. #include <stdio.h>
2. #include <unistd.h>
3. #include <sys/socket.h>
4. #include <strings.h>
5. #include <string.h>
6. #include <ctype.h>
7. #include <arpa/inet.h>
8. #include <sys/un.h>
9. #include <stddef.h>
10.
11. #include "wrap.h"
12.
13. #define SERV_ADDR "serv.socket"
14.
15. int main(void)
16. {
17. int lfd, cfd, len, size, i;
18. struct sockaddr_un servaddr, cliaddr;
19. char buf[4096];
20.
21. lfd = Socket(AF_UNIX, SOCK_STREAM, 0);
22.
23. bzero(&servaddr, sizeof(servaddr));
24. servaddr.sun_family = AF_UNIX;
25. strcpy(servaddr.sun_path, SERV_ADDR);
26.
27. len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path); /* servaddr t
otal len */
28.
29. unlink(SERV_ADDR); /* 确保 bind 之前 serv.sock 文件不存在,bind 会创
建该文件 */
30. Bind(lfd, (struct sockaddr *)&servaddr, len); /* 参 3 不能是 sizeof(servaddr) */
31.
32. Listen(lfd, 20);
33.
34. printf("Accept ...\n");
35. while (1) {
36. len = sizeof(cliaddr); //AF_UNIX 大小+108B
37.
38. cfd = Accept(lfd, (struct sockaddr *)&cliaddr, (socklen_t *)&len);
39.
40. len -= offsetof(struct sockaddr_un, sun_path); /* 得到文件名的长度 */
41. cliaddr.sun_path[len] = '\0'; /* 确保打印时,没有乱码出现 */
42.
43. printf("client bind filename %s\n", cliaddr.sun_path);
44.
45. while ((size = read(cfd, buf, sizeof(buf))) > 0) {
46. for (i = 0; i < size; i++)
47. buf[i] = toupper(buf[i]);
48. write(cfd, buf, size);
49. }
50. close(cfd);
51. }
52. close(lfd);
53.
54. return 0;
55. }
- 客户端
14. #define SERV_ADDR "serv.socket"
15. #define CLIE_ADDR "clie.socket"
16.
17. int main(void)
18. {
19. int cfd, len;
20. struct sockaddr_un servaddr, cliaddr;
21. char buf[4096];
22.
23. cfd = Socket(AF_UNIX, SOCK_STREAM, 0);
24.
25. bzero(&cliaddr, sizeof(cliaddr));
26. cliaddr.sun_family = AF_UNIX;
27. strcpy(cliaddr.sun_path,CLIE_ADDR);
28.
29. len = offsetof(struct sockaddr_un, sun_path) + strlen(cliaddr.sun_path); /* 计算客户端地
址结构有效长度 */
30.
31. unlink(CLIE_ADDR);
32. Bind(cfd, (struct sockaddr *)&cliaddr, len); /* 客户端也需要
bind, 不能依赖自动绑定*/
33.
34.
35. bzero(&servaddr, sizeof(servaddr)); /* 构造 server
地址 */
36. servaddr.sun_family = AF_UNIX;
37. strcpy(servaddr.sun_path, SERV_ADDR);
38.
39. len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path); /* 计算服务器端
地址结构有效长度 */
40.
41. Connect(cfd, (struct sockaddr *)&servaddr, len);
42.
43. while (fgets(buf, sizeof(buf), stdin) != NULL) {
44. write(cfd, buf, strlen(buf));
45. len = read(cfd, buf, sizeof(buf));
46. write(STDOUT_FILENO, buf, len);
47. }
48.
49. close(cfd);
50.
51. return 0;
52. }
libevent库
- 开源跨平台 ,专注与网络通信
libevent框架
-
1,创建
event_base
-
2,创建事件 event
-
3, 将事件添加到 base上
-
4,循环监听事件 满足
-
5, 释放 event_base
-
创建
evevnt_base
struct event_base* event_base_new(void);
struct event_base* base = event_base_new()
-
创建事件event
-
常规事件 event event_new() bufferevent -----> bufferevent_socket_new();
-
-
将事件添加到 base上
int event_add(struct event*ev, const struct timeval*tv)
-
循环监听事件满足
-
int event_base_dispatch(struct event_base*base); event_base_dispatch(base);
-
-
释放 event_base
event_base_free(base);
创建事件对象
-
创建事件 event
-
struct event *ev
-
struct event* event_new(struct event_base *base , evutil_socket_t fd , short what, event-callback_fn cd,void *arg)
; -
参数: base :event_base_new() 返回值。 fd :绑定到 event 上的 文件描述符 what 对应的事件(r,w,e) EV_READ 一次读事件 EV_WARITE 一次写事件 EV_PERSIST 持续触发。 结合 event_bse cb 一旦事件满足监听条件,回调函数 typedef void (*event_callback_fn)(evutil_socket_t fd, short, void *) arg: 回调函数的参数 返回值 成功创建 event
添加事件到event_base
event_add()
int event_add(struct event *ev, const struct timeval *tv)
- ev : event_new() 返回值
- tv NULL
销毁事件
int event_free(struct event*ev)
- ev event_new() 返回值
使用fifo的读写
- 读端
1. #include <stdio.h>
2. #include <unistd.h>
3. #include <stdlib.h>
4. #include <sys/types.h>
5. #include <sys/stat.h>
6. #include <string.h>
7. #include <fcntl.h>
8. #include <event2/event.h>
9.
10. // 对操作处理函数
11. void read_cb(evutil_socket_t fd, short what, void *arg)
12. {
13. // 读管道
14. char buf[1024] = {0};
15.
16. int len = read(fd, buf, sizeof(buf));
17.
18. printf("read event: %s \n", what & EV_READ ? "Yes" : "No");
19. printf("data len = %d, buf = %s\n", len, buf);
20.
21. sleep(1);
22. }
23.
24.
25. // 读管道
26. int main(int argc, const char* argv[])
27. {
28. unlink("myfifo");
29.
30. //创建有名管道
31. mkfifo("myfifo", 0664);
32.
33. // open file
34. //int fd = open("myfifo", O_RDONLY | O_NONBLOCK);
35. int fd = open("myfifo", O_RDONLY);
36. if(fd == -1)
37. {
38. perror("open error");
39. exit(1);
40. }
41.
42. // 创建个 event_base
43. struct event_base* base = NULL;
44. base = event_base_new();
45.
46. // 创建事件
47. struct event* ev = NULL;
48. ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL);
49.
50. // 添加事件
51. event_add(ev, NULL);
52.
53. // 事件循环
54. event_base_dispatch(base); // while(1) { epoll();}
55.
56. // 释放资源
57. event_free(ev);
58. event_base_free(base);
59. close(fd);
60.
61. return 0;
62. }
-
写管道
-
1. #include <stdio.h> 2. #include <unistd.h> 3. #include <stdlib.h> 4. #include <sys/types.h> 5. #include <sys/stat.h> 6. #include <string.h> 7. #include <fcntl.h> 8. #include <event2/event.h> 9. 10. // 对操作处理函数 11. void write_cb(evutil_socket_t fd, short what, void *arg) 12. { 13. // write 管道 14. char buf[1024] = {0}; 15. 16. static int num = 0; 17. sprintf(buf, "hello,world-%d\n", num++); 18. write(fd, buf, strlen(buf)+1); 19. 20. sleep(1); 21. } 22. 23. 24. // 写管道 25. int main(int argc, const char* argv[]) 26. { 27. // open file 28. //int fd = open("myfifo", O_WRONLY | O_NONBLOCK); 29. int fd = open("myfifo", O_WRONLY); 30. if(fd == -1) 31. { 32. perror("open error"); 33. exit(1); 34. } 35. 36. // 写管道 37. struct event_base* base = NULL; 38. base = event_base_new(); 39. 40. // 创建事件 41. struct event* ev = NULL; 42. // 检测的写缓冲区是否有空间写 43. //ev = event_new(base, fd, EV_WRITE , write_cb, NULL); 44. ev = event_new(base, fd, EV_WRITE | EV_PERSIST, write_cb, NULL); 45. 46. // 添加事件 47. event_add(ev, NULL); 48. 49. // 事件循环 50. event_base_dispatch(base); 51. 52. // 释放资源 53. event_free(ev); 54. event_base_free(base); 55. close(fd); 56. 57. return 0; 58. }
未决和非未决
- 非未决 :没有资格被处理
- 未决 : 有资格被处理,但尚未被处理
- event_new --> event —> 非未决 --> event_add --> 未决 --> dispatch() && 监听事件被触发 --> 激活态 --> 执 行 回 调 函 数 --> 处 理 态 --> 非 未 决 event_add && EV_PERSIST --> 未 决 --> event_del --> 非未决
bufferevent特性
- 带缓冲区的事件
bufferevent
#include<event2/bufferevent.h>
read/write
两个缓冲,借助队列
创建,销毁对象
- 创建,销毁
bufferevent
struct bufferevent *ev
;struct bufferevent *bufferevent_socket_new(struct event_base & base, evutil_socket_t fd,enum bufferevent_options options);
- base : event_base;
- fd 封装到bufferevent内的fd
- options :BEV_OPT_CLOSE_ON_FREE
- 返回成功创建的bufferevent的对象
销毁
viod bufferevent_socket_free(strcut bufferevent*ev)
;
给bufferevent事件对象设置回调
-
bufferevent_socket_bew(fd) bufferevent_setcb(callback) void bufferevent_setcb(struct bufferevent*bufev,bufferevent_data_cb readcb,bufferevent_data_cb writecb,bufferevent_event_cb eventcb, void*cbarg) 参数: bufev: bufferevent_socket_new()返回值 readcb: 设置bufferevent读缓冲,对应回调 read_cb{ bufferevent_read() 读数据} writecb: 设置bufferevent写缓冲,对应回调 write_cb{ } ------给调用者,发送写成功通知,可以NULL eventcb 设置事件回调 也可传NULL typedef void (*bufferevent_event_cb)(struct bufferevent *bev , short events , void *ctx); void event_cb(struct bufferevent *bev, short events, void *ctx){} events: BEV_EVENT_CONNECTED cbarg:上述回调函数使用的参数
-
read 回调函数类型 typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void*ct); void read_cb(struct bufferevent*bev,void * cbarg){ ... bufferevent_read();} bufferevent_read()函数原型 size_t bufferevent_read(struct bufferevent *bev, void *buf, size_t bufsize); write 回调函数类型: int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
缓冲区开启和关闭
- 启动,关闭 bufferevent的缓冲区:
void bufferevent_enable (struct bufferevent *bufev, short events);
启动- events: EV_READ, EV_WRITE, EV_READ|EV_WRITE
- 默认 write 缓冲区时 enable 的,read缓冲是 disable 的
bufferevent_enable(evev,EV_READ)
—–开启读缓冲
客户端和服务器连接和监听
-
连接客户端:
-
socket(); connect(); int bufferevent_socket_connect(struct bufferevent*bev ,struct sockaddr *address,int addrlen); 参数: bev: bufferevent事件对象(包含fd) address----len:类似与connect()
-
-
创建监听服务器
-
---socket(),bind(),listen(),accept(); struct evconnlistener*listner; struct evconnlistener*evconnlistener_new_bind(struct event_base *base , evconnlistenner_cb cb , void*ptr, ussigned flags, int backlog, const struct sockaddr*sa, int socklen) 参数: base event_base cb 回调函数 , 一旦被回调,说明在其内部应该与客户端完成,数据读写操作,进行通信。 ptr 回调函数参数 flags LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE backlog: listen() sa 服务器自己的地址结构体 socklen 服务器自己的地址结构的大小 返回值: 成功创建的监听器 释放监听服务器: void evconnlistener_free(struct evconnlistener*lev);
-
libevent实现TCP服务器
- 流程
- 服务器端libevent 创建TCP连接
- 1,创建 event_base;
- 2,创建服务器连接监听器
evconnlister_new_bind()
- 3, 在
evconnlister_new_bind
的回调函数中,处理接受连接后的操作 - 4,回调函数被调用,说明有一个新客户端连接上来。会得到一个新fd,用于跟客户端通信
- 5, 创建 bufferevent事件对象。 bufferevent_socket_new();
- 6, 使用 bufferevent_setcb() 给bufferevent的read , write , event 设置回调函数
- 7,设置bufferevent的读写缓冲区 enable/ disable
- 8, 启动循环监听
- 9, 释放资源
实现
1. #include <stdio.h>
2. #include <unistd.h>
3. #include <stdlib.h>
4. #include <sys/types.h>
5. #include <sys/stat.h>
6. #include <string.h>
7. #include <event2/event.h>
8. #include <event2/listener.h>
9. #include <event2/bufferevent.h>
10.
11. // 读缓冲区回调
12. void read_cb(struct bufferevent *bev, void *arg)
13. {
14. char buf[1024] = {0};
15. bufferevent_read(bev, buf, sizeof(buf));
16. printf("client say: %s\n", buf);
17.
18. char *p = "我是服务器, 已经成功收到你发送的数据!";
19. // 发数据给客户端
20. bufferevent_write(bev, p, strlen(p)+1);
21. sleep(1);
22. }
23.
24. // 写缓冲区回调
25. void write_cb(struct bufferevent *bev, void *arg)
26. {
27. printf("I'm 服务器, 成功写数据给客户端,写缓冲区回调函数被回调...\n");
28. }
29.
30. // 事件
31. void event_cb(struct bufferevent *bev, short events, void *arg)
32. {
33. if (events & BEV_EVENT_EOF)
34. {
35. printf("connection closed\n");
36. }
37. else if(events & BEV_EVENT_ERROR)
38. {
39. printf("some other error\n");
40. }
41.
42. bufferevent_free(bev);
43. printf("buffevent 资源已经被释放...\n");
44. }
45.
46.
47.
48. void cb_listener(
49. struct evconnlistener *listener,
50. evutil_socket_t fd,
51. struct sockaddr *addr,
52. int len, void *ptr)
53. {
54. printf("connect new client\n");
55.
56. struct event_base* base = (struct event_base*)ptr;
57. // 通信操作
58. // 添加新事件
59. struct bufferevent *bev;
60. bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
61.
62. // 给 bufferevent 缓冲区设置回调
63. bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
64. bufferevent_enable(bev, EV_READ);
65. }
66.
67.
68. int main(int argc, const char* argv[])
69. {
70.
71. // init server
72. struct sockaddr_in serv;
73.
74. memset(&serv, 0, sizeof(serv));
75. serv.sin_family = AF_INET;
76. serv.sin_port = htons(9876);
77. serv.sin_addr.s_addr = htonl(INADDR_ANY);
78.
79. struct event_base* base;
80. base = event_base_new();
81. // 创建套接字
82. // 绑定
83. // 接收连接请求
84. struct evconnlistener* listener;
85. listener = evconnlistener_new_bind(base , cb_listener, base, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUS EABLE, 36, (struct sockaddr*)&serv, sizeof(serv));
89. event_base_dispatch(base);
90.
91. evconnlistener_free(listener);
92. event_base_free(base);
93.
94. return 0;
95. }
libevent实现客户端
流程
- 1,创建
event_base
- 2, 使用
bufferevent_socket_new()
创建一个跟服务器通信的buffer event的事件对象 - 3, 使用
bufferevent_socket_connect()
连接服务器 - 4, 使用
bufferevent_setcb()
给bufferevent
对象的read,write,event设置回调 - 5, 设置 bufferevent 对象的 读写缓冲区
- 6, 接受,发送数据
bufferevent_read()
bufferevent_wirte()
- 7, 释放资源
实现
1. #include <stdio.h>
2. #include <unistd.h>
3. #include <stdlib.h>
4. #include <sys/types.h>
5. #include <sys/stat.h>
6. #include <string.h>
7. #include <event2/bufferevent.h>
8. #include <event2/event.h>
9. #include <arpa/inet.h>
10.
11. void read_cb(struct bufferevent *bev, void *arg)
12. {
13. char buf[1024] = {0};
14. bufferevent_read(bev, buf, sizeof(buf));
15.
16. printf("fwq say:%s\n", buf);
17.
18. bufferevent_write(bev, buf, strlen(buf)+1);
19. sleep(1);
20. }
21.
22. void write_cb(struct bufferevent *bev, void *arg)
23. {
24. printf("----------我是客户端的写回调函数,没卵用\n");
25. }
26.
27. void event_cb(struct bufferevent *bev, short events, void *arg)
28. {
29. if (events & BEV_EVENT_EOF)
30. {
31. printf("connection closed\n");
32. }
33. else if(events & BEV_EVENT_ERROR)
34. {
35. printf("some other error\n");
36. }
37. else if(events & BEV_EVENT_CONNECTED)
38. {
39. printf("已经连接服务器...\\(^o^)/...\n");
40. return;
41. }
42.
43. // 释放资源
44. bufferevent_free(bev);
45. }
46.
47. // 客户端与用户交互,从终端读取数据写给服务器
48. void read_terminal(evutil_socket_t fd, short what, void *arg)
49. {
50. // 读数据
51. char buf[1024] = {0};
52. int len = read(fd, buf, sizeof(buf));
53.
54. struct bufferevent* bev = (struct bufferevent*)arg;
55. // 发送数据
56. bufferevent_write(bev, buf, len+1);
57. }
58.
59. int main(int argc, const char* argv[])
60. {
61. struct event_base* base = NULL;
62. base = event_base_new();
63.
64. int fd = socket(AF_INET, SOCK_STREAM, 0);
65.
66. // 通信的 fd 放到 bufferevent 中
67. struct bufferevent* bev = NULL;
68. bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
69.
70. // init server info
71. struct sockaddr_in serv;
72. memset(&serv, 0, sizeof(serv));
73. serv.sin_family = AF_INET;
74. serv.sin_port = htons(9876);
75. inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
76.
77. // 连接服务器
78. bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));
79.
80. // 设置回调
81. bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
82.
83. // 设置读回调生效
84. bufferevent_enable(bev, EV_READ);
85.
86. // 创建事件
87. struct event* ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, read_terminal, bev);
89. // 添加事件
90. event_add(ev, NULL);
91.
92. event_base_dispatch(base);
93.
94. event_free(ev);
95.
96. event_base_free(base);
97.
98. return 0;
99. }