Linux网络编程——黑马程序员笔记

Posted 行稳方能走远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux网络编程——黑马程序员笔记相关的知识,希望对你有一定的参考价值。

01P-复习-Linux网络编程

02P-信号量生产者复习

03P-协议
协议:
一组规则。

04P-7层模型和4层模型及代表协议
分层模型结构:

OSI七层模型:  物、数、网、传、会、表、应

TCP/IP 4层模型:网(链路层/网络接口层)、网、传、应

	应用层:http、ftp、nfs、ssh、telnet。。。

	传输层:TCP、UDP

	网络层:IP、ICMP、IGMP

	链路层:以太网帧协议、ARP

05P-网络传输数据封装流程

网络传输流程:

数据没有封装之前,是不能在网络中传递。

数据-》应用层-》传输层-》网络层-》链路层  --- 网络环境

06P-以太网帧和ARP请求

以太网帧协议:

ARP协议:根据 Ip 地址获取 mac 地址。

以太网帧协议:根据mac地址,完成数据包传输。

07P-IP协议
IP协议:

版本: IPv4、IPv6  -- 4位

TTL: time to live 。 设置数据包在路由节点中的跳转上限。每经过一个路由节点,该值-1, 减为0的路由,有义务将该数据包丢弃

源IP: 32位。--- 4字节		192.168.1.108 --- 点分十进制 IP地址(string)  --- 二进制 

目的IP:32位。--- 4字节

08P-端口号和UDP协议
UDP:
16位:源端口号。 2^16 = 65536

16位:目的端口号。

IP地址:可以在网络环境中,唯一标识一台主机。

端口号:可以网络的一台主机上,唯一标识一个进程。

ip地址+端口号:可以在网络环境中,唯一标识一个进程。

09P-TCP协议
TCP协议:

16位:源端口号。	2^16 = 65536  

16位:目的端口号。

32序号;

32确认序号。	

6个标志位。

16位窗口大小。	2^16 = 65536

10P-BS和CS模型对比
c/s模型:

client-server

b/s模型:

browser-server

		C/S					B/S

优点:	缓存大量数据、协议选择灵活			安全性、跨平台、开发工作量较小

	速度快

缺点:	安全性、跨平台、开发工作量较大			不能缓存大量数据、严格遵守 http

11P-套接字
网络套接字: socket

一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现。)

在通信过程中, 套接字一定是成对出现的。

12P-回顾

13P-网络字节序
网络字节序:

小端法:(pc本地存储)	高位存高地址。地位存低地址。	int a = 0x12345678

大端法:(网络存储)	高位存低地址。地位存高地址。

htonl --> 本地--》网络 (IP)			192.168.1.11 --> string --> atoi --> int --> htonl --> 网络字节序

htons --> 本地--》网络 (port)

ntohl --> 网络--》 本地(IP)

ntohs --> 网络--》 本地(Port)

14P-IP地址转换函数
IP地址转换函数:

int inet_pton(int af, const char *src, void *dst);		本地字节序(string IP) ---> 网络字节序

	af:AF_INET、AF_INET6

	src:传入,IP地址(点分十进制)

	dst:传出,转换后的 网络字节序的 IP地址。 

	返回值:

		成功: 1

		异常: 0, 说明src指向的不是一个有效的ip地址。

		失败:-1

   const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);	网络字节序 ---> 本地字节序(string IP)

	af:AF_INET、AF_INET6

	src: 网络字节序IP地址

	dst:本地字节序(string IP)

	size: dst 的大小。

	返回值: 成功:dst。 	

		失败:NULL

15P-sockaddr地址结构

sockaddr地址结构: IP + port --> 在网络环境中唯一标识一个进程。

struct sockaddr_in addr;

addr.sin_family = AF_INET/AF_INET6				man 7 ip

addr.sin_port = htons(9527);
		
	int dst;

	inet_pton(AF_INET, "192.157.22.45", (void *)&dst);

addr.sin_addr.s_addr = dst;

【*】addr.sin_addr.s_addr = htonl(INADDR_ANY);		取出系统中有效的任意IP地址。二进制类型。

bind(fd, (struct sockaddr *)&addr, size);

16P-socket模型创建流程分析

17P-socket和bind
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 

	返回值:

		成功: 新套接字所对应文件描述符

		失败: -1 errno

#include <arpa/inet.h>

 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_port = htons(8888);

		addr.sin_addr.s_addr = htonl(INADDR_ANY);

	addr: 传入参数(struct sockaddr *)&addr

	addrlen: sizeof(addr) 地址结构的大小。

	返回值:

		成功:0

		失败:-1 errno

18P-listen和accept
int listen(int sockfd, int backlog); 设置同时与服务器建立连接的上限数。(同时进行3次握手的客户端数量)

	sockfd: socket 函数返回值

	backlog:上限数值。最大值 128.


	返回值:

		成功:0

		失败:-1 errno	

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符。

	sockfd: socket 函数返回值

	addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)

		socklen_t clit_addr_len = sizeof(addr);

	addrlen:传入传出。 &clit_addr_len

		 入:addr的大小。 出:客户端addr实际大小。

	返回值:

		成功:能与客户端进行数据通信的 socket 对应的文件描述。

		失败: -1 , errno

19P-connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 使用现有的 socket 与服务器建立连接

	sockfd: socket 函数返回值

		struct sockaddr_in srv_addr;		// 服务器地址结构

		srv_addr.sin_family = AF_INET;

		srv_addr.sin_port = 9527 	跟服务器bind时设定的 port 完全一致。

		inet_pton(AF_INET, "服务器的IP地址",&srv_adrr.sin_addr.s_addr);

	addr:传入参数。服务器的地址结构

		
	addrlen:服务器的地址结构的大小

	返回值:

		成功:0

		失败:-1 errno

	如果不使用bind绑定客户端地址结构, 采用"隐式绑定".

20P-CS模型的TCP通信分析
TCP通信流程分析:

