3.9.1.linux网络编程框架
3.9.1.1、网络是分层的
(1)OSI 7层模型(理论指导)
(2)网络为什么要分层
(3)网络分层的具体表现
3.9.1.2、TCP/IP协议引入(网络分层实现的具体实现)
(1)TCP/IP协议是用的最多的网络协议实现
(2)TCP/IP分为4层,对应OSI的7层
(3)我们编程时最关注【应用层】,了解传输层(TCP/UDP/TFTP),网际互联层和网络接入层不用管
3.9.1.3、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.1、关于TCP理解的重点
(1)TCP协议工作在传输层,对上服务socket接口(操作系统跟网络编程有关的API),对下调用IP层(网络层)
(2)TCP协议面向连接,通信前必须先3次握手(经过3个步骤的握手通信)建立连接关系后才能开始通信(打电话例子)。
qq聊天就是面向非连接的。
(3)TCP协议提供可靠传输,不怕【丢包】、【乱序】等。
3.9.2.2、TCP如何保证可靠传输
(1)TCP在传输有效信息前要求通信双方必须先握手,建立连接才能通信
(2)TCP的接收方收到数据包后会ack回应(每次通信都有回应)给发送方,若发送方未收到ack会丢包重传
(3)TCP的有效数据内容会附带校验码,以防止内容在传递过程中损坏
(4)TCP会根据网络带宽(网络带宽)来自动调节适配速率(滑动窗口技术)也就是说通信双方的通信速率不是固定的,通信速率和两个方面有关:(1)一个包的大小(2)发送包的频率
(5)发送方会给各分割报文编号,接收方会校验编号,一旦顺序错误即会重传。
3.9.3.1、TCP建立连接时的三次握手
(1)建立连接需要三次握手
(2)建立连接的条件:服务器listen(监听:随时等待客户端的连接)时客户端主动发起connect
3.9.3.2、TCP关闭连接时的四次握手
(3)关闭连接需要四次握手
(4)服务器或者客户端都可以主动发起关闭
注:这些握手协议已经封装在TCP协议内部,socket编程接口平时不用管
3.9.3.3、基于TCP通信的服务模式
(1)具有公网IP地址(在外网可以访问)的服务器(或者使用动态IP地址映射技术:花生壳)
(2)服务器端socket、bind、listen后处于监听状态
(3)客户端socket后,直接connect去发起连接。
(4)服务器收到并同意客户端接入后会建立TCP连接,然后双方开始收发数据,收发时是双向的,而且双方均可发起
(5)双方均可发起关闭连接
3.9.3.4、常见的使用了TCP传输协议的网络应用
(1)http、ftp(单纯的传文件)
(2)QQ服务器
(3)mail服务器
3.9.4.socket编程接口介绍
3.9.4.1、建立连接
(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.3、建立连接后进行数据的发送和接收
(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.4、辅助性函数(主要是进行IP地址转换的)
(1)inet_aton、inet_addr、inet_ntoa
(2)inet_ntop(从32位二进制转换为点分十进制字符串)、inet_pton(从点分十进制字符串转换为32位二进制)(推荐使用)
注意:点分十进制字符串即:"192.168.1.11"类型
3.9.4.5、表示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.1、inet_addr、inet_ntoa、inet_aton
3.9.5.2、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.1、服务器端程序编写
服务器跟你的这台电脑的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.2、客户端程序编写
(1)socket
(2)connect
概念:端口号,实质就是一个数字编号,用来在我们一台主机中(主机的操作系统中)唯一的标识一个能上网的进程。端口号和IP地址一起会被打包到当前进程发出或者接收到的每一个数据包中。每一个数据包将来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),所以IP地址和端口号这两个往往是打包在一起不分家的。
3.9.8.socket实践编程3
3.9.8.1、客户端发送&服务器接收
3.9.8.2、服务器发送&客户端接收
3.9.8.3、探讨:如何让服务器和客户端好好沟通
(1)客户端和服务器原则上都可以任意的发和收,但是实际上双方必须配合:client发的时候server就收,而server发的时候client就收
(2)必须了解到的一点:client和server之间的通信是异步的,这就是问题的根源
(3)解决方案:依靠应用层协议来解决。说白了就是我们server和client事先做好一系列的通信约定。
3.9.9.socket编程实践4
3.9.9.1、自定义应用层协议第一步:规定发送和接收方法
(1)规定连接建立后由客户端主动向服务器发出1个请求数据包,然后服务器收到数据包后回复客户端一个回应数据包,这就是一个通信回合,完成了一次数据交换
(2)整个连接的通信就是由N多个回合组成的。
3.9.9.2、自定义应用层协议第二步:定义数据包格式
3.9.9.3、常用应用层协议:http、ftp······
3.9.9.4、UDP简介
- (47)LINUX应用编程和网络编程之二Linux文件属性
Linux下的文件系统为树形结构,入口为/ 树形结构下的文件目录: 无论哪个版本的Linux系统,都有这些目录,这些目录应该是标准的.各个Linux发行版本会存在一些小小的差异,但总体来说,还是大体差 ...
- 网络编程4 网络编程之FTP上传简单示例&socketserver介绍&验证合法性连接
今日大纲: 1.FTP上传简单示例(详细代码) 2.socketserver简单示例&源码介绍 3.验证合法性连接//[秘钥加密(urandom,sendall)(注意:中文的!不能用)] 内 ...
- linux网络编程之shutdown() 与 close()函数详解
linux网络编程之shutdown() 与 close()函数详解 参考TCPIP网络编程和UNP: shutdown函数不能关闭套接字,只能关闭输入和输出流,然后发送EOF,假设套接字为A,那么这 ...
- 网络编程之UDP编程
网络编程之UDP编程 UDP协议是一种不可靠的网络协议,它在通信的2端各建立一个Socket,但是这个Socket之间并没有虚拟链路,这2个Socket只是发送和接受数据的对象,Java提供了Data ...
- 网络编程之TCP编程
网络编程之TCP编程 前面已经介绍过关于TCP协议的东西,这里不做赘述.Java对于基于TCP协议的网络通信提供了良好的封装,Java使用socket对象来代表两端的通信窗口,并通过Socket产生I ...
- 网络编程之C10K
网络编程之C10K 虽然在过去的十几年里C10K问题已经可以很好的解决,但学习网络编程时研究C10K问题仍然价值巨大,因为技术的发展都是有规律和线索可循的,了解C10K问题及其解决思路,通过举一反三, ...
- unix下网络编程之I/O复用(三)
poll函数 在上文unix下网络编程之I/O复用(二)中已经介绍了select函数的相关使用,本文将介绍另一个常用的I/O复用函数poll.poll提供的功能与select类似,不过在处理流设备时, ...
- 高并发网络编程之epoll详解(转载)
高并发网络编程之epoll详解(转载) 转载自:https://blog.csdn.net/shenya1314/article/details/73691088 在linux 没有实现epoll事件 ...
- 网络编程之socket
网络编程之socket socket:在网络编程中的一个基本组件,也称套接字. 一个套接字就是socket模块中的socket类的一个实例. 套接字包括两个: 服务器套接字和客户机套接字 套接字的实例 ...
随机推荐
- 启用hdfs的高可用
cm-HDFS: 选择另外一个节点的做NN, 生产选node3 选择三个节点作journalNode, node2,3,4 填入journalNode的目录/dfs/jn 经过一系列步骤,如果没报错 ...
- PTA(Basic Level)1061.判断题
判断题的评判很简单,本题就要求你写个简单的程序帮助老师判题并统计学生们判断题的得分. 输入格式: 输入在第一行给出两个不超过 100 的正整数 N 和 M,分别是学生人数和判断题数量.第二行给出 M ...
- P4962 朋也与光玉题解
题目链接 光坂小镇是一个由 n 个点(编号为 1 ~ n),m 条有向边构成的图,每个节点上都有一个光玉,光玉共有 k 种,编号为 0 ~ k−1. 为了使一切改变,朋也需要找齐全部的 k种光玉.他可 ...
- c++中的四种智能指针
c++中的四种智能指针 写惯了python,golang再来写c++总觉得头大,很大一个原因就是他没有一个GC机制. 不过c++中提供了智能指针,也不是不能用,李姐万岁! auto_ptr, shar ...
- Android 之 悬浮窗口
1. 创建并设置 WindowManager 类 WindowManager mWindowManager; // 取得系统窗体 mWindowManager = (WindowManager) ...
- ios / % 四舍五入 向上取整(ceil()) 向下取整(floor())
1. / //Test "/" cout << "Test \"/\"!" << endl; cout ...
- String与C风格字符串转换
String字符串转换为C风格字符串需要利用string类的成员函数c_str().而C风格字符串转换转换为string字符串可以直接利用运算符=.首先介绍c_str()函数原型: const val ...
- excle 文件的导入和导出
//excle 文件导出 public function excel(){ try{ include(BASE_PATH."Excel/PHPExcel.php"); // ech ...
- TCP/IP的网络客户端和服务器端程序
服务器端的过程可以分为以下几个步骤: (1) 初始化套接字的版本信息WSAStartup (2)创建套接字 ,需要两个套接字及客户端和服务器端的套接字 (3)绑定服务器(bind),该函数用于绑定服 ...
- Python线程学习
Python3 通过两个标准库 _thread 和 threading 提供对线程的支持. _thread 提供了低级别的.原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较 ...