Linux网络编程笔记

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 – 发送SYNSEND_SYN– 接收 ACK、SYNSEND_SYN – 发送 ACKESTABLISHED(数据通信态)

  • 2,主动关闭连接请求端: ESTABLISHED(数据通信态)–发送FINFIN_WAIT_1 --接收ACKFIN_WAIT_2(半关闭)

    ​ – 接收对端发送FINFIN_WAIT_2(半关闭)–回发ACKTIME_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函数

  • epollLinux下多路复用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 ); 参 数 domainAF_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. #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. }
  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值