server:
	1. socket()	创建socket

	2. bind()	绑定服务器地址结构

	3. listen()	设置监听上限

	4. accept()	阻塞监听客户端连接

	5. read(fd)	读socket获取客户端数据

	6. 小--大写	toupper()

	7. write(fd)

	8. close();

client:

	1. socket()	创建socket

	2. connect();	与服务器建立连接

	3. write()	写数据到 socket

	4. read()	读转换后的数据。

	5. 显示读取结果

	6. close()

21P-server的实现
代码如下:

  1. #include <stdio.h>
  2. #include <ctype.h>
  3. #include <sys/socket.h>
  4. #include <arpa/inet.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <unistd.h>
  8. #include <errno.h>
  9. #include <pthread.h>
  10. #define SERV_PORT 9527
  11. void sys_err(const char *str)
  12. perror(str);  
    
  13. exit(1);  
    
  14. int main(int argc, char *argv[])
  15. int lfd = 0, cfd = 0;  
    
  16. int ret, i;  
    
  17. char buf[BUFSIZ], client_IP[1024];  
    
  18. struct sockaddr_in serv_addr, clit_addr;  // 定义服务器地址结构 和 客户端地址结构  
    
  19. socklen_t clit_addr_len;                  // 客户端地址结构大小  
    
  20. serv_addr.sin_family = AF_INET;             // IPv4  
    
  21. serv_addr.sin_port = htons(SERV_PORT);      // 转为网络字节序的 端口号  
    
  22. serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 获取本机任意有效IP  
    
  23. lfd = socket(AF_INET, SOCK_STREAM, 0);      //创建一个 socket  
    
  24. if (lfd == -1)   
    
  25.     sys_err("socket error");  
    
  26.   
    
  27. bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));//给服务器socket绑定地址结构(IP+port)  
    
  28. listen(lfd, 128);                   //  设置监听上限  
    
  29. clit_addr_len = sizeof(clit_addr);  //  获取客户端地址结构大小  
    
  30. cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);   // 阻塞等待客户端连接请求  
    
  31. if (cfd == -1)  
    
  32.     sys_err("accept error");  
    
  33. printf("client ip:%s port:%d\\n",   
    
  34.         inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),   
    
  35.         ntohs(clit_addr.sin_port));         // 根据accept传出参数,获取客户端 ip 和 port  
    
  36. while (1)   
    
  37.     ret = read(cfd, buf, sizeof(buf));      // 读客户端数据  
    
  38.     write(STDOUT_FILENO, buf, ret);         // 写到屏幕查看  
    
  39.     for (i = 0; i < ret; i++)                // 小写 -- 大写  
    
  40.         buf[i] = toupper(buf[i]);  
    
  41.     write(cfd, buf, ret);                   // 将大写,写回给客户端。  
    
  42.   
    
  43. close(lfd);  
    
  44. close(cfd);  
    
  45. return 0;  
    

编译测试,结果如下:

22P-获取客户端地址结构
cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);
accept函数中的clit_addr传出的就是客户端地址结构,IP+port

于是,在代码中增加此段代码,可获取客户端信息:
printf(“client ip:%s port:%d\\n”,
inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),
ntohs(clit_addr.sin_port));

上一节代码中已经有这段代码,这里就不再跑一遍了。

23P-client的实现

  1. #include <stdio.h>
  2. #include <sys/socket.h>
  3. #include <arpa/inet.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. #include <errno.h>
  8. #include <pthread.h>
  9. #define SERV_PORT 9527
  10. void sys_err(const char *str)
  11. perror(str);  
    
  12. exit(1);  
    
  13. int main(int argc, char *argv[])
  14. int cfd;  
    
  15. int conter = 10;  
    
  16. char buf[BUFSIZ];  
    
  17. struct sockaddr_in serv_addr;          //服务器地址结构  
    
  18. serv_addr.sin_family = AF_INET;  
    
  19. serv_addr.sin_port = htons(SERV_PORT);  
    
  20. //inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);  
    
  21. inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);  
    
  22. cfd = socket(AF_INET, SOCK_STREAM, 0);  
    
  23. if (cfd == -1)  
    
  24.     sys_err("socket error");  
    
  25. int ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));  
    
  26. if (ret != 0)  
    
  27.     sys_err("connect err");  
    
  28. while (--conter)   
    
  29.     write(cfd, "hello\\n", 6);  
    
  30.     ret = read(cfd, buf, sizeof(buf));  
    
  31.     write(STDOUT_FILENO, buf, ret);  
    
  32.     sleep(1);  
    
  33.   
    
  34. close(cfd);  
    
  35. return 0;  
    

编译运行,结果如下:

这里遇到过一个问题,如果之前运行server,用Ctrl+z终止进程,ps aux列表里会有服务器进程残留,这个会影响当前服务器。解决方法是kill掉这些服务器进程。不然端口被占用,当前运行的服务器进程接收不到东西,没有回显。

24P-总结

协议:
一组规则。

分层模型结构:

OSI七层模型:  物、数、网、传、会、表、应

TCP/IP 4层模型:网(链路层/网络接口层)、网、传、应

	应用层:http、ftp、nfs、ssh、telnet。。。

	传输层:TCP、UDP

	网络层:IP、ICMP、IGMP

	链路层:以太网帧协议、ARP

c/s模型:

client-server

b/s模型:

browser-server

		C/S					B/S

优点:	缓存大量数据、协议选择灵活			安全性、跨平台、开发工作量较小

	速度快

缺点:	安全性、跨平台、开发工作量较大			不能缓存大量数据、严格遵守 http

网络传输流程:

数据没有封装之前,是不能在网络中传递。

数据-》应用层-》传输层-》网络层-》链路层  --- 网络环境

以太网帧协议:

ARP协议:根据 Ip 地址获取 mac 地址。

以太网帧协议:根据mac地址,完成数据包传输。

IP协议:

版本: IPv4、IPv6  -- 4位

TTL: time to live 。 设置数据包在路由节点中的跳转上限。每经过一个路由节点,该值-1, 减为0的路由,有义务将该数据包丢弃

源IP: 32位。--- 4字节		192.168.1.108 --- 点分十进制 IP地址(string)  --- 二进制 

