Linux网络编程
TCP/IP与OSI
网络互联促成了TCP/IP协议的产生:
- TCP协议分成两个不同的协议:
- 用来检测网络传输差错的传输控制协议TCP
- 专门负责对不同网络进行互联的互联网协议IP
- 从此,TCP/IP协议产生。
网络体系结构:
- 网络采用分而治之的方法设计,将网络的功能划分成不同的模块,以分层的形式有机组合在一起。
- 每层实现不同的功能,其内部实现方法对外部其他层次来说都是透明的。每层向上层提供服务。同时使用下层提供的服务。
- 网络体系结构即指网络的层次结构和每层所使用协议的集合。
- 两类非常重要的体系结构:OSI与TCP/IP
OSI模型:
- 应用层
- 表示层
- 会话层
- 传输层
- 网络层
- 数据链路层
- 物理层
TCP模型:
- 应用层(FTP、HTTP、DNS、SMTP)----应用部分----
- 传输层(TCP、UDP)------Linux内核部分-----
- 网络层(IP、ICMP、IGMP)
- 网络接口和物理层。(以太网、令牌环网、FDDI等)
socket编程基础
流式套接字(SOCK_STREAM):唯一对应着TCP
- 提供了一个面向连接、可靠的数据传输服务、数据无差错、无重复的发送和接收。内设置流量控制。
数据包套接字(SOCK_DGRAM):唯一对应着UDP
- 提供无连接服务,数据包以独立数据包的形式发送,不提供无差错保证。数据可能会丢失重复,顺序发送,但可能会乱序接收。
原始套接字(SOCK_RAW):对应着多个协议,发送穿透了传输层
- 可以对较低层次协议,如IP、ICMP直接访问。
IP地址
IP地址是Internet中主机标志。
- IP地址分为IPV4和IPV6。IPV4采用32位的整数表示。IPV6采用128位整数表示。
- mobileIPV6:local IP(本地注册IP)、roam IP(漫游IP)
IPV4地址
- 点分形式:192.168.0.246
- 32位整数
特殊的IP地址
- 局域网的IP:192.XXX.XXX.XXX、10.xxx。xxx。xxx
- 广播IP:XXX.XXX.XXX.255 255.255.255.255(全网广播)
- 组播IP:224.xxx.xxx.xxx ~ 239.xxx.xxx.xxx
端口号
- 为了区分一台主机接收到的数据包应该转交给哪个任务来进行处理,使用端口号来进行区分。
- TCP端口号和UDP端口号是独立的。
- 端口号一般有LANA
- 保留端口:1~1023(FTP:23 SSH:22 HTTP:80 HTTPS:469)
TCP编程
tcp编程API:
- socket();//建立socket套接字
- bind();//套接字绑定端口
- listen();//监听端口
- accept();//接收客户端链接
- connect();//与服务端建立socket链接
- read();//读取套接字内容,会阻塞
- write();//向socket套接字中写内容。
- close();//关闭socket链接。
TCP并发服务器:
- TCP循环服务器:
socket(); bind(); listen(); while(1){ accept(); process(); close(); }
- TCP 多进程并发服务器:
socket(); bind(); listen(); while(1){ accept(); if(fork() == 0){ process(); close(); exit(); } close(); }
- TCP多线程并发服务器:
socket(); bind((); listen(); while(1){ accept(); if(pthread_create() != -1){ process(); close(); exit(); } close(); }
UDP网络编程
API
- send()//类似write()
- recv()//类似read()
服务端
- socket()
- bind()//绑定IP、端口
- recvfrom()//阻塞等待接收消息
- sendto()//发送消息
- close()
客户端
- socket()
- sendto()//发送消息
- sendto(int socket, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
- dest_addr–发送目标的sock信息。
- addrlen–发送目标的sock结构体长度。
- recvfrom()//接收消息
- recvfrom(int sockfd, void *buf, size_t, len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
- src_addr–发送端的socket信息。
- close()//关闭socket套接字
udp-server:
/************************************************************************* > File Name: udp_demo.c > Author: > Mail: > Created Time: 2019年05月25日 星期六 07时04分23秒 ************************************************************************/ #include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<strings.h> #include<string.h> #include<arpa/inet.h> #define BUFSIZE 128 #define SERV_PORT 8888 #define QUIT_STR "quit" int main(){ //create socket int fd = -1; fd = socket(AF_INET,SOCK_DGRAM, 0); if(fd < 0){ perror("socket fail\n"); return -1; } int b_reuse = 1; //bind setopt //setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); //bind struct sockaddr_in sin; bzero(&sin, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(SERV_PORT); sin.sin_addr.s_addr = inet_addr("127.0.0.1"); int ret = bind(fd, (struct sockaddr *)&sin, sizeof(sin)); if(ret < 0){ perror("bind fail\n"); return -1; } char buf[BUFSIZE]; struct sockaddr_in client_sin; socklen_t addrlen = sizeof(client_sin); char ipv4_addr[16]; while(1){ bzero(buf, BUFSIZE); bzero(ipv4_addr, sizeof(ipv4_addr)); printf("recv msg waiting...\n"); ret = recvfrom(fd, buf, sizeof(buf), 0,(struct sockaddr *)&client_sin, &addrlen); printf("recv msg success\n"); if(ret < 0){ perror("recvfrom fail\n"); continue; } if(!inet_ntop(AF_INET, (void *)&client_sin.sin_addr, ipv4_addr, sizeof(client_sin))){ perror("inet_ntop fail\n"); return -1; } printf("Recived from(%s:%d), data: %s\n", ipv4_addr, ntohs(client_sin.sin_port), buf); if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))){//recv quit printf("Client(%s:%d) is exiting!\n", ipv4_addr, ntohs(client_sin.sin_port)); break; } } printf("function over\n"); return 0; }
udp-client
/************************************************************************* > File Name: udp_demo.c > Author: > Mail: > Created Time: 2019年05月26日 星期日 07时03分53秒 ************************************************************************/ #include<stdio.h> #include<arpa/inet.h> #include<sys/types.h> #include<sys/socket.h> #include<strings.h> #include<string.h> #include<stdlib.h> #define BUFSIZE 128 #define QUIT_STR "quit" void usage(char *s){ printf("\n This is udp demo!\n"); printf("\nUsage:\n\t %s serv_ip serv_port", s); printf("\n\t serv_ip: udp server ip address"); printf("\n\t serv_port: udp server port \n\n"); } int main(int argc, char **argv){ int fd = -1; struct sockaddr_in sin; if(argc != 3){ usage(argv[0]); printf("31\n"); return -1; } int port = 0; port = atoi(argv[2]); if(port < 0 ||(port > 50000)){ usage(argv[0]); printf("input port = [%d] 38\n", port); return -1; } if(fd = socket(AF_INET, SOCK_DGRAM, 0) < 0){ perror("socket fail\n"); return -1; } bzero(&sin, sizeof(sin)); printf("-->port=[%d], addr=[%s]\n", port, argv[1]); sin.sin_family = AF_INET; sin.sin_port = htons(port); sin.sin_addr.s_addr = inet_addr(argv[1]); // if(inet_pton(AF_INET, argv[1], (void *)&sin.sin_addr) != 1){ // perror("inet_pton\n"); // return -1; // } char buf[BUFSIZE]; printf("UDP client starting ...OK\n"); while(1){ bzero(buf, BUFSIZE); printf("Please input the string to server:\n"); if(fgets(buf, BUFSIZE -1, stdin) == NULL){ perror("fgets fail\n"); continue; } sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin)); if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))){ printf("Client is exited\n"); break; } } printf("function over!\n"); return 0; }
网络编程 I/O多路复用
阻塞I/O模式
- 阻塞 I/O模式是最普通使用的I/O模式,大部分程序使用的都是阻塞模式的I/O
- 缺省情况下,套接字建立后所处于的模式就是阻塞I/O模式。
- 前面学习的很多读写函数在调用过程中会发生阻塞。
- 读操作中的read、recv、recvfrom
- 写操作中的write、send
- 其他操作:accept、connect
非阻塞I/O模式
- 当我们将一个套接字设置为非阻塞模式,我们相当于高速系统内核,如果请求的I/O操作不能干马上完成就马上返回一个错误给我。
- 当一个应用程序使用非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可度,(成为polling)
- 应用程序不停的polling内核来检查是否I/O操作已经就绪,这将是一个极浪费CPU资源的操作。
- 这种模式使用中不普遍。
- 使用fcntl()函数来设置一个套接字描述符为非阻塞模式
多路复用
Linux中每个进程默认情况下,最多可以打开1024个文件,最多有1024个文件描述符
文件描述符的特点:
- 非负整数
- 从最小可用的数字来分配
- 每个进程启动时默认打开0、1、2三个文件描述符。
- 多路复用API
- select()
int main(){ fd_set rset; fd = socket(); bind(); listen(); maxfd = fd; FD_ZERO(&rset); FD_SET(fd, &rset);//将建立好的套接字加入集合中。 struct timeval tout; tout.tv_sec = 5; tout.tv_usec = 0; select(maxfd +1 , &rset, NULL, NULL, &tout); if(FD_ISSET(fd, &rset)){ newfd = accept(fd, ...); } //依次判断已建立的客户端是否有数据。 }