3.9.1.linux网络编程框架
3.9.1.一、网络是分层的
(1)OSI 7层模型(理论指导)
(2)网络为何要分层
(3)网络分层的具体表现
3.9.1.二、TCP/IP协议引入(网络分层实现的具体实现)
(1)TCP/IP协议是用的最多的网络协议实现
(2)TCP/IP分为4层,对应OSI的7层
(3)咱们编程时最关注【应用层】,了解传输层(TCP/UDP/TFTP),网际互联层和网络接入层不用管
3.9.1.三、BS和CS
(1)CS架构介绍(client server, 客户端服务器架构)
(2)BS架构介绍(broswer server,浏览器服务器架构)
-----------------------------------------------------------------------------------------------------------------------------------------------------------
网络编程补充:注意在不一样函数中sockfd是不一样的。
服务器端
(1)建立套接字函数:int socket(int domain,int type,int protocol);第一个参数是建立套接字所使用的协议栈,一般为AF_INET;di第二个参数是用来指定套接字的类型,即指定数据流套接字(SOCK_STREAM)仍是数据报套接字(SOCK_DGRAM);参数protocol一般设置为0.返回一个socket描述符。
(2)绑定套接字:将套接字与计算机上的某个端口/IP绑定,进而在该端口监听服务请求,使用 int bind(int sockfd,struct sockaddr *my_addr,int addrlen),咱们计算机存储数据的方式是低位字节优先,而数据在网络上传输的时候是高位字节优先,因此通常须要先进行字节转换。其中htonl()和htons()函数是把主机字节顺序转化为网络字节顺序;ntohl()函数和ntohs()是将网络字节顺序转化为主机字节顺序。
(3)监听网络端口请求:int listen(int sockfd,int backlog),backlog用来设置请求队列中容许的最大请求数。注意:TCP传输中为被动套接字设置了两个队列:彻底创建链接的队列和未彻底创建链接的队列。
(4)接收链接请求:从彻底创建链接的队列中接收一个链接,使用int accept(int sockfd,void *addr,int *addrlen);参数addr为一个指向sockaddr_in结构体的指针,这个结构体是客户端的IP地址和端口。服务器接收客户端的链接请求后,accept函数会返回一个新的socket描述符,服务器进程可使用这个新的描述符同客户进程传输数据。
(5)面向链接的数据传输:面向链接就是说在链接成功以后才会进行数据传输。 int send(int sockfd,const void *msg,int len,unsigned int flags);其中第一个参数为第四个步骤中产生的新的socket描述符;参数msg为一个指针,指向要发送的数据;参数len为要发送的数据长度;参数flag为控制选项,通常置0. int recv(int sockfd,void *buf,int len,unsigned int flags);其中第一个参数为第四个步骤中的socket描述符;参数buf为存放接收数据的缓冲区;参数len为缓冲的字节数;参数flags为控制选项,通常状况下设定为0,。
(6)关闭套接字:int close(int sockfd);其中参数为第四个步骤中产生的新的socket描述符
客户端
(1)建立套接字:int socket(int domain,int type,int protocol);第一个参数是建立套接字所使用的协议栈,一般为AF_INET;di第二个参数是用来指定套接字的类型,即指定数据流套接字(SOCK_STREAM)仍是数据报套接字(SOCK_DGRAM);参数protocol一般设置为0.返回一个socket描述符。
(2)与服务器创建链接:int connect(int sockfd,struct sockaddr *serv_addr,int *addrlen );其中第一个参数为第一个步骤中的socket描述符,第二个参数是指向sockaddr结构的指针,该结构体中包含了要链接的服务器端的IP地址和端口信息。
(3)面向链接的数据传输:面向链接就是说在链接成功以后才会进行数据传输。 int send(int sockfd,const void *msg,int len,unsigned int flags);其中第一个参数为第一个步骤中的socket描述符;参数msg为一个指针,指向要发送的数据;参数len为要发送的数据长度;参数flag为控制选项,通常置0. int recv(int sockfd,void *buf,int len,unsigned int flags);其中第一个参数为第一个步骤中的socket描述符;参数buf为存放接收数据的缓冲区;参数len为缓冲的字节数;参数flags为控制选项,通常状况下设定为0。
(4)关闭套接字:int close(int sockfd);其中参数为第一个步骤中的socket描述符
服务器模型
(1)循环服务器模型和并发服务器模型:由于对于循环服务器来讲,TCP循环服务器一次只可以处理一个客户端的请求,处理完成后才可以接受下一个请求,因此不多使用。咱们常用的是并发服务器模型,所谓并发,也就是说这种服务器模型在同一个时刻能够响应多个客户端的请求。它的核心思想就是每个客户端的请求并非由服务器进程直接处理,而是由服务器建立一个子进程来处理,这个优点能够弥补TCP循环服务器容易出现某个客户端独占服务端的缺陷。
(2)TCP并发服务器的模型以下:
(3)并发服务器模型代码示例:
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define SERV_PORT 5550 //服务器监听端口号
#define LENGTH 10 //请求队列的长度
#define SIZE 128 //缓冲区的长度
int main()
{
int res;
int pth; //定义建立子进程标识符
int sockfd; //定义监听sockfd描述符
int clientfd; //定义数据传输sockfd描述符
struct sockaddr_in hostaddr; //服务器(主机)IP地址和端口号信息
struct sockaddr_in clientaddr; //客户端IP地址和端口号信息
unsigned int addrlen;
char buf[SIZE]; //定义缓冲区
int cnt;
/*建立套接字*/
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
printf("套接字建立失败!\n");
exit(1);
}
/*将套接字与IP地址和端口号进行绑定*/
hostaddr.sin_family = AF_INET; //TCP/IP协议
hostaddr.sin_port = htons(SERV_PORT); //让系统随机选择一个未被占用的端口号
hostaddr.sin_addr.s_addr = INADDR_ANY; //服务器(主机)IP地址
bzero(&(hostaddr.sin_zero),8); //清零
res = bind(sockfd,(struct sockaddr *)&hostaddr,sizeof(struct sockaddr) );
if(res == -1)
{
perror("套接字绑定失败!\n");
exit(1);
}
/*将套接字设置为监听模式,以等待客户端的链接请求*/
res = listen(sockfd,LENGTH);
if(res == -1)
{
perror("设置监听模式失败!\n");
exit(1);
}
printf("等待客户端请求链接.......\n");
/*循环等待客户端的链接请求*/
while(1)
{
addrlen = sizeof(struct sockaddr_in);
clientfd =accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen);
/*接受一个客户端链接*/
if(clientfd == -1)
{
perror("与客户端链接错误\n");
continue;
}
pth = fork(); //建立子进程
if(pth<0)
{
perror("子进程建立失败\n");
exit(1);
}
if(pth == 0) //子进程,处理客户端的请求
{
// close(sockfd); //关闭父进程的套接字
printf("客户端IP:%s\n", inet_ntoa(clientaddr.sin_addr)); //输出客户端IP地址
cnt = recv (clientfd ,buf ,SIZE ,0); //接收客户端的数据
if(cnt==-1)
{
perror("接收客户端数据失败\n");
exit(1);
}
printf("收到的数据是: %s\n",buf);
close(clientfd); //关闭当前客户端的链接
exit(0); //子进程退出
}
close(clientfd); //父进程中,关闭子进程的套接字,准备接收下一个客户端的链接
}
return 0;
}
(4)多路复用 I/O 并发服务器:为了解决子进程带来的系统资源消耗问题
-----------------------------------------------------------------------------------------------------------------------------------------------------------
3.9.2.TCP协议的学习1
3.9.2.一、关于TCP理解的重点
(1)TCP协议工做在传输层,对上服务socket接口(操做系统跟网络编程有关的API),对下调用IP层(网络层)
(2)TCP协议面向链接,通讯前必须先3次握手(通过3个步骤的握手通讯)创建链接关系后才能开始通讯(打电话例子)。
qq聊天就是面向非链接的。
(3)TCP协议提供可靠传输,不怕【丢包】、【乱序】等。
3.9.2.二、TCP如何保证可靠传输
(1)TCP在传输有效信息前要求通讯双方必须先握手,创建链接才能通讯
(2)TCP的接收方收到数据包后会ack回应(每次通讯都有回应)给发送方,若发送方未收到ack会丢包重传
(3)TCP的有效数据内容会附带校验码,以防止内容在传递过程当中损坏
(4)TCP会根据网络带宽(网络带宽)来自动调节适配速率(滑动窗口技术)也就是说通讯双方的通讯速率不是固定的,通讯速率和两个方面有关:(1)一个包的大小(2)发送包的频率
(5)发送方会给各分割报文编号,接收方会校验编号,一旦顺序错误即会重传。
3.9.3.一、TCP创建链接时的三次握手
(1)创建链接须要三次握手
(2)创建链接的条件:服务器listen(监听:随时等待客户端的链接)时客户端主动发起connect
3.9.3.二、TCP关闭链接时的四次握手
(3)关闭链接须要四次握手
(4)服务器或者客户端均可以主动发起关闭
注:这些握手协议已经封装在TCP协议内部,socket编程接口平时不用管
3.9.3.三、基于TCP通讯的服务模式
(1)具备公网IP地址(在外网能够访问)的服务器(或者使用动态IP地址映射技术:花生壳)
(2)服务器端socket、bind、listen后处于监听状态
(3)客户端socket后,直接connect去发起链接。
(4)服务器收到并赞成客户端接入后会创建TCP链接,而后双方开始收发数据,收发时是双向的,并且双方都可发起
(5)双方都可发起关闭链接
3.9.3.四、常见的使用了TCP传输协议的网络应用
(1)http、ftp(单纯的传文件)
(2)QQ服务器
(3)mail服务器
3.9.4.socket编程接口介绍
3.9.4.一、创建链接
(1)socket。socket函数相似于open,用来打开一个网络链接,若是成功则返回一个网络文件描述符(int类型),以后咱们操做这个网络链接都经过这个网络文件描述符。
/*
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain表示是IPV4仍是IPV6.内部是一个宏定义,参数domain用来指定要建立的套接字所使用的协议栈,一般为AF_INET.表示互联网协议族(TCP/IP协议族)
type:用来指定套接字的类型
(1) SOCK_STREAM TCP协议 数据流套接字
(2)SOCK_DGRAM UDP协议 数据报套接字
(3)SOCK_SEQPACKET
protocol:一般设为0,让系统根据咱们以前设定的domain和type自动设置协议。
返回值:一个socket描述符,是指向内部数据结构的指针;在调用过程当中出现错误的时候会返回-1.
*/
(2)bind 绑定套接字函数
/* #include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *address,socklen_t address_len);
*/
sockfd为要绑定的socket描述符,address为一个指向本机IP地址和端口号等信息的sockaddr结构体的指针,address_len一般设置为sockaddr结构的长度。
返回值:函数调用成功后返回值为0,不然返回值为-1.
sockaddr结构用来保存socket信息:
struct sockaddr {
unsigned short sa_family; //表示套接字的协议栈地址,对于TCP/IP能够设置为 AF_INET
char sa_data[14]; //sa_data为套接字的IP地址和端口号
};
(3)listen
// int listen(int socket, int backlog);
backlog表示请求队列中容许的最大请求数。
(4)connect客户端
/* int connect(int socket, const struct sockaddr *address,socklen_t address_len);*/
返回成功返回值为0,不然为-1.
3.9.4.三、创建链接后进行数据的发送和接收
(1)send和write
/*ssize_t send(int socket, const void *buffer, size_t length, int flags);*/
flags:通常设置为0便可
(1)MSG_EOR:Terminates a record (if supported by the protocol).
(2)MSG_OOB:Sends out-of-band data on sockets that support out-of-band communications. The significance and semantics of out-of-band data are protocol-specific.
(2)recv和read
/* ssize_t recv(int socket, void *buffer, size_t length, int flags);*/
3.9.4.四、辅助性函数(主要是进行IP地址转换的)
(1)inet_aton、inet_addr、inet_ntoa
(2)inet_ntop(从32位二进制转换为点分十进制字符串)、inet_pton(从点分十进制字符串转换为32位二进制)(推荐使用)
注意:点分十进制字符串即:"192.168.1.11"类型
3.9.4.五、表示IP地址相关数据结构
(1)都定义在 netinet/in.h
(2)struct sockaddr,这个结构体是网络编程接口中用来表示一个IP地址的,注意这个IP地址是不区分IPv4和IPv6的(或者说是兼容IPv4和IPv6的)
(3)typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型(vi /usr/include/netinet/in.h)
(4)struct in_addr
{
in_addr_t s_addr;
};
(5)struct sockaddr_in 针对IPV4
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
(6)struct sockaddr 这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都须要这个结构体,这个结构体是兼容IPV4和IPV6的。在实际编程中这个结构体会被一个struct sockaddr_in或者一个struct sockaddr_in6所填充。
3.9.5.IP地址格式转换函数实践
补充:
(一)大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿相似于把数据看成字符串顺序处理地址由小向大增长,而数据从高位往低位放;
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和咱们的逻辑方法一致。
(二)
网络字节序:网络字节是只容许大段模式
电脑是小端模式:要转换为大端模式
3.9.5.一、inet_addr、inet_ntoa、inet_aton
3.9.5.二、inet_pton、inet_ntop
inet_addr解析:会自动检测咱们电脑是大端仍是小端
/*
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp); //参数是一个点分十进制的IP地址字符串,转为in_addr_t,即uint32_t网络字节大端格式的二进制
*/
inet_pton解析:参数是一个点分十进制的IP地址字符串,转为32位二进制 兼容IPV6
/*
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
*/
inet_ntop 解析:
/*
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
*/
代码示例:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define IPADDR "192.168.1.102"
// 0x66 01 a8 c0
// 102 1 168 192
// 网络字节序,其实就是大端模式
int main(void)
{
struct in_addr addr = {0};
char buf[50] = {0};
addr.s_addr = 0x6703a8c0;
inet_ntop(AF_INET, &addr, buf, sizeof(buf));
printf("ip addr = %s.\n", buf);
/* 模块二
// 使用inet_pton来转换
int ret = 0;
struct in_addr addr = {0};
ret = inet_pton(AF_INET, IPADDR, &addr);
if (ret != 1)
{
printf("inet_pton error\n");
return -1;
}
printf("addr = 0x%x.\n", addr.s_addr);
*/
/*模块一
in_addr_t addr = 0;
addr = inet_addr(IPADDR);
printf("addr = 0x%x.\n", addr); // 0x6601a8c0
*/
return 0;
}
3.9.6_7.soekct实践编程1_2
3.9.6.一、服务器端程序编写
服务器跟你的这台电脑的IP地址相绑定。当客户端路由到你这台电脑的IP后,怎么找到你这台电脑的具体哪一个服务器呢?答案就是经过端口号来标识。
IP地址用来精确到某一台电脑,端口号用来标识这台电脑的某一个具体进程。
(1)socket
(2)bind
(3)listen
(4)accept,返回值是一个fd,accept正确返回就表示咱们已经和前来链接个人客户端之间创建了一个TCP链接了,之后咱们就要经过这个链接来和客户端进行读写操做,读写操做就须要一个fd,这个fd就由accept来返回了。
注意:socket返回的fd叫作监听fd,是用来监听客户端的,也就是判断是哪个客户端链接来的,不能用来和任何客户端进行读写;accept返回的fd叫作链接fd,用来和链接那端的客户端程序进行读写。
3.9.6.二、客户端程序编写
(1)socket
(2)connect
概念:端口号,实质就是一个数字编号,用来在咱们一台主机中(主机的操做系统中)惟一的标识一个能上网的进程。端口号和IP地址一块儿会被打包到当前进程发出或者接收到的每个数据包中。每个数据包未来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),因此IP地址和端口号这两个每每是打包在一块儿不分家的。
3.9.8.socket实践编程3
3.9.8.一、客户端发送&服务器接收
3.9.8.二、服务器发送&客户端接收
3.9.8.三、探讨:如何让服务器和客户端好好沟通
(1)客户端和服务器原则上均可以任意的发和收,可是实际上双方必须配合:client发的时候server就收,而server发的时候client就收
(2)必须了解到的一点:client和server之间的通讯是异步的,这就是问题的根源
(3)解决方案:依靠应用层协议来解决。说白了就是咱们server和client事先作好一系列的通讯约定。
3.9.9.socket编程实践4
3.9.9.一、自定义应用层协议第一步:规定发送和接收方法
(1)规定链接创建后由客户端主动向服务器发出1个请求数据包,而后服务器收到数据包后回复客户端一个回应数据包,这就是一个通讯回合,完成了一次数据交换
(2)整个链接的通讯就是由N多个回合组成的。
3.9.9.二、自定义应用层协议第二步:定义数据包格式
3.9.9.三、经常使用应用层协议:http、ftp······
3.9.9.四、UDP简介