目的IP:32位。--- 4字节

IP地址:可以在网络环境中,唯一标识一台主机。

端口号:可以网络的一台主机上,唯一标识一个进程。

ip地址+端口号:可以在网络环境中,唯一标识一个进程。

UDP:
16位:源端口号。 2^16 = 65536

16位:目的端口号。

TCP协议:

16位:源端口号。	2^16 = 65536  

16位:目的端口号。

32序号;

32确认序号。	

6个标志位。

16位窗口大小。	2^16 = 65536 

网络套接字: socket

一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现。)

在通信过程中, 套接字一定是成对出现的。

网络字节序:

小端法:(pc本地存储)	高位存高地址。地位存低地址。	int a = 0x12345678

大端法:(网络存储)	高位存低地址。地位存高地址。

htonl --> 本地--》网络 (IP)			192.168.1.11 --> string --> atoi --> int --> htonl --> 网络字节序

htons --> 本地--》网络 (port)

ntohl --> 网络--》 本地(IP)

ntohs --> 网络--》 本地(Port)

IP地址转换函数:

int inet_pton(int af, const char *src, void *dst);		本地字节序(string IP) ---> 网络字节序

	af:AF_INET、AF_INET6

	src:传入,IP地址(点分十进制)

	dst:传出,转换后的 网络字节序的 IP地址。 

	返回值:

		成功: 1

		异常: 0, 说明src指向的不是一个有效的ip地址。

		失败:-1

   const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);	网络字节序 ---> 本地字节序(string IP)

	af:AF_INET、AF_INET6

	src: 网络字节序IP地址

	dst:本地字节序(string IP)

	size: dst 的大小。

	返回值: 成功:dst。 	

		失败:NULL

sockaddr地址结构: IP + port --> 在网络环境中唯一标识一个进程。

struct sockaddr_in addr;

addr.sin_family = AF_INET/AF_INET6				man 7 ip

addr.sin_port = htons(9527);
		
	int dst;

	inet_pton(AF_INET, "192.157.22.45", (void *)&dst);

addr.sin_addr.s_addr = dst;

【*】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 

	返回值:

		成功: 新套接字所对应文件描述符

		失败: -1 errno

 #include <arpa/inet.h>

 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_port = htons(8888);

		addr.sin_addr.s_addr = htonl(INADDR_ANY);

	addr: 传入参数(struct sockaddr *)&addr

	addrlen: sizeof(addr) 地址结构的大小。

	返回值:

		成功:0

		失败:-1 errno

int listen(int sockfd, int backlog);		设置同时与服务器建立连接的上限数。(同时进行3次握手的客户端数量)

	sockfd: socket 函数返回值

	backlog:上限数值。最大值 128.


	返回值:

		成功:0

		失败:-1 errno	

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符。

	sockfd: socket 函数返回值

	addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)

		socklen_t clit_addr_len = sizeof(addr);

	addrlen:传入传出。 &clit_addr_len

		 入:addr的大小。 出:客户端addr实际大小。

	返回值:

		成功:能与客户端进行数据通信的 socket 对应的文件描述。

		失败: -1 , errno


   int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);	  使用现有的 socket 与服务器建立连接

	sockfd: socket 函数返回值

		struct sockaddr_in srv_addr;		// 服务器地址结构

		srv_addr.sin_family = AF_INET;

		srv_addr.sin_port = 9527 	跟服务器bind时设定的 port 完全一致。

		inet_pton(AF_INET, "服务器的IP地址",&srv_adrr.sin_addr.s_addr);

	addr:传入参数。服务器的地址结构

		
	addrlen:服务器的地址结构的大小

	返回值:

		成功:0

		失败:-1 errno

	如果不使用bind绑定客户端地址结构, 采用"隐式绑定".

TCP通信流程分析:

server:
	1. socket()	创建socket

	2. bind()	绑定服务器地址结构

	3. listen()	设置监听上限

	4. accept()	阻塞监听客户端连接

	5. read(fd)	读socket获取客户端数据

	6. 小--大写	toupper()

	7. write(fd)

	8. close();

client:

	1. socket()	创建socket

	2. connect();	与服务器建立连接

	3. write()	写数据到 socket

	4. read()	读转换后的数据。

	5. 显示读取结果

	6. close()

25P-复习

26P-三次握手建立连接

27P-数据通信

并不是一次发送,一次应答。也可以批量应答

28P-四次握手关闭连接

29P-半关闭补充说明
这里其实就是想说明,完成两次挥手后,不是说两端的连接断开了,主动端关闭了写缓冲区,不能再向对端发送数据,被动端关闭了读缓冲区,不能再从对端读取数据。然而主动端还是能够读取对端发来的数据。

30P-滑动窗口和TCP数据包格式

滑动窗口:

发送给连接对端,本端的缓冲区大小(实时),保证数据不会丢失。

31P-通信时序与代码对应关系

32P-TCP通信时序总结
三次握手:

主动发起连接请求端,发送 SYN 标志位,请求建立连接。 携带序号号、数据字节数(0)、滑动窗口大小。

被动接受连接请求端,发送 ACK 标志位,同时携带 SYN 请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小。

主动发起连接请求端,发送 ACK 标志位,应答服务器连接请求。携带确认序号。

四次挥手:

主动关闭连接请求端, 发送 FIN 标志位。 

被动关闭连接请求端, 应答 ACK 标志位。 		 ----- 半关闭完成。


被动关闭连接请求端, 发送 FIN 标志位。

主动关闭连接请求端, 应答 ACK 标志位。		 ----- 连接全部关闭

滑动窗口:

发送给连接对端,本端的缓冲区大小(实时),保证数据不会丢失。

