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, ...);
}
//依次判断已建立的客户端是否有数据。
}