33P-错误处理函数的封装思路
wrap.h文件如下,就是包裹函数的声明

  1. #ifndef _WRAP_H
  2. #define _WRAP_H
  3. void perr_exit(const char *s);
  4. int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
  5. int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
  6. int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
  7. int Listen(int fd, int backlog);
  8. int Socket(int family, int type, int protocol);
  9. ssize_t Read(int fd, void *ptr, size_t nbytes);
  10. ssize_t Write(int fd, const void *ptr, size_t nbytes);
  11. int Close(int fd);
  12. ssize_t Readn(int fd, void *vptr, size_t n);
  13. ssize_t Writen(int fd, const void *vptr, size_t n);
  14. ssize_t my_read(int fd, char *ptr);
  15. ssize_t Readline(int fd, void *vptr, size_t maxlen);
  16. #endif

wrap.c随便取一部分,如下,就是包裹函数的代码:

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. #include <errno.h>
  5. #include <sys/socket.h>
  6. void perr_exit(const char *s)
  7.  perror(s);  
    
  8. exit(-1);  
    
  9. int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
  10. int n;  
    
  11. again:
  12. if ((n = accept(fd, sa, salenptr)) < 0)   
    
  13.     if ((errno == ECONNABORTED) || (errno == EINTR))  
    
  14.         goto again;  
    
  15.     else  
    
  16.         perr_exit("accept error");  
    
  17.   
    
  18. return n;  
    
  19. int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
  20. int n;  
    
  21. if ((n = bind(fd, sa, salen)) < 0)  
    
  22.     perr_exit("bind error");  
    
  23. return n;  
    

这里原函数和包裹函数的函数名差异只有首字母大写,这是因为man page对字母大小写不敏感,同名的包裹函数一样可以跳转至man page

34P-错误处理函数封装
就是重新包裹需要检查返回值的函数,让代码不那么肥胖。

35P-封装思想总结和readn、readline封装思想说明
错误处理函数:

封装目的: 

	在 server.c 编程过程中突出逻辑,将出错处理与逻辑分开,可以直接跳转man手册。


【wrap.c】								【wrap.h】


存放网络通信相关常用 自定义函数						存放 网络通信相关常用 自定义函数原型(声明)。

命名方式:系统调用函数首字符大写, 方便查看man手册
	
	  如:Listen()、Accept();

函数功能:调用系统调用函数,处理出错场景。

在 server.c 和 client.c 中调用 自定义函数

联合编译 server.c 和 wrap.c 生成 server

	 client.c 和 wrap.c 生成 client

readn:
读 N 个字节

readline:

读一行

36P-中午复习
三次握手:

主动发起连接请求端,发送 SYN 标志位,请求建立连接。 携带序号号、数据字节数(0)、滑动窗口大小。

被动接受连接请求端,发送 ACK 标志位,同时携带 SYN 请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小。

主动发起连接请求端,发送 ACK 标志位,应答服务器连接请求。携带确认序号。

四次挥手:

主动关闭连接请求端, 发送 FIN 标志位。 

被动关闭连接请求端, 应答 ACK 标志位。 		 ----- 半关闭完成。


被动关闭连接请求端, 发送 FIN 标志位。

主动关闭连接请求端, 应答 ACK 标志位。		 ----- 连接全部关闭

滑动窗口:

发送给连接对端,本端的缓冲区大小(实时),保证数据不会丢失。

错误处理函数:

封装目的: 

	在 server.c 编程过程中突出逻辑,将出错处理与逻辑分开,可以直接跳转man手册。


【wrap.c】								【wrap.h】


存放网络通信相关常用 自定义函数						存放 网络通信相关常用 自定义函数原型(声明)。

命名方式:系统调用函数首字符大写, 方便查看man手册
	
	  如:Listen()、Accept();

函数功能:调用系统调用函数,处理出错场景。

在 server.c 和 client.c 中调用 自定义函数

联合编译 server.c 和 wrap.c 生成 server

	 client.c 和 wrap.c 生成 client

readn:
读 N 个字节

readline:

读一行

read 函数的返回值:

1. > 0 实际读到的字节数

2. = 0 已经读到结尾(对端已经关闭)【 !重 !点 !】

3. -1 应进一步判断errno的值:

	errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式 读。 没有数据到达。 

	errno = EINTR 慢速系统调用被 中断。

	errno = “其他情况” 异常。

37P-多进程并发服务器思路分析
1. Socket(); 创建 监听套接字 lfd
2. Bind() 绑定地址结构 Strcut scokaddr_in addr;
3. Listen();
4. while (1)

	cfd = Accpet();			接收客户端连接请求。
	pid = fork();
	if (pid == 0)			子进程 read(cfd) --- 小-》大 --- write(cfd)

		close(lfd)		关闭用于建立连接的套接字 lfd

		read()
		小--大
		write()

	 else if (pid > 0) 	

		close(cfd);		关闭用于与客户端通信的套接字 cfd	
		contiue;
	
  

5. 子进程:

	close(lfd)

	read()

	小--大

	write()	

   父进程:

	close(cfd);

	注册信号捕捉函数:	SIGCHLD

	在回调函数中, 完成子进程回收

		while (waitpid());

38P-多线程并发服务器分析
多线程并发服务器: server.c

1. Socket();		创建 监听套接字 lfd

2. Bind()		绑定地址结构 Strcut scokaddr_in addr;

3. Listen();		

4. while (1) 		

	cfd = Accept(lfd, );

	pthread_create(&tid, NULL, tfn, (void *)cfd);

	pthread_detach(tid);  				// pthead_join(tid, void **);  新线程---专用于回收子线程。
  

5. 子线程:

	void *tfn(void *arg) 
	
		// close(lfd)			不能关闭。 主线程要使用lfd

		read(cfd)

		小--大

		write(cfd)

		pthread_exit((void *)10);	
	

39P-多进程并发服务器实现
第一个版本的代码如下:

  1. #include <stdio.h>
  2. #include <ctype.h>
  3. #include <stdlib.h>
  4. #include <sys/wait.h>
  5. #include <string.h>
  6. #include <strings.h>
  7. #include <unistd.h>
  8. #include <errno.h>
  9. #include <signal.h>
  10. #include <sys/socket.h>
  11. #include <arpa/inet.h>
  12. #include <pthread.h>
  13. #include “wrap.h”
  14. #define SRV_PORT 9999
  15. int main(int argc, char *argv[])
  16. int lfd, cfd;  
    
  17. pid_t pid;  
    
  18. struct sockaddr_in srv_addr, clt_addr;  
    
  19. socklen_t clt_addr_len;   
    
  20. char buf[BUFSIZ];  
    
  21. int ret, i;  
    
  22. //memset(&srv_addr, 0, sizeof(srv_addr));                 // 将地址结构清零  
    
  23. bzero(&srv_addr, sizeof(srv_addr));  
    
  24. srv_addr.sin_family = AF_INET;  
    
  25. srv_addr.sin_port = htons(SRV_PORT);  
    
  26. srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    
  27. lfd = Socket(AF_INET, SOCK_STREAM, 0);  
    
  28. Bind(lfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));  
    
  29. Listen(lfd, 128);  
    
  30. clt_addr_len = sizeof(clt_addr);  
    
  31. while (1)   
    
  32.     cfd = Accept(lfd, (struct sockaddr *)&clt_addr, &clt_addr_len);  
    
  33.     pid = fork();  
    
  34.     if (pid < 0)   
    
  35.         perr_exit("fork error");  
    
  36.      else if (pid == 0)   
    
  37.         close(lfd);  
    
  38.         break;          
    
  39.      else   
    
  40.         close(cfd);   
    
  41.         continue;  
    
  42.       
    
  43.   
    
  44. if (pid == 0)   
    
  45.     for (;;)   
    
  46.         ret = Read(cfd, buf, sizeof(buf));  
    
  47.         if (ret == 0)   
    
  48.             close(cfd);  
    
  49.             exit(1);  
    
  50.            
    
  51.         for (i = 0; i < ret; i++)  
    
  52.             buf[i] = toupper(buf[i]);  
    
  53.         write(cfd, buf, ret);  
    
  54.         write(STDOUT_FILENO, buf, ret);  
    
  55.       
    
  56.   
    
  57. return 0;  
    

编译运行,结果如下:

这个代码,有问题。我们Ctrl+C终止一个连接进程,会发现,有僵尸进程。

如上图所示,有个僵尸进程。这是因为父进程在阻塞等待,没来得及去回收这个子进程。

所以需要修改代码,增加子进程回收,用信号捕捉来实现。
修改部分如图所示:

完整代码如下:

  1. #include <stdio.h>
  2. #include <ctype.h>
  3. #include <stdlib.h>
  4. #include <sys/wait.h>
  5. #include <string.h>
  6. #include <strings.h>
  7. #include <unistd.h>
  8. #include <errno.h>
  9. #include <signal.h>
  10. #include <sys/socket.h>
  11. #include <arpa/inet.h>
  12. #include <pthread.h>
  13. #include “wrap.h”
  14. #define SRV_PORT 9999
  15. void catch_child(int signum)
  16. while ((waitpid(0, NULL, WNOHANG)) > 0);  
    
  17. return ;  
    
  18. int main(int argc, char *argv[])
  19. int lfd, cfd;  
    
  20. pid_t pid;  
    
  21. struct sockaddr_in srv_addr, clt_addr;  
    
  22. socklen_t clt_addr_len;   
    
  23. char buf[BUFSIZ];  
    
  24. int ret, i;  
    
  25. //memset(&srv_addr, 0, sizeof(srv_addr));                 // 将地址结构清零  
    
  26. bzero(&srv_addr, sizeof(srv_addr));  
    
  27. srv_addr.sin_family = AF_INET;  
    
  28. srv_addr.sin_port = htons(SRV_PORT);  
    
  29. srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    
  30. lfd = Socket(AF_INET, SOCK_STREAM, 0);  
    
  31. Bind(lfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));  
    
  32. Listen(lfd, 128);  
    
  33. clt_addr_len = sizeof(clt_addr);  
    
  34. while (1)   
    
  35.     cfd = Accept(lfd, (struct sockaddr *)&clt_addr, &clt_addr_len);  
    
  36.     pid = fork();  
    
  37.     if (pid < 0)   
    
  38.         perr_exit("fork error");  
    
  39.      else if (pid == 0)   
    
  40.         close(lfd);  
    
  41.         break;          
    
  42.      else   
    
  43.         struct sigaction act;  
    
  44.         act.sa_handler = catch_child;  
    
  45.         sigemptyset(&act.sa_mask);  
    
  46.         act.sa_flags = 0;  
    
  47.         ret = sigaction(SIGCHLD, &act, NULL);  
    
  48.         if (ret != 0)   
    
  49.            perr_exit("sigaction error");  
    
  50.           
    
  51.         close(cfd);   
    
  52.         continue;  
    
  53.       
    
  54.   
    
  55. if (pid == 0)   
    
  56.     for (;;)   
    
  57.         ret = Read(cfd, buf, sizeof(buf));  
    
  58.         if (ret == 0)   
    
  59.             close(cfd);  
    
  60.             exit(1);  
    
  61.            
    
  62.         for (i = 0; i < ret; i++)  
    
  63.             buf[i] = toupper(buf[i]);  
    
  64.         write(cfd, buf, ret);  
    
  65.         write(STDOUT_FILENO, buf, ret);  
    
  66.       
    
  67.   
    
  68. return 0;  
    

这样,当子进程退出时,父进程收到信号,就会去回收子进程了,不会出现僵尸进程。

40P-多进程服务器测试IP地址调整
使用桥接模式,让自己主机和其他人主机处于同一个网段

41P-服务器程序上传外网服务器并访问
scp -r 命令,将本地文件拷贝至远程服务器上目标位置
scp -r 源地址 目标地址

42P-多线程服务器代码review
代码如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <arpa/inet.h>
  4. #include <pthread.h>
  5. #include <ctype.h>
  6. #include <unistd.h>
  7. #include <fcntl.h>
  8. #include “wrap.h”
  9. #define MAXLINE 8192
  10. #define SERV_PORT 8000
  11. struct s_info //定义一个结构体, 将地址结构跟cfd捆绑
  12. struct sockaddr_in cliaddr;  
    
  13. int connfd;  
    
  14. ;
  15. void *do_work(void *arg)
  16. int n,i;  
    
  17. struct s_info *ts = (struct s_info*)arg;  
    
  18. char buf[MAXLINE];  
    
  19. char str[INET_ADDRSTRLEN];      //#define INET_ADDRSTRLEN 16  可用"[+d"查看  
    
  20. while (1)   
    
  21.     n = Read(ts->connfd, buf, MAXLINE);                     //读客户端  
    
  22.     if (n == 0)   
    
  23.         printf("the client %d closed...\\n", ts->connfd);  
    
  24.         break;                                              //跳出循环,关闭cfd  
    
  25.       
    
  26.     printf("received from %s at PORT %d\\n",  
    
  27.             inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),  
    
  28.             ntohs((*ts).cliaddr.sin_port));                 //打印客户端信息(IP/PORT)  
    
  29.     for (i = 0; i < n; i++)   
    
  30.         buf[i] = toupper(buf[i]);                           //小写-->大写  
    
  31.     Write(STDOUT_FILENO, buf, n);                           //写出至屏幕  
    
  32.     Write(ts->connfd, buf, n);                              //回写给客户端  
    
  33.   
    
  34. Close(ts->connfd);  
    
  35. return (void *)0;  
    
  36. int main(void)
  37. struct sockaddr_in servaddr, cliaddr;  
    
  38. socklen_t cliaddr_len;  
    
  39. int listenfd, connfd;  
    
  40. pthread_t tid;  
    
  41. struct s_info ts[256];      //创建结构体数组.  
    
  42. int i = 0;  
    
  43. listenfd = Socket(AF_INET, SOCK_STREAM, 0);                     //创建一个socket, 得到lfd  
    
  44. bzero(&servaddr, sizeof(servaddr));                             //地址结构清零  
    
  45. servaddr.sin_family = AF_INET;  
    
  46. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);                               //指定本地任意IP  
    
  47. servaddr.sin_port = htons(SERV_PORT);                                       //指定端口号   
    
  48. Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));             //绑定  
    
  49. Listen(listenfd, 128);                                                      //设置同一时刻链接服务器上限数  
    
  50. printf("Accepting client connect ...\\n");  
    
  51. while (1)   
    
  52.     cliaddr_len = sizeof(cliaddr);  
    
  53.     connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);   //阻塞监听客户端链接请求  
    
  54.     ts[i].cliaddr = cliaddr;  
    
  55.     ts[i].connfd = connfd;  
    
  56.     pthread_create(&tid, NULL, do_work, (void*)&ts[i]);  
    
  57.     pthread_detach(tid);                                                    //子线程分离,防止僵线程产生.  
    
  58.     i++;  
    
  59.   
    
  60. return 0;  
    

编译运行,结果如下:

43P-read返回值和总结

三次握手:

主动发起连接请求端,发送 SYN 标志位,请求建立连接。 携带序号号、数据字节数(0)、滑动窗口大小。

被动接受连接请求端,发送 ACK 标志位,同时携带 SYN 请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小。

主动发起连接请求端,发送 ACK 标志位,应答服务器连接请求。携带确认序号。

四次挥手:

主动关闭连接请求端, 发送 FIN 标志位。 

被动关闭连接请求端, 应答 ACK 标志位。 		 ----- 半关闭完成。


被动关闭连接请求端, 发送 FIN 标志位。

主动关闭连接请求端, 应答 ACK 标志位。		 ----- 连接全部关闭

滑动窗口:

发送给连接对端,本端的缓冲区大小(实时),保证数据不会丢失。

错误处理函数:

封装目的: 

	在 server.c 编程过程中突出逻辑,将出错处理与逻辑分开,可以直接跳转man手册。


【wrap.c】								【wrap.h】


存放网络通信相关常用 自定义函数						存放 网络通信相关常用 自定义函数原型(声明)。

命名方式:系统调用函数首字符大写, 方便查看man手册
	
	  如:Listen()、Accept();

函数功能:调用系统调用函数,处理出错场景。

在 server.c 和 client.c 中调用 自定义函数

联合编译 server.c 和 wrap.c 生成 server

	 client.c 和 wrap.c 生成 client

readn:
读 N 个字节

readline:

读一行

read 函数的返回值:

1. > 0 实际读到的字节数

2. = 0 已经读到结尾(对端已经关闭)【 !重 !点 !】

3. -1 应进一步判断errno的值:

	errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式 读。 没有数据到达。 

	errno = EINTR 慢速系统调用被 中断。

	errno = “其他情况” 异常。

多进程并发服务器:server.c

1. Socket();		创建 监听套接字 lfd
2. Bind()	绑定地址结构 Strcut scokaddr_in addr;
3. Listen();	
4. while (1) 

	cfd = Accpet();			接收客户端连接请求。
	pid = fork();
	if (pid == 0)			子进程 read(cfd) --- 小-》大 --- write(cfd)

		close(lfd)		关闭用于建立连接的套接字 lfd

		read()
		小--大
		write()

	 else if (pid > 0) 	

		close(cfd);		关闭用于与客户端通信的套接字 cfd	
		contiue;
	
  

5. 子进程:

	close(lfd)

	read()

	小--大

	write()	

   父进程:

	close(cfd);

	注册信号捕捉函数:	SIGCHLD

	在回调函数中, 完成子进程回收

		while (waitpid());

多线程并发服务器: server.c

1. Socket();		创建 监听套接字 lfd

2. Bind()		绑定地址结构 Strcut scokaddr_in addr;

3. Listen();		

4. while (1) 		

	cfd = Accept(lfd, );

	pthread_create(&tid, NULL, tfn, (void *)cfd);

	pthread_detach(tid);  				// pthead_join(tid, void **);  新线程---专用于回收子线程。
  

5. 子线程:

	void *tfn(void *arg) 
	
		// close(lfd)			不能关闭。 主线程要使用lfd

		read(cfd)

		小--大

		write(cfd)

		pthread_exit((void *)10);	
	

44P-复习

45P-TCP状态-主动发起连接
46P-TCP状态-主动关闭连接
47P-TCP状态-被动接收连接
48P-TCP状态-被动关闭连接
49P-2MSL时长
50P-TCP状态-其他状态

netstat -apn | grep client 查看客户端网络连接状态
netstat -apn | grep port 查看端口的网络连接状态

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_WAIT(只有主动关闭连接方,会经历该状态)

			-- 等 2MSL时长 -- CLOSE 

3. 被动接收连接请求端: CLOSE -- LISTEN -- 接收 SYN -- LISTEN -- 发送 ACK、SYN -- SYN_RCVD -- 接收ACK -- ESTABLISHED(数据通信态)

4. 被动关闭连接请求端: ESTABLISHED(数据通信态) -- 接收 FIN -- ESTABLISHED(数据通信态) -- 发送ACK 

			-- CLOSE_WAIT (说明对端【主动关闭连接端】处于半关闭状态) -- 发送FIN -- LAST_ACK -- 接收ACK -- CLOSE


重点记忆: ESTABLISHED、FIN_WAIT_2 <--> CLOSE_WAIT、TIME_WAIT(2MSL)

netstat -apn | grep  端口号

2MSL时长:

一定出现在【主动关闭连接请求端】。 --- 对应 TIME_WAIT 状态。

保证,最后一个 ACK 能成功被对端接收。(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求。)

51P-端口复用函数
52P-半关闭及shutdown函数
端口复用:

int opt = 1;		// 设置端口复用。

setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));

半关闭:

通信双方中,只有一端关闭通信。  --- FIN_WAIT_2

close(cfd);

shutdown(int fd, int how);	

	how: 	SHUT_RD	关读端

		SHUT_WR	关写端

		SHUT_RDWR 关读写

shutdown在关闭多个文件描述符应用的文件时,采用全关闭方法。close,只关闭一个。

53P-多路IO转接服务器设计思路

54P-select函数参数简介
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

	nfds:监听的所有文件描述符中,最大文件描述符+1

	readfds: 读 文件描述符监听集合。	传入、传出参数

	writefds:写 文件描述符监听集合。	传入、传出参数		NULL

	exceptfds:异常 文件描述符监听集合	传入、传出参数		NULL

	timeout: 	> 0: 	设置监听超时时长。

			NULL:	阻塞监听

			0:	非阻塞监听,轮询
	返回值:

		> 0:	所有监听集合(3个)中, 满足对应事件的总数。

		0:	没有满足监听条件的文件描述符

		-1: 	errno

55P-中午复习

56P-select函数原型分析
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

	nfds:监听的所有文件描述符中,最大文件描述符+1

	readfds: 读 文件描述符监听集合。	传入、传出参数

	writefds:写 文件描述符监听集合。	传入、传出参数		NULL

	exceptfds:异常 文件描述符监听集合	传入、传出参数		NULL

	timeout: 	> 0: 	设置监听超时时长。

			NULL:	阻塞监听

			0:	非阻塞监听,轮询
	返回值:

		> 0:	所有监听集合(3个)中, 满足对应事件的总数。

		0:	没有满足监听条件的文件描述符

		-1: 	errno

57P-select相关函数参数分析
void FD_CLR(int fd, fd_set *set) 把某一个fd清除出去
int FD_ISSET(int fd, fd_set *set) 判定某个fd是否在位图中
void FD_SET(int fd, fd_set *set) 把某一个fd添加到位图
void FD_ZERO(fd_set *set) 位图所有二进制位置零

select多路IO转接:

原理:  借助内核, 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);	FD_SET(5, &rset);	FD_SET(6, &rset);


void FD_CLR(int fd, fd_set *set);	--- 将一个文件描述符从监听集合中 移除。

	FD_CLR(4, &rset);

int  FD_ISSET(int fd, fd_set *set);	--- 判断一个文件描述符是否在监听集合中。

	返回值: 在:1;不在:0;

	FD_ISSET(4, &rset);

58P-select实现多路IO转接设计思路
思路分析:

int maxfd = 0;

lfd = socket() ;			创建套接字

maxfd = lfd;

bind();					绑定地址结构

listen();				设置监听上限

fd_set rset, allset;			创建r监听集合

FD_ZERO(&allset);				将r监听集合清空

FD_SET(lfd, &allset);			将 lfd 添加至读集合中。

while(1) 

	rset = allset;			保存监听集合

	ret  = select(lfd+1, &rset, NULL, NULL, NULL);		监听文件描述符集合对应事件。

	if(ret > 0) 							有监听的描述符满足对应事件
	
		if (FD_ISSET(lfd, &rset)) 				// 1 在。 0不在。

			cfd = accept();				建立连接,返回用于通信的文件描述符

			maxfd = cfd;

			FD_SET(cfd, &allset);				添加到监听通信描述符集合中。
		

		for (i = lfd+1; i <= 最大文件描述符; i++)

			FD_ISSET(i, &rset)				有read、write事件

			read()

			小 -- 大

			write();
			
	

59P-select实现多路IO转接-代码review

代码如下:

  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. #include “wrap.h”
  8. #define SERV_PORT 6666
  9. int main(int argc, char *argv[])
  10. int i, j, n, nready;  
    
  11. int maxfd = 0;  
    
  12. int listenfd, connfd;  
    
  13. char buf[BUFSIZ];         /* #define INET_ADDRSTRLEN 16 */  
    
  14. struct sockaddr_in clie_addr, serv_addr;  
    
  15. socklen_t clie_addr_len;  
    
  16. listenfd = Socket(AF_INET, SOCK_STREAM, 0);    
    
  17. int opt = 1;  
    
  18. setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  
    
  19. bzero(&serv_addr, sizeof(serv_addr));  
    
  20. serv_addr.sin_family= AF_INET;  
    
  21. serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    
  22. serv_addr.sin_port= htons(SERV_PORT);  
    
  23. Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));  
    
  24. Listen(listenfd, 128);  
    
  25. fd_set rset, allset;                            /* rset 读事件文件描述符集合 allset用来暂存 */  
    
  26. maxfd = listenfd;  
    
  27. FD_ZERO(&allset);  
    
  28. FD_SET(listenfd, &allset);                                  /* 构造select监控文件描述符集 */  
    
  29. while (1)      
    
  30.     rset = allset;                                          /* 每次循环时都从新设置select监控信号集 */  
    
  31.     nready = select(maxfd+1, &rset, NULL, NULL, NULL);  
    
  32.     if (nready < 0)  
    
  33.         perr_exit("select error");  
    
  34.     if (FD_ISSET(listenfd, &rset))                         /* 说明有新的客户端链接请求 */  
    
  35.         clie_addr_len = sizeof(clie_addr);  
    
  36.         connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);       /* Accept 不会阻塞 */  
    
  37.         FD_SET(connfd, &allset);                            /* 向监控文件描述符集合allset添加新的文件描述符connfd */  
    
  38.         if (maxfd < connfd)  
    
  39.             maxfd = connfd;  
    
  40.         if (0 == --nready)                                  /* 只有listenfd有事件, 后续的 for 不需执行 */  
    
  41.             continue;  
    
  42.        
    
  43.     for (i = listenfd+1; i <= maxfd; i++)                  /* 检测哪个clients 有数据就绪 */  
    
  44.         if (FD_ISSET(i, &rset))   
    
  45.             if ((n = Read(i, buf, sizeof(buf))) == 0)     /* 当client关闭链接时,服务器端也关闭对应链接 */  
    
  46.                 Close(i);  
    
  47.                 FD_CLR(i, &allset);                        /* 解除select对此文件描述符的监控 */  
    
  48.              else if (n > 0)   
    
  49.                 for (j = 0; j < n; j++)  
    
  50.                     buf[j] = toupper(buf[j]);  
    
  51.                 Write(i, buf, n);  
    
  52.               
    
  53.           
    
  54.       
    
  55.   
    
  56. Close(listenfd);  
    
  57. return 0;  
    

编译运行,结果如下:

如图,借助select也可以实现多线程

60P-select实现多路IO转接-代码实现
61P-select实现多路IO转接-添加注释

代码太长了,直接看59话吧

62P-select优缺点

select优缺点:

缺点:	监听上限受文件描述符限制。 最大 1024.

	检测满足条件的fd, 自己添加业务逻辑提高小。 提高了编码难度。

优点:	跨平台。win、linux、macOS、Unix、类Unix、mips

select代码里有个可以优化的地方,用数组存下文件描述符,这样就不需要每次扫描一大堆无关文件描述符了

63P-添加一个自定义数组提高效率
这里就是改进之前代码的问题,之前的代码,如果最大fd是1023,每次确定有事件发生的fd时,就要扫描3-1023的所有文件描述符,这看起来很蠢。于是定义一个数组,把要监听的文件描述符存下来,每次扫描这个数组就行了。看起来科学得多。

如图,加个client数组,存要监听的描述符。
代码如下,挺长的

  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. #include “wrap.h”
  8. #define SERV_PORT 6666
  9. int main(int argc, char *argv[])
  10. int i, j, n, maxi;  
    
  11. int nready, client[FD_SETSIZE];                 /* 自定义数组client, 防止遍历1024个文件描述符  FD_SETSIZE默认为1024 */  
    
  12. int maxfd, listenfd, connfd, sockfd;  
    
  13. char buf[BUFSIZ], str[INET_ADDRSTRLEN];         /* #define INET_ADDRSTRLEN 16 */  
    
  14. struct sockaddr_in clie_addr, serv_addr;  
    
  15. socklen_t clie_addr_len;  
    
  16. fd_set rset, allset;                            /* rset 读事件文件描述符集合 allset用来暂存 */  
    
  17. listenfd = Socket(AF_INET, SOCK_STREAM, 0);  
    
  18. int opt = 1;  
    
  19. setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  
    
  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. Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));  
    
  25. Listen(listenfd, 128);  
    
  26. maxfd = listenfd;                                           /* 起初 listenfd 即为最大文件描述符 */  
    
  27. maxi = -1;                                                  /* 将来用作client[]的下标, 初始值指向0个元素之前下标位置 */  
    
  28. for (i = 0; i < FD_SETSIZE; i++)  
    
  29.     client[i] = -1;                                         /* 用-1初始化client[] */  
    
  30. FD_ZERO(&allset);  
    
  31. FD_SET(listenfd, &allset);                                  /* 构造select监控文件描述符集 */  
    
  32. while (1)      
    
  33.     rset = allset;                                          /* 每次循环时都重新设置select监控信号集 */  
    
  34.     nready = select(maxfd+1, &rset, NULL, NULL, NULL);  //2  1--lfd  1--connfd  
    
  35.     if (nready < 0)  
    
  36.         perr_exit("select error");  
    
  37.     if (FD_ISSET(listenfd, &rset))                         /* 说明有新的客户端链接请求 */  
    
  38.         clie_addr_len = sizeof(clie_addr);  
    
  39.         connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);       /* Accept 不会阻塞 */  
    
  40.         printf("received from %s at PORT %d\\n",  
    
  41.                 inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),  
    
  42.                 ntohs(clie_addr.sin_port));  
    
  43.         for (i = 0; i < FD_SETSIZE; i++)  
    
  44.             if (client[i] < 0)                             /* 找client[]中没有使用的位置 */  
    
  45. <

    以上是关于Linux网络编程——黑马程序员笔记的主要内容,如果未能解决你的问题,请参考以下文章

    [学习笔记]黑马程序员-Hadoop入门视频教程

    黑马公开课——运行原理与GC学习笔记

    黑马程序员 C++教程从0到1入门编程笔记3C++核心编程(内存分区模型引用函数提高)

    黑马程序员 C++教程从0到1入门编程笔记5C++核心编程(类和对象——继承多态)

    黑马程序员 C++教程从0到1入门编程笔记5C++核心编程(类和对象——继承多态)

    黑马程序员 C++教程从0到1入门编程笔记6C++核心编程(文件操作)